* @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 .= '' . htmlentities(Tools::substr($column, 0, 200), ENT_QUOTES, 'UTF-8') . ' | ';
}
}
$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 .= '