* @author PrestaShop SA * @copyright 2017-2018 thirty bees * @copyright 2007-2016 PrestaShop SA * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) * PrestaShop is an internationally registered trademark & property of PrestaShop SA */ use Thirtybees\Core\Import\CSVDataSource; use Thirtybees\Core\Import\DataSourceInterface; use Thirtybees\Core\Import\ImportEntityType; /** * Class AdminImportControllerCore */ class AdminImportControllerCore extends AdminController { const MAX_COLUMNS = 6; const UNFRIENDLY_ERROR = false; const MAX_LINE_SIZE = 0; const ENTITY_TYPE_CATEGORIES = 'categories'; const ENTITY_TYPE_PRODUCTS = 'products'; const ENTITY_TYPE_COMBINATIONS = 'combinations'; const ENTITY_TYPE_CUSTOMERS = 'customers'; const ENTITY_TYPE_ADDRESSES = 'addresses'; const ENTITY_TYPE_MANUFACTURERS = 'manufacturers'; const ENTITY_TYPE_SUPPLIERS = 'suppliers'; const ENTITY_TYPE_ALIAS = 'alias'; const ENTITY_TYPE_STORE_CONTACTS = 'store_contacts'; const ENTITY_TYPE_SUPPLY_ORDERS = 'supply_orders'; const ENTITY_TYPE_SUPPLY_ORDER_DETAILS = 'supply_order_details'; /** @var array $columnMask */ public static $columnMask; /** @var array $defaultValues */ public static $defaultValues = []; /** @var callable[] $validators */ public static $validators; /** @var array $entitities */ public $entities = []; /** @var array $available_fields */ public $available_fields = []; /** @var array $required_fields */ public $required_fields = []; /** @var string $separator */ public $separator; /** @var string $multiple_value_separator */ public $multiple_value_separator; /** * Cached information returned by hook 'actionRegisterImportDataSource' * * @var array */ protected $registeredDataSources = null; /** * AdminImportControllerCore constructor. * * @throws PrestaShopException */ public function __construct() { @ini_set('max_execution_time', 0); $this->bootstrap = true; parent::__construct(); $this->entities = [ static::ENTITY_TYPE_CATEGORIES => $this->l('Categories'), static::ENTITY_TYPE_PRODUCTS => $this->l('Products'), static::ENTITY_TYPE_COMBINATIONS => $this->l('Combinations'), static::ENTITY_TYPE_CUSTOMERS => $this->l('Customers'), static::ENTITY_TYPE_ADDRESSES => $this->l('Addresses'), static::ENTITY_TYPE_MANUFACTURERS => $this->l('Manufacturers'), static::ENTITY_TYPE_SUPPLIERS => $this->l('Suppliers'), static::ENTITY_TYPE_ALIAS => $this->l('Alias'), static::ENTITY_TYPE_STORE_CONTACTS => $this->l('Store contacts'), ]; if (Configuration::get('PS_ADVANCED_STOCK_MANAGEMENT')) { $this->entities[static::ENTITY_TYPE_SUPPLY_ORDERS] = $this->l('Supply Orders'); $this->entities[static::ENTITY_TYPE_SUPPLY_ORDER_DETAILS] = $this->l('Supply Order Details'); } // Register module entity types foreach (static::getEntityTypes() as $key => $entityTypes) { $this->entities[$key] = $entityTypes->getName(); } $this->separator = substr(trim(Tools::getValue('separator', ',')), 0, 1); $this->multiple_value_separator = substr(trim(Tools::getValue('multiple_value_separator', ';')), 0, 1); // initialize selected entity $selectedEntity = $this->getSelectedEntity(); if (static::hasEntityType($selectedEntity)) { $this->available_fields = static::getEntityType($selectedEntity)->getAvailableFields(); return; } // initialize default validators static::$validators = [ 'active' => [static::class, 'getBoolean'], 'tax_rate' => [static::class, 'getPrice'], 'price_tex' => [static::class, 'getPrice'], 'price_tin' => [static::class, 'getPrice'], 'reduction_price' => [static::class, 'getPrice'], 'reduction_percent' => [static::class, 'getPrice'], 'wholesale_price' => [static::class, 'getPrice'], 'ecotax' => [static::class, 'getPrice'], 'name' => [static::class, 'createMultiLangField'], 'description' => [static::class, 'createMultiLangField'], 'additional_description' => [static::class, 'createMultiLangField'], 'description_short' => [static::class, 'createMultiLangField'], 'meta_title' => [static::class, 'createMultiLangField'], 'meta_keywords' => [static::class, 'createMultiLangField'], 'meta_description' => [static::class, 'createMultiLangField'], 'link_rewrite' => [static::class, 'createMultiLangField'], 'available_now' => [static::class, 'createMultiLangField'], 'available_later' => [static::class, 'createMultiLangField'], 'category' => [static::class, 'split'], 'online_only' => [static::class, 'getBoolean'], 'accessories' => [static::class, 'split'], 'image_alt' => [static::class, 'split'], ]; switch ($selectedEntity) { case static::ENTITY_TYPE_COMBINATIONS: $this->required_fields = [ 'group', 'attribute', ]; $this->available_fields = [ 'no' => ['label' => $this->l('Ignore this column')], 'id_product' => ['label' => $this->l('Product ID')], 'product_reference' => ['label' => $this->l('Product Reference')], 'group' => [ 'label' => $this->l('Attribute (Name:Type:Position)').'*', ], 'attribute' => [ 'label' => $this->l('Value (Value:Position)').'*', ], 'supplier_reference' => ['label' => $this->l('Supplier reference')], 'reference' => ['label' => $this->l('Reference')], 'ean13' => ['label' => $this->l('EAN13')], 'upc' => ['label' => $this->l('UPC')], 'wholesale_price' => ['label' => $this->l('Cost price')], 'price' => ['label' => $this->l('Impact on price')], 'ecotax' => ['label' => $this->l('Ecotax')], 'quantity' => ['label' => $this->l('Quantity')], 'minimal_quantity' => ['label' => $this->l('Minimal quantity')], 'weight' => ['label' => $this->l('Impact on weight')], 'default_on' => ['label' => $this->l('Default (0 = No, 1 = Yes)')], 'available_date' => ['label' => $this->l('Combination availability date')], 'image_position' => [ 'label' => $this->l('Choose among product images by position (1,2,3...)'), ], 'image_url' => ['label' => $this->l('Image URLs (x,y,z...)')], 'image_alt' => ['label' => $this->l('Image alt texts (x,y,z...)')], 'shop' => [ 'label' => $this->l('ID / Name of shop'), 'help' => $this->l('Ignore this field if you don\'t use the Multistore tool. If you leave this field empty, the default shop will be used.'), ], 'advanced_stock_management' => [ 'label' => $this->l('Advanced Stock Management'), 'help' => $this->l('Enable Advanced Stock Management on product (0 = No, 1 = Yes)'), ], 'depends_on_stock' => [ 'label' => $this->l('Depends on stock'), 'help' => $this->l('0 = Use quantity set in product, 1 = Use quantity from warehouse.'), ], 'warehouse' => [ 'label' => $this->l('Warehouse'), 'help' => $this->l('ID of the warehouse to set as storage.'), ], ]; static::$defaultValues = [ 'reference' => '', 'supplier_reference' => '', 'ean13' => '', 'upc' => '', 'wholesale_price' => 0, 'price' => 0, 'ecotax' => 0, 'quantity' => 0, 'minimal_quantity' => 1, 'weight' => 0, 'default_on' => 0, 'advanced_stock_management' => 0, 'depends_on_stock' => 0, 'available_date' => date('Y-m-d'), ]; break; case static::ENTITY_TYPE_CATEGORIES: $this->available_fields = [ 'no' => ['label' => $this->l('Ignore this column')], 'id' => ['label' => $this->l('ID')], 'active' => ['label' => $this->l('Active (0/1)')], 'name' => ['label' => $this->l('Name')], 'parent' => ['label' => $this->l('Parent category')], 'is_root_category' => [ 'label' => $this->l('Root category (0/1)'), 'help' => $this->l('A category root is where a category tree can begin. This is used with multistore.'), ], 'description' => ['label' => $this->l('Description')], 'meta_title' => ['label' => $this->l('Meta title')], 'meta_keywords' => ['label' => $this->l('Meta keywords')], 'meta_description' => ['label' => $this->l('Meta description')], 'link_rewrite' => ['label' => $this->l('Rewritten URL')], 'image' => ['label' => $this->l('Image URL')], 'shop' => [ 'label' => $this->l('ID / Name of shop'), 'help' => $this->l('Ignore this field if you don\'t use the Multistore tool. If you leave this field empty, the default shop will be used.'), ], 'additional_description' => ['label' => $this->l('Additional description')], ]; static::$defaultValues = [ 'active' => '1', 'parent' => Configuration::get('PS_HOME_CATEGORY'), 'link_rewrite' => '', ]; break; case static::ENTITY_TYPE_PRODUCTS: static::$validators['image'] = [ static::class, 'split' ]; $this->available_fields = [ 'no' => ['label' => $this->l('Ignore this column')], 'id' => ['label' => $this->l('ID')], 'active' => ['label' => $this->l('Active (0/1)')], 'name' => ['label' => $this->l('Name')], 'category' => ['label' => $this->l('Categories (x,y,z...)')], 'price_tex' => ['label' => $this->l('Price tax excluded')], 'price_tin' => ['label' => $this->l('Price tax included')], 'id_tax_rules_group' => ['label' => $this->l('Tax rule ID')], 'wholesale_price' => ['label' => $this->l('Cost price')], 'on_sale' => ['label' => $this->l('On sale (0/1)')], 'reduction_price' => ['label' => $this->l('Discount amount')], 'reduction_percent' => ['label' => $this->l('Discount percent')], 'reduction_from' => ['label' => $this->l('Discount from')], 'reduction_to' => ['label' => $this->l('Discount to')], 'reference' => ['label' => $this->l('Reference #')], 'supplier_reference' => ['label' => $this->l('Supplier reference #')], 'supplier' => ['label' => $this->l('Supplier')], 'manufacturer' => ['label' => $this->l('Brand')], 'ean13' => ['label' => $this->l('EAN13')], 'upc' => ['label' => $this->l('UPC')], 'ecotax' => ['label' => $this->l('Ecotax')], 'width' => ['label' => $this->l('Width')], 'height' => ['label' => $this->l('Height')], 'depth' => ['label' => $this->l('Depth')], 'weight' => ['label' => $this->l('Weight')], 'quantity' => ['label' => $this->l('Quantity')], 'minimal_quantity' => ['label' => $this->l('Minimal quantity')], 'visibility' => ['label' => $this->l('Visibility')], 'additional_shipping_cost' => ['label' => $this->l('Additional shipping cost')], 'unity' => ['label' => $this->l('Unit for the price per unit')], 'unit_price' => ['label' => $this->l('Price per unit')], 'description_short' => ['label' => $this->l('Summary')], 'description' => ['label' => $this->l('Description')], 'tags' => ['label' => $this->l('Tags (x,y,z...)')], 'meta_title' => ['label' => $this->l('Meta title')], 'meta_keywords' => ['label' => $this->l('Meta keywords')], 'meta_description' => ['label' => $this->l('Meta description')], 'link_rewrite' => ['label' => $this->l('Rewritten URL')], 'available_now' => ['label' => $this->l('Label when in stock')], 'available_later' => ['label' => $this->l('Label when backorder allowed')], 'available_for_order' => ['label' => $this->l('Available for order (0 = No, 1 = Yes)')], 'available_date' => ['label' => $this->l('Product availability date')], 'date_add' => ['label' => $this->l('Product creation date')], 'show_price' => ['label' => $this->l('Show price (0 = No, 1 = Yes)')], 'image' => ['label' => $this->l('Image URLs (x,y,z...)')], 'image_alt' => ['label' => $this->l('Image alt texts (x,y,z...)')], 'delete_existing_images' => [ 'label' => $this->l('Delete existing images (0 = No, 1 = Yes)'), ], 'features' => ['label' => $this->l('Feature (Name:Value:Position:Public name)')], 'online_only' => ['label' => $this->l('Available online only (0 = No, 1 = Yes)')], 'condition' => ['label' => $this->l('Condition')], 'customizable' => ['label' => $this->l('Customizable (0 = No, 1 = Yes)')], 'uploadable_files' => ['label' => $this->l('Uploadable files (0 = No, 1 = Yes)')], 'text_fields' => ['label' => $this->l('Text fields (0 = No, 1 = Yes)')], 'out_of_stock' => ['label' => $this->l('Action when out of stock')], 'is_virtual' => ['label' => $this->l('Virtual product (0 = No, 1 = Yes)')], 'file_url' => ['label' => $this->l('File URL')], 'nb_downloadable' => [ 'label' => $this->l('Number of allowed downloads'), 'help' => $this->l('Number of days this file can be accessed by customers. Set to zero for unlimited access.'), ], 'date_expiration' => ['label' => $this->l('Expiration date')], 'nb_days_accessible' => [ 'label' => $this->l('Number of days'), 'help' => $this->l('Number of days this file can be accessed by customers. Set to zero for unlimited access.'), ], 'shop' => [ 'label' => $this->l('ID / Name of shop'), 'help' => $this->l('Ignore this field if you don\'t use the Multistore tool. If you leave this field empty, the default shop will be used.'), ], 'advanced_stock_management' => [ 'label' => $this->l('Advanced Stock Management'), 'help' => $this->l('Enable Advanced Stock Management on product (0 = No, 1 = Yes).'), ], 'depends_on_stock' => [ 'label' => $this->l('Depends on stock'), 'help' => $this->l('0 = Use quantity set in product, 1 = Use quantity from warehouse.'), ], 'warehouse' => [ 'label' => $this->l('Warehouse'), 'help' => $this->l('ID of the warehouse to set as storage.'), ], 'accessories' => ['label' => $this->l('Accessories (x,y,z...)')], ]; static::$defaultValues = [ 'id_category' => [(int) Configuration::get('PS_HOME_CATEGORY')], 'id_category_default' => null, 'active' => '1', 'width' => 0.000000, 'height' => 0.000000, 'depth' => 0.000000, 'weight' => 0.000000, 'visibility' => 'both', 'additional_shipping_cost' => 0.00, 'unit_price' => 0, 'quantity' => 0, 'minimal_quantity' => 1, 'price' => 0, 'id_tax_rules_group' => 0, 'description_short' => [(int) Configuration::get('PS_LANG_DEFAULT') => ''], 'link_rewrite' => [(int) Configuration::get('PS_LANG_DEFAULT') => ''], 'online_only' => 0, 'condition' => 'new', 'available_date' => date('Y-m-d'), 'date_add' => date('Y-m-d H:i:s'), 'date_upd' => date('Y-m-d H:i:s'), 'customizable' => 0, 'uploadable_files' => 0, 'text_fields' => 0, 'advanced_stock_management' => 0, 'depends_on_stock' => 0, 'is_virtual' => 0, ]; break; case static::ENTITY_TYPE_CUSTOMERS: //Overwrite required_fields AS only email is required whereas other entities $this->required_fields = ['email', 'passwd', 'lastname', 'firstname']; $this->available_fields = [ 'no' => ['label' => $this->l('Ignore this column')], 'id' => ['label' => $this->l('ID')], 'active' => ['label' => $this->l('Active (0/1)')], 'id_gender' => ['label' => $this->l('Titles ID (Mr = 1, Ms = 2, else 0)')], 'email' => ['label' => $this->l('Email').'*'], 'passwd' => ['label' => $this->l('Password').'*'], 'birthday' => ['label' => $this->l('Birth date')], 'lastname' => ['label' => $this->l('Last name').'*'], 'firstname' => ['label' => $this->l('First name').'*'], 'newsletter' => ['label' => $this->l('Newsletter (0/1)')], 'optin' => ['label' => $this->l('Partner offers (0/1)')], 'date_add' => ['label' => $this->l('Registration date')], 'group' => ['label' => $this->l('Groups (x,y,z...)')], 'id_default_group' => ['label' => $this->l('Default group ID')], 'id_shop' => [ 'label' => $this->l('ID / Name of shop'), 'help' => $this->l('Ignore this field if you don\'t use the Multistore tool. If you leave this field empty, the default shop will be used.'), ], ]; static::$defaultValues = [ 'active' => '1', 'id_shop' => Configuration::get('PS_SHOP_DEFAULT'), ]; break; case static::ENTITY_TYPE_ADDRESSES: //Overwrite required_fields $this->required_fields = [ 'alias', 'lastname', 'firstname', 'address1', 'postcode', 'country', 'customer_email', 'city', ]; $this->available_fields = [ 'no' => ['label' => $this->l('Ignore this column')], 'id' => ['label' => $this->l('ID')], 'alias' => ['label' => $this->l('Alias').'*'], 'active' => ['label' => $this->l('Active (0/1)')], 'customer_email' => ['label' => $this->l('Customer email').'*'], 'id_customer' => ['label' => $this->l('Customer ID')], 'manufacturer' => ['label' => $this->l('Brand')], 'supplier' => ['label' => $this->l('Supplier')], 'company' => ['label' => $this->l('Company')], 'lastname' => ['label' => $this->l('Last name').'*'], 'firstname' => ['label' => $this->l('First name ').'*'], 'address1' => ['label' => $this->l('Address').'*'], 'address2' => ['label' => $this->l('Address (2)')], 'postcode' => ['label' => $this->l('Zip/postal code').'*'], 'city' => ['label' => $this->l('City').'*'], 'country' => ['label' => $this->l('Country').'*'], 'state' => ['label' => $this->l('State')], 'other' => ['label' => $this->l('Other')], 'phone' => ['label' => $this->l('Phone')], 'phone_mobile' => ['label' => $this->l('Mobile Phone')], 'vat_number' => ['label' => $this->l('VAT number')], 'dni' => ['label' => $this->l('Identification number')], ]; static::$defaultValues = [ 'alias' => 'Alias', 'postcode' => 'X', ]; break; case static::ENTITY_TYPE_MANUFACTURERS: case static::ENTITY_TYPE_SUPPLIERS: //Overwrite validators AS name is not MultiLangField static::$validators = [ 'description' => [static::class, 'createMultiLangField'], 'short_description' => [static::class, 'createMultiLangField'], 'meta_title' => [static::class, 'createMultiLangField'], 'meta_keywords' => [static::class, 'createMultiLangField'], 'meta_description' => [static::class, 'createMultiLangField'], ]; $this->available_fields = [ 'no' => ['label' => $this->l('Ignore this column')], 'id' => ['label' => $this->l('ID')], 'active' => ['label' => $this->l('Active (0/1)')], 'name' => ['label' => $this->l('Name')], 'description' => ['label' => $this->l('Description')], 'short_description' => ['label' => $this->l('Short description')], 'meta_title' => ['label' => $this->l('Meta title')], 'meta_keywords' => ['label' => $this->l('Meta keywords')], 'meta_description' => ['label' => $this->l('Meta description')], 'image' => ['label' => $this->l('Image URL')], 'shop' => [ 'label' => $this->l('ID / Name of group shop'), 'help' => $this->l('Ignore this field if you don\'t use the Multistore tool. If you leave this field empty, the default shop will be used.'), ], ]; static::$defaultValues = [ 'shop' => Shop::getGroupFromShop(Configuration::get('PS_SHOP_DEFAULT')), ]; break; case static::ENTITY_TYPE_ALIAS: //Overwrite required_fields $this->required_fields = [ 'alias', 'search', ]; $this->available_fields = [ 'no' => ['label' => $this->l('Ignore this column')], 'id' => ['label' => $this->l('ID')], 'alias' => ['label' => $this->l('Alias').'*'], 'search' => ['label' => $this->l('Search').'*'], 'active' => ['label' => $this->l('Active')], ]; static::$defaultValues = [ 'active' => '1', ]; break; case static::ENTITY_TYPE_STORE_CONTACTS: // Overwrite validators static::$validators = [ 'hours' => [static::class, 'split'], ]; $this->required_fields = [ 'address1', 'city', 'country', 'latitude', 'longitude', ]; $this->available_fields = [ 'no' => ['label' => $this->l('Ignore this column')], 'id' => ['label' => $this->l('ID')], 'active' => ['label' => $this->l('Active (0/1)')], 'name' => ['label' => $this->l('Name')], 'address1' => ['label' => $this->l('Address').'*'], 'address2' => ['label' => $this->l('Address (2)')], 'postcode' => ['label' => $this->l('Zip/postal code')], 'state' => ['label' => $this->l('State')], 'city' => ['label' => $this->l('City').'*'], 'country' => ['label' => $this->l('Country').'*'], 'latitude' => ['label' => $this->l('Latitude').'*'], 'longitude' => ['label' => $this->l('Longitude').'*'], 'phone' => ['label' => $this->l('Phone')], 'fax' => ['label' => $this->l('Fax')], 'email' => ['label' => $this->l('Email address')], 'note' => ['label' => $this->l('Note')], 'hours' => ['label' => $this->l('Hours (x,y,z...)')], 'image' => ['label' => $this->l('Image URL')], 'shop' => [ 'label' => $this->l('ID / Name of shop'), 'help' => $this->l('Ignore this field if you don\'t use the Multistore tool. If you leave this field empty, the default shop will be used.'), ], ]; static::$defaultValues = [ 'active' => '1', ]; break; case static::ENTITY_TYPE_SUPPLY_ORDERS: // required fields $this->required_fields = [ 'id_supplier', 'id_warehouse', 'reference', 'date_delivery_expected', ]; // available fields $this->available_fields = [ 'no' => ['label' => $this->l('Ignore this column')], 'id' => ['label' => $this->l('ID')], 'id_supplier' => ['label' => $this->l('Supplier ID *')], 'id_lang' => ['label' => $this->l('Lang ID')], 'id_warehouse' => ['label' => $this->l('Warehouse ID *')], 'id_currency' => ['label' => $this->l('Currency ID *')], 'reference' => ['label' => $this->l('Supply Order Reference *')], 'date_delivery_expected' => ['label' => $this->l('Delivery Date (Y-M-D)*')], 'discount_rate' => ['label' => $this->l('Discount rate')], 'is_template' => ['label' => $this->l('Template')], ]; // default values static::$defaultValues = [ 'id_lang' => (int) Configuration::get('PS_LANG_DEFAULT'), 'id_currency' => Currency::getDefaultCurrency()->id, 'discount_rate' => '0', 'is_template' => '0', ]; break; case static::ENTITY_TYPE_SUPPLY_ORDER_DETAILS: // required fields $this->required_fields = [ 'supply_order_reference', 'id_product', 'unit_price_te', 'quantity_expected', ]; // available fields $this->available_fields = [ 'no' => ['label' => $this->l('Ignore this column')], 'supply_order_reference' => ['label' => $this->l('Supply Order Reference *')], 'id_product' => ['label' => $this->l('Product ID *')], 'id_product_attribute' => ['label' => $this->l('Product Attribute ID')], 'unit_price_te' => ['label' => $this->l('Unit Price (tax excl.)*')], 'quantity_expected' => ['label' => $this->l('Quantity Expected *')], 'discount_rate' => ['label' => $this->l('Discount Rate')], 'tax_rate' => ['label' => $this->l('Tax Rate')], ]; // default values static::$defaultValues = [ 'discount_rate' => '0', 'tax_rate' => '0', ]; break; } } /** * @param string $field * * @return bool */ protected static function getBoolean($field) { return (bool) $field; } /** * @param string $field * * @return float */ protected static function getPrice($field) { return Tools::parseNumber($field); } /** * @param string $infos * @param string $key * @param ObjectModel $entity * * @return bool * * @throws PrestaShopException */ protected static function fillInfo($infos, $key, $entity) { $infos = trim($infos); if (isset(static::$validators[$key][1]) && static::$validators[$key][1] == 'createMultiLangField' && Tools::getValue('iso_lang')) { $idLang = Language::getIdByIso(Tools::getValue('iso_lang')); $tmp = call_user_func(static::$validators[$key], $infos); foreach ($tmp as $idLangTmp => $value) { if (empty($entity->{$key}[$idLangTmp]) || $idLangTmp == $idLang) { $entity->{$key}[$idLangTmp] = $value; } } } elseif (!empty($infos) || $infos == '0') { // ($infos == '0') => if you want to disable a product by using "0" in active because empty('0') return true $entity->{$key} = isset(static::$validators[$key]) ? call_user_func(static::$validators[$key], $infos) : $infos; } return true; } /** * @param string $a * @param string $b * * @return int */ protected static function usortFiles($a, $b) { if ($a == $b) { return 0; } return ($b < $a) ? 1 : -1; } /** * @return void * * @throws PrestaShopException */ public function setMedia() { $backOfficeTheme = ((Validate::isLoadedObject($this->context->employee) && $this->context->employee->bo_theme) ? $this->context->employee->bo_theme : 'default'); if (!file_exists(_PS_BO_ALL_THEMES_DIR_.$backOfficeTheme.DIRECTORY_SEPARATOR.'template')) { $backOfficeTheme = 'default'; } // We need to set parent media first, so that jQuery is loaded before the dependant plugins parent::setMedia(); $this->addJs(__PS_BASE_URI__.$this->admin_webpath.'/themes/'.$backOfficeTheme.'/js/jquery.iframe-transport.js'); $this->addJs(__PS_BASE_URI__.$this->admin_webpath.'/themes/'.$backOfficeTheme.'/js/jquery.fileupload.js'); $this->addJs(__PS_BASE_URI__.$this->admin_webpath.'/themes/'.$backOfficeTheme.'/js/jquery.fileupload-process.js'); $this->addJs(__PS_BASE_URI__.$this->admin_webpath.'/themes/'.$backOfficeTheme.'/js/jquery.fileupload-validate.js'); $this->addJs(__PS_BASE_URI__.'js/vendor/spin.js'); $this->addJs(__PS_BASE_URI__.'js/vendor/ladda.js'); } /** * @return void * * @throws PrestaShopException * @noinspection PhpUnused */ public function ajaxProcessuploadCsv() { $filenamePrefix = date('YmdHis').'-'; $filename = preg_replace('/[^A-Za-z0-9._\-]/', '', $_FILES['file']['name']); $extensions = array_keys($this->getFileExtensions()); $extensionsRegexp = implode('|', array_map('preg_quote', $extensions)); if (isset($_FILES['file']) && !empty($_FILES['file']['error'])) { $_FILES['file']['error'] = Tools::decodeUploadError($_FILES['file']['error']); } elseif (!preg_match('#([^.]*?)\.('.$extensionsRegexp.')$#is', $filename)) { $_FILES['file']['error'] = $this->l('Unsupported file type. Supported extensions: ') . implode(', ' , $extensions); } elseif (!@filemtime($_FILES['file']['tmp_name']) || !@move_uploaded_file($_FILES['file']['tmp_name'], static::getPath().$filenamePrefix.str_replace("\0", '', $filename)) ) { $_FILES['file']['error'] = $this->l('An error occurred while uploading / copying the file.'); } else { @chmod(static::getPath().$filenamePrefix.$filename, 0664); $_FILES['file']['filename'] = $filenamePrefix.str_replace('\0', '', $filename); } $this->ajaxDie(json_encode($_FILES)); } /** * @param string $file * * @return string */ public static function getPath($file = '') { return _PS_ADMIN_DIR_.DIRECTORY_SEPARATOR.'import'.DIRECTORY_SEPARATOR.$file; } /** * @return void * * @throws PrestaShopException */ public function init() { parent::init(); if (Tools::isSubmit('submitImportFile')) { $this->display = 'import'; } } /** * @return void * * @throws PrestaShopException * @throws SmartyException */ public function initContent() { $this->initToolbar(); $this->initPageHeaderToolbar(); if ($this->display == 'import') { if (Tools::getValue('filename')) { $this->content .= $this->renderView(); } else { $this->errors[] = $this->l('To proceed, please upload a file first.'); $this->content .= $this->renderForm(); } } else { $this->content .= $this->renderForm(); } $this->context->smarty->assign( [ 'content' => $this->content, 'url_post' => static::$currentIndex.'&token='.$this->token, 'show_page_header_toolbar' => $this->show_page_header_toolbar, 'page_header_toolbar_title' => $this->page_header_toolbar_title, 'page_header_toolbar_btn' => $this->page_header_toolbar_btn, ] ); } /** * @return void */ public function initToolbar() { if ($this->display === 'import') { // Default cancel button - like old back link $back = Tools::safeOutput(Tools::getValue('back', '')); if (empty($back)) { $back = static::$currentIndex . '&token=' . $this->token; } $this->toolbar_btn['cancel'] = [ 'href' => $back, 'desc' => $this->l('Cancel'), ]; // Default save button - action dynamically handled in javascript $this->toolbar_btn['save-import'] = [ 'href' => '#', 'desc' => $this->l('Import .CSV data'), ]; } } /** * @return string * * @throws PrestaShopDatabaseException * @throws PrestaShopException * @throws SmartyException */ public function renderView() { $this->addJS(_PS_JS_DIR_.'admin/import.js'); $datasource = $this->openDataSource(); $nbColumn = $datasource->getNumberOfColumns(); $nbTable = ceil($nbColumn / static::MAX_COLUMNS); $res = []; foreach ($this->required_fields as $elem) { $res[] = '\''.$elem.'\''; } $previewRows = []; for ($i = 0; $i<10; $i++) { $previewRow = $datasource->getRow(); if ($previewRow) { $previewRows[] = $previewRow; } } $data = []; for ($i = 0; $i < $nbTable; $i++) { $data[$i] = $this->generateContentTable($i, $nbColumn, $previewRows); } $entityType = $this->getSelectedEntity(); $this->context->cookie->isoLangSelected = urlencode(Tools::getValue('iso_lang')); $this->context->cookie->separatorSelected = urlencode($this->separator); $this->context->cookie->multipleValueSeparatorSelected = urlencode($this->multiple_value_separator); $this->context->cookie->fileSelected = urlencode(Tools::getValue('filename')); // Show date format select only in Products,Combinations and Customer import $dateFormats = null; if (in_array(Tools::getIntValue('entity'), [1, 2, 3])) { if ( ! empty($this->context->language) && ! empty($this->context->language->date_format_lite) ) { $dateFormats[$this->context->language->date_format_lite] = [ 'label' => $this->context->language->date_format_lite .' - ' .$this->l('from back office language'), ]; } $dateFormats['Y-m-d'] = ['label' => 'Y-m-d']; $dateFormats['Y-d-m'] = ['label' => 'Y-d-m']; $dateFormats['d-m-Y'] = ['label' => 'd-m-Y']; $dateFormats['d.m.Y'] = ['label' => 'd.m.Y']; } $this->tpl_view_vars = [ 'import_matchs' => Db::readOnly()->getArray((new DbQuery())->select('*')->from('import_match')), 'fields_value' => [ 'filename' => Tools::getValue('filename'), 'importer' => Tools::getValue('importer'), 'entity' => $entityType, 'iso_lang' => Tools::getValue('iso_lang'), 'truncate' => Tools::getValue('truncate'), 'forceIDs' => Tools::getValue('forceIDs'), 'regenerate' => Tools::getValue('regenerate'), 'forceCat' => Tools::getValue('forceCat'), 'match_ref' => Tools::getValue('match_ref'), 'separator' => $this->separator, 'multiple_value_separator' => $this->multiple_value_separator, ], 'nb_table' => $nbTable, 'nb_column' => $nbColumn, 'res' => implode(',', $res), 'max_columns' => static::MAX_COLUMNS, 'no_pre_select' => ['price_tin', 'feature'], 'available_fields' => $this->available_fields, 'data' => $data, 'date_formats' => $dateFormats, ]; return parent::renderView(); } /** * @param int $offset * @return DataSourceInterface * * @throws PrestaShopException */ protected function openDataSource($offset = 0) { // construct data source $filepath = static::getPath(Tools::getValue('filename')); $importer = Tools::getValue('importer'); $implementations = $this->getRegisteredDataSources(); if (! isset($implementations[$importer])) { throw new PrestaShopException('Import implementation "'.$importer.'" not found'); } $implementation = $implementations[$importer]; $constructor = $implementation['constructor']; /** @var DataSourceInterface $dataSource */ $dataSource = $constructor($filepath, [ 'separator' => $this->separator, 'multipleValueSeparator' => $this->multiple_value_separator, ]); if (! $dataSource instanceof DataSourceInterface) { throw new PrestaShopException('Failed to create datasource using implementation "'.$implementation['name'].'"'); } // seek current row $toSkip = Tools::getIntValue('skip'); if ($offset && $offset > 0) { $toSkip += $offset; } for ($i = 0; $i < $toSkip; ++$i) { $dataSource->getRow(); } return $dataSource; } /** * @param string $currentTable * @param int $nbColumn * @param array $previewRows * * @return string */ protected function generateContentTable($currentTable, $nbColumn, array $previewRows) { $html = ''; // Header for ($i = 0; $i < $nbColumn; $i++) { if (static::MAX_COLUMNS * (int) $currentTable <= $i && $i < static::MAX_COLUMNS * ((int) $currentTable + 1)) { $html .= ''; } } $html .= ''; static::setLocale(); foreach ($previewRows as $currentLine => $line) { $html .= ''; foreach ($line as $nbC => $column) { $column = $column ?? ''; if ((static::MAX_COLUMNS * (int) $currentTable <= $nbC) && ((int) $nbC < static::MAX_COLUMNS * ((int) $currentTable + 1))) { $html .= ''; } } $html .= ''; } $html .= ''; return $html; } /** * @param int $nbC * * @return string */ protected function getTypeValuesOptions($nbC) { $i = 0; $noPreSelect = ['price_tin', 'feature']; $options = ''; foreach ($this->available_fields as $key => $field) { $options .= '