* @copyright Since 2007 PrestaShop SA and Contributors * @license https://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0) */ use PhpOffice\PhpSpreadsheet\IOFactory; use PhpOffice\PhpSpreadsheet\Writer\Csv; @ini_set('max_execution_time', '0'); /* No max line limit since the lines can be more than 4096. Performance impact is not significant. */ define('MAX_LINE_SIZE', 0); /* Used for validatefields diying without user friendly error or not */ define('UNFRIENDLY_ERROR', false); /* this value set the number of columns visible on each page */ define('MAX_COLUMNS', 6); /* correct Mac error on eof */ @ini_set('auto_detect_line_endings', '1'); class AdminImportControllerCore extends AdminController { public static $column_mask; public $entities = []; public $available_fields = []; /** @var array|string[] */ public $required_fields = []; public static $default_values = []; public static $validators = [ 'active' => ['AdminImportController', 'getBoolean'], 'tax_rate' => ['AdminImportController', 'getPrice'], /* Tax excluded */ 'price_tex' => ['AdminImportController', 'getPrice'], /* Tax included */ 'price_tin' => ['AdminImportController', 'getPrice'], 'reduction_price' => ['AdminImportController', 'getPrice'], 'reduction_percent' => ['AdminImportController', 'getPrice'], 'wholesale_price' => ['AdminImportController', 'getPrice'], 'ecotax' => ['AdminImportController', 'getPrice'], 'name' => ['AdminImportController', 'createMultiLangField'], 'description' => ['AdminImportController', 'createMultiLangField'], 'description_short' => ['AdminImportController', 'createMultiLangField'], 'meta_title' => ['AdminImportController', 'createMultiLangField'], 'meta_keywords' => ['AdminImportController', 'createMultiLangField'], 'meta_description' => ['AdminImportController', 'createMultiLangField'], 'link_rewrite' => ['AdminImportController', 'createMultiLangField'], 'available_now' => ['AdminImportController', 'createMultiLangField'], 'available_later' => ['AdminImportController', 'createMultiLangField'], 'category' => ['AdminImportController', 'split'], 'online_only' => ['AdminImportController', 'getBoolean'], 'accessories' => ['AdminImportController', 'split'], 'image_alt' => ['AdminImportController', 'split'], 'delivery_in_stock' => ['AdminImportController', 'createMultiLangField'], 'delivery_out_stock' => ['AdminImportController', 'createMultiLangField'], ]; public $separator; public $convert; public $multiple_value_separator; /** * This flag shows if import was executed in current request. * Used for symfony migration purposes. * * @var bool */ private $importExecuted = false; public function __construct() { $this->bootstrap = true; parent::__construct(); $this->entities = [ $this->trans('Categories', [], 'Admin.Global'), $this->trans('Products', [], 'Admin.Global'), $this->trans('Combinations', [], 'Admin.Global'), $this->trans('Customers', [], 'Admin.Global'), $this->trans('Addresses', [], 'Admin.Global'), $this->trans('Brands', [], 'Admin.Global'), $this->trans('Suppliers', [], 'Admin.Global'), $this->trans('Alias', [], 'Admin.Shopparameters.Feature'), $this->trans('Store contacts', [], 'Admin.Advparameters.Feature'), ]; // @since 1.5.0 if (Configuration::get('PS_ADVANCED_STOCK_MANAGEMENT')) { $this->entities = array_merge( $this->entities, [ $this->trans('Supply Orders', [], 'Admin.Advparameters.Feature'), $this->trans('Supply Order Details', [], 'Admin.Advparameters.Feature'), ] ); } $this->entities = array_flip($this->entities); switch ((int) Tools::getValue('entity')) { case $this->entities[$this->trans('Combinations', [], 'Admin.Global')]: $this->required_fields = [ 'group', 'attribute', ]; $this->available_fields = [ 'no' => ['label' => $this->trans('Ignore this column', [], 'Admin.Advparameters.Feature')], 'id_product' => ['label' => $this->trans('Product ID', [], 'Admin.Advparameters.Feature')], 'product_reference' => ['label' => $this->trans('Product Reference', [], 'Admin.Advparameters.Feature')], 'group' => [ 'label' => $this->trans('Attribute (Name:Type:Position)', [], 'Admin.Advparameters.Feature') . '*', ], 'attribute' => [ 'label' => $this->trans('Value (Value:Position)', [], 'Admin.Advparameters.Feature') . '*', ], 'supplier_reference' => ['label' => $this->trans('Supplier reference', [], 'Admin.Advparameters.Feature')], 'reference' => ['label' => $this->trans('Reference', [], 'Admin.Global')], 'ean13' => ['label' => $this->trans('EAN-13', [], 'Admin.Advparameters.Feature')], 'upc' => ['label' => $this->trans('UPC', [], 'Admin.Advparameters.Feature')], 'mpn' => ['label' => $this->trans('MPN', [], 'Admin.Catalog.Feature')], 'wholesale_price' => ['label' => $this->trans('Cost price', [], 'Admin.Catalog.Feature')], 'price' => ['label' => $this->trans('Impact on price', [], 'Admin.Catalog.Feature')], 'ecotax' => ['label' => $this->trans('Ecotax', [], 'Admin.Catalog.Feature')], 'quantity' => ['label' => $this->trans('Quantity', [], 'Admin.Global')], 'minimal_quantity' => ['label' => $this->trans('Minimal quantity', [], 'Admin.Advparameters.Feature')], 'low_stock_threshold' => ['label' => $this->trans('Low stock level', [], 'Admin.Catalog.Feature')], 'low_stock_alert' => ['label' => $this->trans('Receive a low stock alert by email', [], 'Admin.Catalog.Feature')], 'weight' => ['label' => $this->trans('Impact on weight', [], 'Admin.Catalog.Feature')], 'default_on' => ['label' => $this->trans('Default (0 = No, 1 = Yes)', [], 'Admin.Advparameters.Feature')], 'available_date' => ['label' => $this->trans('Combination availability date', [], 'Admin.Advparameters.Feature')], 'image_position' => [ 'label' => $this->trans('Choose among product images by position (1,2,3...)', [], 'Admin.Advparameters.Feature'), ], 'image_url' => ['label' => $this->trans('Image URLs (x,y,z...)', [], 'Admin.Advparameters.Feature')], 'image_alt' => ['label' => $this->trans('Image alt texts (x,y,z...)', [], 'Admin.Advparameters.Feature')], 'shop' => [ 'label' => $this->trans('ID / Name of the store', [], 'Admin.Advparameters.Feature'), 'help' => $this->trans('Ignore this field if you don\'t use the Multistore tool. If you leave this field empty, the default store will be used.', [], 'Admin.Advparameters.Help'), ], 'advanced_stock_management' => [ 'label' => $this->trans('Advanced Stock Management', [], 'Admin.Advparameters.Feature'), 'help' => $this->trans('Enable advanced stock management on product (0 = No, 1 = Yes)', [], 'Admin.Advparameters.Help'), ], 'depends_on_stock' => [ 'label' => $this->trans('Depends on stock', [], 'Admin.Advparameters.Feature'), 'help' => $this->trans('0 = Use quantity set in product, 1 = Use quantity from warehouse.', [], 'Admin.Advparameters.Help'), ], 'warehouse' => [ 'label' => $this->trans('Warehouse', [], 'Admin.Advparameters.Feature'), 'help' => $this->trans('ID of the warehouse to set as storage.', [], 'Admin.Advparameters.Help'), ], ]; self::$default_values = [ 'reference' => '', 'supplier_reference' => '', 'ean13' => '', 'upc' => '', 'mpn' => '', 'wholesale_price' => 0, 'price' => 0, 'ecotax' => 0, 'quantity' => 0, 'minimal_quantity' => 1, 'low_stock_threshold' => null, 'low_stock_alert' => false, 'weight' => 0, 'default_on' => null, 'advanced_stock_management' => 0, 'depends_on_stock' => 0, 'available_date' => date('Y-m-d'), ]; break; case $this->entities[$this->trans('Categories', [], 'Admin.Global')]: $this->available_fields = [ 'no' => ['label' => $this->trans('Ignore this column', [], 'Admin.Advparameters.Feature')], 'id' => ['label' => $this->trans('ID', [], 'Admin.Global')], 'active' => ['label' => $this->trans('Active (0/1)', [], 'Admin.Advparameters.Feature')], 'name' => ['label' => $this->trans('Name', [], 'Admin.Global')], 'parent' => ['label' => $this->trans('Parent category', [], 'Admin.Catalog.Feature')], 'is_root_category' => [ 'label' => $this->trans('Root category (0/1)', [], 'Admin.Advparameters.Feature'), 'help' => $this->trans('A category root is where a category tree can begin. This is used with multistore.', [], 'Admin.Advparameters.Help'), ], 'description' => ['label' => $this->trans('Description', [], 'Admin.Global')], 'meta_title' => ['label' => $this->trans('Meta title', [], 'Admin.Global')], 'meta_keywords' => ['label' => $this->trans('Meta keywords', [], 'Admin.Global')], 'meta_description' => ['label' => $this->trans('Meta description', [], 'Admin.Global')], 'link_rewrite' => ['label' => $this->trans('Rewritten URL', [], 'Admin.Shopparameters.Feature')], 'image' => ['label' => $this->trans('Image URL', [], 'Admin.Advparameters.Feature')], 'shop' => [ 'label' => $this->trans('ID / Name of the store', [], 'Admin.Advparameters.Feature'), 'help' => $this->trans('Ignore this field if you don\'t use the Multistore tool. If you leave this field empty, the default store will be used.', [], 'Admin.Advparameters.Help'), ], ]; self::$default_values = [ 'active' => '1', 'parent' => Configuration::get('PS_HOME_CATEGORY'), 'link_rewrite' => '', ]; break; case $this->entities[$this->trans('Products', [], 'Admin.Global')]: self::$validators['image'] = [ 'AdminImportController', 'split', ]; $this->available_fields = [ 'no' => ['label' => $this->trans('Ignore this column', [], 'Admin.Advparameters.Feature')], 'id' => ['label' => $this->trans('ID', [], 'Admin.Global')], 'active' => ['label' => $this->trans('Active (0/1)', [], 'Admin.Advparameters.Feature')], 'name' => ['label' => $this->trans('Name', [], 'Admin.Global')], 'category' => ['label' => $this->trans('Categories (x,y,z...)', [], 'Admin.Advparameters.Feature')], 'price_tex' => ['label' => $this->trans('Price tax excluded', [], 'Admin.Advparameters.Feature')], 'price_tin' => ['label' => $this->trans('Price tax included', [], 'Admin.Advparameters.Feature')], 'id_tax_rules_group' => ['label' => $this->trans('Tax rule ID', [], 'Admin.Advparameters.Feature')], 'wholesale_price' => ['label' => $this->trans('Cost price', [], 'Admin.Catalog.Feature')], 'on_sale' => ['label' => $this->trans('On sale (0/1)', [], 'Admin.Advparameters.Feature')], 'reduction_price' => ['label' => $this->trans('Discount amount', [], 'Admin.Advparameters.Feature')], 'reduction_percent' => ['label' => $this->trans('Discount percent', [], 'Admin.Advparameters.Feature')], 'reduction_from' => ['label' => $this->trans('Discount from (yyyy-mm-dd)', [], 'Admin.Advparameters.Feature')], 'reduction_to' => ['label' => $this->trans('Discount to (yyyy-mm-dd)', [], 'Admin.Advparameters.Feature')], 'reference' => ['label' => $this->trans('Reference #', [], 'Admin.Advparameters.Feature')], 'supplier_reference' => ['label' => $this->trans('Supplier reference #', [], 'Admin.Advparameters.Feature')], 'supplier' => ['label' => $this->trans('Supplier', [], 'Admin.Global')], 'manufacturer' => ['label' => $this->trans('Brand', [], 'Admin.Global')], 'ean13' => ['label' => $this->trans('EAN-13', [], 'Admin.Advparameters.Feature')], 'upc' => ['label' => $this->trans('UPC', [], 'Admin.Advparameters.Feature')], 'mpn' => ['label' => $this->trans('MPN', [], 'Admin.Catalog.Feature')], 'ecotax' => ['label' => $this->trans('Ecotax', [], 'Admin.Catalog.Feature')], 'width' => ['label' => $this->trans('Width', [], 'Admin.Global')], 'height' => ['label' => $this->trans('Height', [], 'Admin.Global')], 'depth' => ['label' => $this->trans('Depth', [], 'Admin.Global')], 'weight' => ['label' => $this->trans('Weight', [], 'Admin.Global')], 'delivery_in_stock' => [ 'label' => $this->trans( 'Delivery time of in-stock products:', [], 'Admin.Catalog.Feature' ), ], 'delivery_out_stock' => [ 'label' => $this->trans( 'Delivery time of out-of-stock products with allowed orders:', [], 'Admin.Advparameters.Feature' ), ], 'quantity' => ['label' => $this->trans('Quantity', [], 'Admin.Global')], 'minimal_quantity' => ['label' => $this->trans('Minimal quantity', [], 'Admin.Advparameters.Feature')], 'low_stock_threshold' => ['label' => $this->trans('Low stock level', [], 'Admin.Catalog.Feature')], 'low_stock_alert' => ['label' => $this->trans('Receive a low stock alert by email', [], 'Admin.Catalog.Feature')], 'visibility' => ['label' => $this->trans('Visibility', [], 'Admin.Catalog.Feature')], 'additional_shipping_cost' => ['label' => $this->trans('Additional shipping cost', [], 'Admin.Advparameters.Feature')], 'unity' => ['label' => $this->trans('Unit for the price per unit', [], 'Admin.Advparameters.Feature')], 'unit_price' => ['label' => $this->trans('Price per unit', [], 'Admin.Advparameters.Feature')], 'description_short' => ['label' => $this->trans('Summary', [], 'Admin.Catalog.Feature')], 'description' => ['label' => $this->trans('Description', [], 'Admin.Global')], 'tags' => ['label' => $this->trans('Tags (x,y,z...)', [], 'Admin.Advparameters.Feature')], 'meta_title' => ['label' => $this->trans('Meta title', [], 'Admin.Global')], 'meta_keywords' => ['label' => $this->trans('Meta keywords', [], 'Admin.Global')], 'meta_description' => ['label' => $this->trans('Meta description', [], 'Admin.Global')], 'link_rewrite' => ['label' => $this->trans('Rewritten URL', [], 'Admin.Advparameters.Feature')], 'available_now' => ['label' => $this->trans('Label when in stock', [], 'Admin.Catalog.Feature')], 'available_later' => ['label' => $this->trans('Label when backorder allowed', [], 'Admin.Advparameters.Feature')], 'available_for_order' => ['label' => $this->trans('Available for order (0 = No, 1 = Yes)', [], 'Admin.Advparameters.Feature')], 'available_date' => ['label' => $this->trans('Product availability date', [], 'Admin.Advparameters.Feature')], 'date_add' => ['label' => $this->trans('Product creation date', [], 'Admin.Advparameters.Feature')], 'show_price' => ['label' => $this->trans('Show price (0 = No, 1 = Yes)', [], 'Admin.Advparameters.Feature')], 'image' => ['label' => $this->trans('Image URLs (x,y,z...)', [], 'Admin.Advparameters.Feature')], 'image_alt' => ['label' => $this->trans('Image alt texts (x,y,z...)', [], 'Admin.Advparameters.Feature')], 'delete_existing_images' => [ 'label' => $this->trans('Delete existing images (0 = No, 1 = Yes)', [], 'Admin.Advparameters.Feature'), ], 'features' => ['label' => $this->trans('Feature (Name:Value:Position:Customized)', [], 'Admin.Advparameters.Feature')], 'online_only' => ['label' => $this->trans('Available online only (0 = No, 1 = Yes)', [], 'Admin.Advparameters.Feature')], 'condition' => ['label' => $this->trans('Condition', [], 'Admin.Catalog.Feature')], 'customizable' => ['label' => $this->trans('Customizable (0 = No, 1 = Yes)', [], 'Admin.Advparameters.Feature')], 'uploadable_files' => ['label' => $this->trans('Uploadable files (0 = No, 1 = Yes)', [], 'Admin.Advparameters.Feature')], 'text_fields' => ['label' => $this->trans('Text fields (0 = No, 1 = Yes)', [], 'Admin.Advparameters.Feature')], 'out_of_stock' => ['label' => $this->trans('Action when out of stock', [], 'Admin.Advparameters.Feature')], 'is_virtual' => ['label' => $this->trans('Virtual product (0 = No, 1 = Yes)', [], 'Admin.Advparameters.Feature')], 'file_url' => ['label' => $this->trans('File URL', [], 'Admin.Advparameters.Feature')], 'nb_downloadable' => [ 'label' => $this->trans('Number of allowed downloads', [], 'Admin.Catalog.Feature'), 'help' => $this->trans('Number of days this file can be accessed by customers. Set to zero for unlimited access.', [], 'Admin.Catalog.Help'), ], 'date_expiration' => ['label' => $this->trans('Expiration date (yyyy-mm-dd)', [], 'Admin.Advparameters.Feature')], 'nb_days_accessible' => [ 'label' => $this->trans('Number of days', [], 'Admin.Advparameters.Feature'), 'help' => $this->trans('Number of days this file can be accessed by customers. Set to zero for unlimited access.', [], 'Admin.Catalog.Help'), ], 'shop' => [ 'label' => $this->trans('ID / Name of the store', [], 'Admin.Advparameters.Feature'), 'help' => $this->trans('Ignore this field if you don\'t use the Multistore tool. If you leave this field empty, the default store will be used.', [], 'Admin.Advparameters.Help'), ], 'advanced_stock_management' => [ 'label' => $this->trans('Advanced Stock Management', [], 'Admin.Advparameters.Feature'), 'help' => $this->trans('Enable advanced stock management on product (0 = No, 1 = Yes).', [], 'Admin.Advparameters.Help'), ], 'depends_on_stock' => [ 'label' => $this->trans('Depends on stock', [], 'Admin.Advparameters.Feature'), 'help' => $this->trans('0 = Use quantity set in product, 1 = Use quantity from warehouse.', [], 'Admin.Advparameters.Help'), ], 'warehouse' => [ 'label' => $this->trans('Warehouse', [], 'Admin.Advparameters.Feature'), 'help' => $this->trans('ID of the warehouse to set as storage.', [], 'Admin.Advparameters.Help'), ], 'accessories' => ['label' => $this->trans('Accessories (x,y,z...)', [], 'Admin.Advparameters.Feature')], ]; self::$default_values = [ '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, 'low_stock_threshold' => null, 'low_stock_alert' => false, '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 $this->entities[$this->trans('Customers', [], 'Admin.Global')]: //Overwrite required_fields AS only email is required whereas other entities $this->required_fields = ['email', 'passwd', 'lastname', 'firstname']; $this->available_fields = [ 'no' => ['label' => $this->trans('Ignore this column', [], 'Admin.Advparameters.Feature')], 'id' => ['label' => $this->trans('ID', [], 'Admin.Global')], 'active' => ['label' => $this->trans('Active (0/1)', [], 'Admin.Advparameters.Feature')], 'id_gender' => ['label' => $this->trans('Titles ID (Mr = 1, Ms = 2, else 0)', [], 'Admin.Advparameters.Feature')], 'email' => ['label' => $this->trans('Email', [], 'Admin.Global') . '*'], 'passwd' => ['label' => $this->trans('Password', [], 'Admin.Global') . '*'], 'birthday' => ['label' => $this->trans('Birth date (yyyy-mm-dd)', [], 'Admin.Advparameters.Feature')], 'lastname' => ['label' => $this->trans('Last name', [], 'Admin.Global') . '*'], 'firstname' => ['label' => $this->trans('First name', [], 'Admin.Global') . '*'], 'newsletter' => ['label' => $this->trans('Newsletter (0/1)', [], 'Admin.Advparameters.Feature')], 'optin' => ['label' => $this->trans('Partner offers (0/1)', [], 'Admin.Advparameters.Feature')], 'date_add' => ['label' => $this->trans('Registration date (yyyy-mm-dd)', [], 'Admin.Advparameters.Feature')], 'group' => ['label' => $this->trans('Groups (x,y,z...)', [], 'Admin.Advparameters.Feature')], 'id_default_group' => ['label' => $this->trans('Default group ID', [], 'Admin.Advparameters.Feature')], 'id_shop' => [ 'label' => $this->trans('ID / Name of the store', [], 'Admin.Advparameters.Feature'), 'help' => $this->trans('Ignore this field if you don\'t use the Multistore tool. If you leave this field empty, the default store will be used.', [], 'Admin.Advparameters.Help'), ], ]; self::$default_values = [ 'active' => '1', 'date_upd' => date('Y-m-d H:i:s'), 'id_shop' => Configuration::get('PS_SHOP_DEFAULT'), ]; break; case $this->entities[$this->trans('Addresses', [], 'Admin.Global')]: //Overwrite required_fields $this->required_fields = [ 'alias', 'lastname', 'firstname', 'address1', 'postcode', 'country', 'customer_email', 'city', ]; $this->available_fields = [ 'no' => ['label' => $this->trans('Ignore this column', [], 'Admin.Advparameters.Feature')], 'id' => ['label' => $this->trans('ID', [], 'Admin.Global')], 'alias' => ['label' => $this->trans('Alias', [], 'Admin.Shopparameters.Feature') . '*'], 'active' => ['label' => $this->trans('Active (0/1)', [], 'Admin.Advparameters.Feature')], 'customer_email' => ['label' => $this->trans('Customer email', [], 'Admin.Advparameters.Feature') . '*'], 'id_customer' => ['label' => $this->trans('Customer ID', [], 'Admin.Advparameters.Feature')], 'manufacturer' => ['label' => $this->trans('Brand', [], 'Admin.Global')], 'supplier' => ['label' => $this->trans('Supplier', [], 'Admin.Global')], 'company' => ['label' => $this->trans('Company', [], 'Admin.Global')], 'lastname' => ['label' => $this->trans('Last name', [], 'Admin.Global') . '*'], 'firstname' => ['label' => $this->trans('First name', [], 'Admin.Global') . '*'], 'address1' => ['label' => $this->trans('Address', [], 'Admin.Global') . '*'], 'address2' => ['label' => $this->trans('Address (2)', [], 'Admin.Global')], 'postcode' => ['label' => $this->trans('Zip/Postal code', [], 'Admin.Global') . '*'], 'city' => ['label' => $this->trans('City', [], 'Admin.Global') . '*'], 'country' => ['label' => $this->trans('Country', [], 'Admin.Global') . '*'], 'state' => ['label' => $this->trans('State', [], 'Admin.Global')], 'other' => ['label' => $this->trans('Other', [], 'Admin.Global')], 'phone' => ['label' => $this->trans('Phone', [], 'Admin.Global')], 'phone_mobile' => ['label' => $this->trans('Mobile phone', [], 'Admin.Global')], 'vat_number' => ['label' => $this->trans('VAT number', [], 'Admin.Orderscustomers.Feature')], 'dni' => ['label' => $this->trans('Identification number', [], 'Admin.Orderscustomers.Feature')], ]; self::$default_values = [ 'alias' => 'Alias', 'postcode' => 'X', ]; break; case $this->entities[$this->trans('Brands', [], 'Admin.Global')]: case $this->entities[$this->trans('Suppliers', [], 'Admin.Global')]: //Overwrite validators AS name is not MultiLangField self::$validators = [ 'description' => ['AdminImportController', 'createMultiLangField'], 'short_description' => ['AdminImportController', 'createMultiLangField'], 'meta_title' => ['AdminImportController', 'createMultiLangField'], 'meta_keywords' => ['AdminImportController', 'createMultiLangField'], 'meta_description' => ['AdminImportController', 'createMultiLangField'], ]; $this->available_fields = [ 'no' => ['label' => $this->trans('Ignore this column', [], 'Admin.Advparameters.Feature')], 'id' => ['label' => $this->trans('ID', [], 'Admin.Global')], 'active' => ['label' => $this->trans('Active (0/1)', [], 'Admin.Advparameters.Feature')], 'name' => ['label' => $this->trans('Name', [], 'Admin.Global')], 'description' => ['label' => $this->trans('Description', [], 'Admin.Global')], 'short_description' => ['label' => $this->trans('Short description', [], 'Admin.Catalog.Feature')], 'meta_title' => ['label' => $this->trans('Meta title', [], 'Admin.Global')], 'meta_keywords' => ['label' => $this->trans('Meta keywords', [], 'Admin.Global')], 'meta_description' => ['label' => $this->trans('Meta description', [], 'Admin.Global')], 'image' => ['label' => $this->trans('Image URL', [], 'Admin.Advparameters.Feature')], 'shop' => [ 'label' => $this->trans('ID / Name of group shop', [], 'Admin.Advparameters.Feature'), 'help' => $this->trans('Ignore this field if you don\'t use the Multistore tool. If you leave this field empty, the default store will be used.', [], 'Admin.Advparameters.Help'), ], ]; if ($this->entities[$this->trans('Suppliers', [], 'Admin.Global')]) { unset($this->available_fields['short_description'], self::$validators['short_description']); } self::$default_values = [ 'shop' => Shop::getGroupFromShop((int) Configuration::get('PS_SHOP_DEFAULT')), ]; break; case $this->entities[$this->trans('Alias', [], 'Admin.Shopparameters.Feature')]: //Overwrite required_fields $this->required_fields = [ 'alias', 'search', ]; $this->available_fields = [ 'no' => ['label' => $this->trans('Ignore this column', [], 'Admin.Advparameters.Feature')], 'id' => ['label' => $this->trans('ID', [], 'Admin.Global')], 'alias' => ['label' => $this->trans('Alias', [], 'Admin.Shopparameters.Feature') . '*'], 'search' => ['label' => $this->trans('Search', [], 'Admin.Shopparameters.Feature') . '*'], 'active' => ['label' => $this->trans('Active', [], 'Admin.Global')], ]; self::$default_values = [ 'active' => '1', ]; break; case $this->entities[$this->trans('Store contacts', [], 'Admin.Advparameters.Feature')]: self::$validators['hours'] = ['AdminImportController', 'split']; self::$validators['address1'] = ['AdminImportController', 'createMultiLangField']; self::$validators['address2'] = ['AdminImportController', 'createMultiLangField']; $this->required_fields = [ 'address1', 'city', 'country', 'latitude', 'longitude', ]; $this->available_fields = [ 'no' => ['label' => $this->trans('Ignore this column', [], 'Admin.Advparameters.Feature')], 'id' => ['label' => $this->trans('ID', [], 'Admin.Global')], 'active' => ['label' => $this->trans('Active (0/1)', [], 'Admin.Advparameters.Feature')], 'name' => ['label' => $this->trans('Name', [], 'Admin.Global')], 'address1' => ['label' => $this->trans('Address', [], 'Admin.Global') . '*'], 'address2' => ['label' => $this->trans('Address (2)', [], 'Admin.Advparameters.Feature')], 'postcode' => ['label' => $this->trans('Zip/Postal code', [], 'Admin.Global')], 'state' => ['label' => $this->trans('State', [], 'Admin.Global')], 'city' => ['label' => $this->trans('City', [], 'Admin.Global') . '*'], 'country' => ['label' => $this->trans('Country', [], 'Admin.Global') . '*'], 'latitude' => ['label' => $this->trans('Latitude', [], 'Admin.Advparameters.Feature') . '*'], 'longitude' => ['label' => $this->trans('Longitude', [], 'Admin.Advparameters.Feature') . '*'], 'phone' => ['label' => $this->trans('Phone', [], 'Admin.Global')], 'fax' => ['label' => $this->trans('Fax', [], 'Admin.Global')], 'email' => ['label' => $this->trans('Email address', [], 'Admin.Global')], 'note' => ['label' => $this->trans('Note', [], 'Admin.Advparameters.Feature')], 'hours' => ['label' => $this->trans('Hours (x,y,z...)', [], 'Admin.Advparameters.Feature')], 'image' => ['label' => $this->trans('Image URL', [], 'Admin.Advparameters.Feature')], 'shop' => [ 'label' => $this->trans('ID / Name of the store', [], 'Admin.Advparameters.Feature'), 'help' => $this->trans('Ignore this field if you don\'t use the Multistore tool. If you leave this field empty, the default store will be used.', [], 'Admin.Advparameters.Help'), ], ]; self::$default_values = [ 'active' => '1', ]; break; } // @since 1.5.0 if (Configuration::get('PS_ADVANCED_STOCK_MANAGEMENT')) { switch ((int) Tools::getValue('entity')) { case $this->entities[$this->trans('Supply Orders', [], 'Admin.Advparameters.Feature')]: // required fields $this->required_fields = [ 'id_supplier', 'id_warehouse', 'reference', 'date_delivery_expected', ]; // available fields $this->available_fields = [ 'no' => ['label' => $this->trans('Ignore this column', [], 'Admin.Advparameters.Feature')], 'id' => ['label' => $this->trans('ID', [], 'Admin.Global')], 'id_supplier' => ['label' => $this->trans('Supplier ID *', [], 'Admin.Advparameters.Feature')], 'id_lang' => ['label' => $this->trans('Lang ID', [], 'Admin.Advparameters.Feature')], 'id_warehouse' => ['label' => $this->trans('Warehouse ID *', [], 'Admin.Advparameters.Feature')], 'id_currency' => ['label' => $this->trans('Currency ID *', [], 'Admin.Advparameters.Feature')], 'reference' => ['label' => $this->trans('Supply Order Reference *', [], 'Admin.Advparameters.Feature')], 'date_delivery_expected' => ['label' => $this->trans('Delivery Date (Y-M-D)*', [], 'Admin.Advparameters.Feature')], 'discount_rate' => ['label' => $this->trans('Discount rate', [], 'Admin.Advparameters.Feature')], 'is_template' => ['label' => $this->trans('Template', [], 'Admin.Advparameters.Feature')], ]; // default values self::$default_values = [ 'id_lang' => (int) Configuration::get('PS_LANG_DEFAULT'), 'id_currency' => Currency::getDefaultCurrency()->id, 'discount_rate' => '0', 'is_template' => '0', ]; break; case $this->entities[$this->trans('Supply Order Details', [], 'Admin.Advparameters.Feature')]: // required fields $this->required_fields = [ 'supply_order_reference', 'id_product', 'unit_price_te', 'quantity_expected', ]; // available fields $this->available_fields = [ 'no' => ['label' => $this->trans('Ignore this column', [], 'Admin.Advparameters.Feature')], 'supply_order_reference' => ['label' => $this->trans('Supply Order Reference *', [], 'Admin.Advparameters.Feature')], 'id_product' => ['label' => $this->trans('Product ID *', [], 'Admin.Advparameters.Feature')], 'id_product_attribute' => ['label' => $this->trans('Product Attribute ID', [], 'Admin.Advparameters.Feature')], 'unit_price_te' => ['label' => $this->trans('Unit Price (tax excl.)*', [], 'Admin.Advparameters.Feature')], 'quantity_expected' => ['label' => $this->trans('Quantity Expected *', [], 'Admin.Advparameters.Feature')], 'discount_rate' => ['label' => $this->trans('Discount Rate', [], 'Admin.Advparameters.Feature')], 'tax_rate' => ['label' => $this->trans('Tax Rate', [], 'Admin.Advparameters.Feature')], ]; // default values self::$default_values = [ 'discount_rate' => '0', 'tax_rate' => '0', ]; break; } } $this->separator = ($separator = Tools::substr((string) (trim(Tools::getValue('separator'))), 0, 1)) ? $separator : ';'; $this->convert = false; $this->multiple_value_separator = ($separator = Tools::substr((string) (trim(Tools::getValue('multiple_value_separator'))), 0, 1)) ? $separator : ','; } public function setMedia($isNewTheme = false) { $bo_theme = ((Validate::isLoadedObject($this->context->employee) && $this->context->employee->bo_theme) ? $this->context->employee->bo_theme : 'default'); if (!file_exists(_PS_BO_ALL_THEMES_DIR_ . $bo_theme . DIRECTORY_SEPARATOR . 'template')) { $bo_theme = 'default'; } // We need to set parent media first, so that jQuery is loaded before the dependant plugins parent::setMedia($isNewTheme); $this->addJs(__PS_BASE_URI__ . $this->admin_webpath . '/themes/' . $bo_theme . '/js/jquery.iframe-transport.js'); $this->addJs(__PS_BASE_URI__ . $this->admin_webpath . '/themes/' . $bo_theme . '/js/jquery.fileupload.js'); $this->addJs(__PS_BASE_URI__ . $this->admin_webpath . '/themes/' . $bo_theme . '/js/jquery.fileupload-process.js'); $this->addJs(__PS_BASE_URI__ . $this->admin_webpath . '/themes/' . $bo_theme . '/js/jquery.fileupload-validate.js'); $this->addJs(__PS_BASE_URI__ . 'js/vendor/spin.js'); $this->addJs(__PS_BASE_URI__ . 'js/vendor/ladda.js'); } /** * @return bool|string * * @throws PrestaShopException * @throws SmartyException */ public function renderForm() { // If import was executed - collect errors or success message // and send them to the migrated controller. if ($this->importExecuted) { $session = $this->getSession(); if ($this->errors) { foreach ($this->errors as $error) { $session->getFlashBag()->add('error', $error); } } else { foreach ($this->warnings as $warning) { $session->getFlashBag()->add('warning', $warning); } $session->getFlashBag()->add( 'success', $this->trans( 'Your file has been successfully imported into your shop. Don\'t forget to re-build the products\' search index.', [], 'Admin.Advparameters.Notification' ) ); } } $request = $this->getSymfonyRequest(); if ($request && $request->isMethod(\Symfony\Component\HttpFoundation\Request::METHOD_GET)) { // Import form is reworked in Symfony. // If user tries to access legacy form directly, // we redirect him to new form. $symfonyImportForm = $this->context->link->getAdminLink('AdminImport'); Tools::redirectAdmin($symfonyImportForm); } if (!is_dir(AdminImportController::getPath())) { return !($this->errors[] = $this->trans('The import directory doesn\'t exist. Please check your file path.', [], 'Admin.Advparameters.Notification')); } if (!is_writable(AdminImportController::getPath())) { $this->displayWarning($this->trans('The import directory must be writable (CHMOD 755 / 777).', [], 'Admin.Advparameters.Notification')); } $files_to_import = scandir(AdminImportController::getPath(), SCANDIR_SORT_NONE); uasort($files_to_import, ['AdminImportController', 'usortFiles']); foreach ($files_to_import as $k => &$filename) { //exclude . .. .svn and index.php and all hidden files if (preg_match('/^\..*|index\.php/i', $filename) || is_dir(AdminImportController::getPath() . $filename)) { unset($files_to_import[$k]); } } unset($filename); $this->fields_form = ['']; $this->toolbar_scroll = false; $this->toolbar_btn = []; // adds fancybox $this->addJqueryPlugin(['fancybox']); $entity_selected = 0; if (isset($this->entities[$this->trans(Tools::ucfirst(Tools::getValue('import_type')))])) { $entity_selected = $this->entities[$this->trans(Tools::ucfirst(Tools::getValue('import_type')))]; $this->context->cookie->entity_selected = (int) $entity_selected; } elseif (isset($this->context->cookie->entity_selected)) { $entity_selected = (int) $this->context->cookie->entity_selected; } $csv_selected = ''; if (isset($this->context->cookie->csv_selected) && @filemtime(AdminImportController::getPath( urldecode($this->context->cookie->csv_selected) ))) { $csv_selected = urldecode($this->context->cookie->csv_selected); } else { $this->context->cookie->csv_selected = $csv_selected; } $id_lang_selected = ''; if (isset($this->context->cookie->iso_lang_selected) && $this->context->cookie->iso_lang_selected) { $id_lang_selected = (int) Language::getIdByIso(urldecode($this->context->cookie->iso_lang_selected)); } $separator_selected = $this->separator; if (isset($this->context->cookie->separator_selected) && $this->context->cookie->separator_selected) { $separator_selected = urldecode($this->context->cookie->separator_selected); } $multiple_value_separator_selected = $this->multiple_value_separator; if (isset($this->context->cookie->multiple_value_separator_selected) && $this->context->cookie->multiple_value_separator_selected) { $multiple_value_separator_selected = urldecode($this->context->cookie->multiple_value_separator_selected); } //get post max size $post_max_size = ini_get('post_max_size'); $bytes = (int) trim($post_max_size); $last = strtolower($post_max_size[strlen($post_max_size) - 1]); switch ($last) { case 'g': $bytes *= 1024; // no break to fall-through case 'm': $bytes *= 1024; // no break to fall-through case 'k': $bytes *= 1024; } if ($bytes == '') { $bytes = 20971520; } // 20Mb $this->tpl_form_vars = [ 'post_max_size' => (int) $bytes, 'module_confirmation' => Tools::isSubmit('import') && !count($this->warnings), 'path_import' => AdminImportController::getPath(), 'entities' => $this->entities, 'entity_selected' => $entity_selected, 'csv_selected' => $csv_selected, 'separator_selected' => $separator_selected, 'multiple_value_separator_selected' => $multiple_value_separator_selected, 'files_to_import' => $files_to_import, 'languages' => Language::getLanguages(false), 'id_language' => ($id_lang_selected) ? $id_lang_selected : $this->context->language->id, 'available_fields' => $this->getAvailableFields(), 'truncateAuthorized' => (Shop::isFeatureActive() && $this->context->employee->isSuperAdmin()) || !Shop::isFeatureActive(), 'PS_ADVANCED_STOCK_MANAGEMENT' => Configuration::get('PS_ADVANCED_STOCK_MANAGEMENT'), ]; return parent::renderForm(); } public function ajaxProcessuploadCsv() { $filename_prefix = date('YmdHis') . '-'; if (isset($_FILES['file']) && !empty($_FILES['file']['error'])) { switch ($_FILES['file']['error']) { case UPLOAD_ERR_INI_SIZE: $_FILES['file']['error'] = $this->trans('The uploaded file exceeds the upload_max_filesize directive in php.ini. If your server configuration allows it, you may add a directive in your .htaccess.', [], 'Admin.Advparameters.Notification'); break; case UPLOAD_ERR_FORM_SIZE: $_FILES['file']['error'] = $this->trans('The uploaded file exceeds the post_max_size directive in php.ini. If your server configuration allows it, you may add a directive in your .htaccess, for example:', [], 'Admin.Advparameters.Notification') . '
php_value post_max_size 20M ' . $this->trans('(click to open "Generators" page)', [], 'Admin.Advparameters.Notification') . ''; break; case UPLOAD_ERR_PARTIAL: $_FILES['file']['error'] = $this->trans('The uploaded file was only partially uploaded.', [], 'Admin.Advparameters.Notification'); break; case UPLOAD_ERR_NO_FILE: $_FILES['file']['error'] = $this->trans('No file was uploaded.', [], 'Admin.Advparameters.Notification'); break; } } elseif (!preg_match('#([^\.]*?)\.(csv|xls[xt]?|o[dt]s)$#is', $_FILES['file']['name'])) { $_FILES['file']['error'] = $this->trans('The extension of your file should be ".csv".', [], 'Admin.Advparameters.Notification'); } elseif (!@filemtime($_FILES['file']['tmp_name']) || !@move_uploaded_file($_FILES['file']['tmp_name'], AdminImportController::getPath() . $filename_prefix . str_replace("\0", '', $_FILES['file']['name']))) { $_FILES['file']['error'] = $this->trans('An error occurred while uploading / copying the file.', [], 'Admin.Advparameters.Notification'); } else { @chmod(AdminImportController::getPath() . $filename_prefix . $_FILES['file']['name'], 0664); $_FILES['file']['filename'] = $filename_prefix . str_replace('\0', '', $_FILES['file']['name']); } die(json_encode($_FILES)); } public function renderView() { $this->addJS(_PS_JS_DIR_ . 'admin/import.js'); $handle = $this->openCsvFile(); $nb_column = $this->getNbrColumn($handle, $this->separator); $nb_table = ceil($nb_column / MAX_COLUMNS); $res = []; foreach ($this->required_fields as $elem) { $res[] = '\'' . $elem . '\''; } $data = []; for ($i = 0; $i < $nb_table; ++$i) { $data[$i] = $this->generateContentTable($i, $nb_column, $handle, $this->separator); } $this->context->cookie->entity_selected = (int) Tools::getValue('entity'); $this->context->cookie->iso_lang_selected = urlencode(Tools::getValue('iso_lang')); $this->context->cookie->separator_selected = urlencode($this->separator); $this->context->cookie->multiple_value_separator_selected = urlencode($this->multiple_value_separator); $this->context->cookie->csv_selected = urlencode(Tools::getValue('csv')); $this->tpl_view_vars = [ 'import_matchs' => Db::getInstance()->executeS('SELECT * FROM ' . _DB_PREFIX_ . 'import_match', true, false), 'fields_value' => [ 'csv' => Tools::getValue('csv'), 'entity' => (int) Tools::getValue('entity'), 'iso_lang' => Tools::getValue('iso_lang'), 'truncate' => Tools::getValue('truncate'), 'forceIDs' => Tools::getValue('forceIDs'), 'regenerate' => Tools::getValue('regenerate'), 'sendemail' => Tools::getValue('sendemail'), 'match_ref' => Tools::getValue('match_ref'), 'separator' => $this->separator, 'multiple_value_separator' => $this->multiple_value_separator, ], 'nb_table' => $nb_table, 'nb_column' => $nb_column, 'res' => implode(',', $res), 'max_columns' => MAX_COLUMNS, 'no_pre_select' => ['price_tin', 'feature'], 'available_fields' => $this->available_fields, 'data' => $data, ]; return parent::renderView(); } public function initToolbar() { switch ($this->display) { case 'import': // Default cancel button - like old back link $back = Tools::safeOutput(Tools::getValue('back', '')); if (empty($back)) { $back = self::$currentIndex . '&token=' . $this->token; } $this->toolbar_btn['cancel'] = [ 'href' => $back, 'desc' => $this->trans('Cancel', [], 'Admin.Actions'), ]; // Default save button - action dynamically handled in javascript $this->toolbar_btn['save-import'] = [ 'href' => '#', 'desc' => $this->trans('Import .CSV data', [], 'Admin.Advparameters.Feature'), ]; break; } } protected function generateContentTable($current_table, $nb_column, $handle, $glue) { $html = ''; // Header for ($i = 0; $i < $nb_column; ++$i) { if (MAX_COLUMNS * (int) $current_table <= $i && (int) $i < MAX_COLUMNS * ((int) $current_table + 1)) { $html .= ''; } } $html .= ''; AdminImportController::setLocale(); for ($current_line = 0; $current_line < 10 && $line = fgetcsv($handle, MAX_LINE_SIZE, $glue); ++$current_line) { /* UTF-8 conversion */ if ($this->convert) { $line = $this->utf8EncodeArray($line); } $html .= ''; foreach ($line as $nb_c => $column) { if ((MAX_COLUMNS * (int) $current_table <= $nb_c) && ((int) $nb_c < MAX_COLUMNS * ((int) $current_table + 1))) { $html .= ''; } } $html .= ''; } $html .= ''; AdminImportController::rewindBomAware($handle); return $html; } public function init() { parent::init(); if (Tools::isSubmit('submitImportFile')) { $this->display = 'import'; } } public function initContent() { if ($this->display == 'import') { if (Tools::getValue('csv')) { $this->content .= $this->renderView(); } else { $this->errors[] = $this->trans('To proceed, please upload a file first.', [], 'Admin.Advparameters.Notification'); $this->content .= $this->renderForm(); } } else { $this->content .= $this->renderForm(); } $this->context->smarty->assign([ 'content' => $this->content, ]); } protected static function rewindBomAware($handle) { // A rewind wrapper that skips BOM signature wrongly if (!is_resource($handle)) { return false; } rewind($handle); if (($bom = fread($handle, 3)) != "\xEF\xBB\xBF") { rewind($handle); } } protected static function getBoolean($field) { return (bool) $field; } protected static function getPrice($field) { return (float) str_replace( [',', '%'], ['.', ''], $field ); } protected static function split($field) { if (empty($field)) { return []; } $separator = Tools::getValue('multiple_value_separator'); if (null === $separator || trim($separator) == '') { $separator = ','; } $tab = ''; $uniqid_path = false; // try data:// protocole. If failed, old school file on filesystem. if (($fd = @fopen('data://text/plain;base64,' . base64_encode($field), 'rb')) === false) { do { $uniqid_path = _PS_UPLOAD_DIR_ . uniqid(); } while (file_exists($uniqid_path)); file_put_contents($uniqid_path, $field); $fd = fopen($uniqid_path, 'rb'); } if ($fd === false) { return []; } $tab = fgetcsv($fd, MAX_LINE_SIZE, $separator); fclose($fd); if ($uniqid_path !== false && file_exists($uniqid_path)) { @unlink($uniqid_path); } if (empty($tab) || (!is_array($tab))) { return []; } return $tab; } protected static function createMultiLangField($field) { $res = []; foreach (Language::getIDs(false) as $id_lang) { $res[$id_lang] = $field; } return $res; } protected function getTypeValuesOptions($nb_c) { $i = 0; $no_pre_select = ['price_tin', 'feature']; $options = ''; foreach ($this->available_fields as $k => $field) { $options .= '