* @copyright 2007-2012 PrestaShop SA * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) * International Registered Trademark & Property of PrestaShop SA */ @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 = array(); public $available_fields = array(); public $required_fields = array('name'); public $cache_image_deleted = array(); public static $default_values = array(); public static $validators = array( 'active' => array('AdminImportController', 'getBoolean'), 'tax_rate' => array('AdminImportController', 'getPrice'), /** Tax excluded */ 'price_tex' => array('AdminImportController', 'getPrice'), /** Tax included */ 'price_tin' => array('AdminImportController', 'getPrice'), 'reduction_price' => array('AdminImportController', 'getPrice'), 'reduction_percent' => array('AdminImportController', 'getPrice'), 'wholesale_price' => array('AdminImportController', 'getPrice'), 'ecotax' => array('AdminImportController', 'getPrice'), 'name' => array('AdminImportController', 'createMultiLangField'), 'description' => array('AdminImportController', 'createMultiLangField'), 'description_short' => array('AdminImportController', 'createMultiLangField'), 'meta_title' => array('AdminImportController', 'createMultiLangField'), 'meta_keywords' => array('AdminImportController', 'createMultiLangField'), 'meta_description' => array('AdminImportController', 'createMultiLangField'), 'link_rewrite' => array('AdminImportController', 'createMultiLangField'), 'available_now' => array('AdminImportController', 'createMultiLangField'), 'available_later' => array('AdminImportController', 'createMultiLangField'), 'category' => array('AdminImportController', 'split'), 'online_only' => array('AdminImportController', 'getBoolean') ); public $separator; public $multiple_value_separator; public function __construct() { $this->entities = array( $this->l('Categories'), $this->l('Products'), $this->l('Combinations'), $this->l('Customers'), $this->l('Addresses'), $this->l('Manufacturers'), $this->l('Suppliers'), ); // @since 1.5.0 if (Configuration::get('PS_ADVANCED_STOCK_MANAGEMENT')) { $this->entities = array_merge( $this->entities, array( $this->l('SupplyOrders'), $this->l('SupplyOrdersDetails'), ) ); } $this->entities = array_flip($this->entities); switch ((int)Tools::getValue('entity')) { case $this->entities[$this->l('Combinations')]: $this->required_fields = array( 'id_product', 'group', 'attribute' ); $this->available_fields = array( 'no' => array('label' => $this->l('Ignore this column')), 'id_product' => array('label' => $this->l('Product ID').'*'), 'group' => array( 'label' => $this->l('Attribute (Name:Type:Position)').'*' ), 'attribute' => array( 'label' => $this->l('Value (Value:Position)').'*' ), 'supplier_reference' => array('label' => $this->l('Supplier reference')), 'reference' => array('label' => $this->l('Reference')), 'ean13' => array('label' => $this->l('EAN13')), 'upc' => array('label' => $this->l('UPC')), 'wholesale_price' => array('label' => $this->l('Wholesale price')), 'price' => array('label' => $this->l('Impact on price')), 'ecotax' => array('label' => $this->l('Ecotax')), 'quantity' => array('label' => $this->l('Quantity')), 'minimal_quantity' => array('label' => $this->l('Minimal quantity')), 'weight' => array('label' => $this->l('Impact on weight')), 'default_on' => array('label' => $this->l('Default (0 = No, 1 = Yes)')), 'image_position' => array( 'label' => $this->l('Image position') ), 'image_url' => array('label' => $this->l('Image URL')), 'delete_existing_images' => array( 'label' => $this->l('Delete existing images (0 = No, 1 = Yes)') ), 'shop' => array( '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, default shop will be used'), ) ); self::$default_values = array( 'reference' => '', 'supplier_reference' => '', 'ean13' => '', 'upc' => '', 'wholesale_price' => 0, 'price' => 0, 'ecotax' => 0, 'quantity' => 0, 'minimal_quantity' => 1, 'weight' => 0, 'default_on' => 0, ); break; case $this->entities[$this->l('Categories')]: $this->available_fields = array( 'no' => array('label' => $this->l('Ignore this column')), 'id' => array('label' => $this->l('ID')), 'active' => array('label' => $this->l('Active (0/1)')), 'name' => array('label' => $this->l('Name *')), 'parent' => array('label' => $this->l('Parent category')), 'is_root_category' => array( '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' => array('label' => $this->l('Description')), 'meta_title' => array('label' => $this->l('Meta-title')), 'meta_keywords' => array('label' => $this->l('Meta-keywords')), 'meta_description' => array('label' => $this->l('Meta-description')), 'link_rewrite' => array('label' => $this->l('URL rewritten')), 'image' => array('label' => $this->l('Image URL')), 'shop' => array( '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, default shop will be used'), ), ); self::$default_values = array( 'active' => '1', 'parent' => Configuration::get('PS_HOME_CATEGORY'), 'link_rewrite' => '' ); break; case $this->entities[$this->l('Products')]: self::$validators['image'] = array( 'AdminImportController', 'split' ); $this->available_fields = array( 'no' => array('label' => $this->l('Ignore this column')), 'id' => array('label' => $this->l('ID')), 'active' => array('label' => $this->l('Active (0/1)')), 'name' => array('label' => $this->l('Name *')), 'category' => array('label' => $this->l('Categories (x,y,z...)')), 'price_tex' => array('label' => $this->l('Price tax excl.')), 'price_tin' => array('label' => $this->l('Price tax incl.')), 'id_tax_rules_group' => array('label' => $this->l('Tax rules ID')), 'wholesale_price' => array('label' => $this->l('Wholesale price')), 'on_sale' => array('label' => $this->l('On sale (0/1)')), 'reduction_price' => array('label' => $this->l('Discount amount')), 'reduction_percent' => array('label' => $this->l('Discount percent')), 'reduction_from' => array('label' => $this->l('Discount from (yyyy-mm-dd)')), 'reduction_to' => array('label' => $this->l('Discount to (yyyy-mm-dd)')), 'reference' => array('label' => $this->l('Reference #')), 'supplier_reference' => array('label' => $this->l('Supplier reference #')), 'supplier' => array('label' => $this->l('Supplier')), 'manufacturer' => array('label' => $this->l('Manufacturer')), 'ean13' => array('label' => $this->l('EAN13')), 'upc' => array('label' => $this->l('UPC')), 'ecotax' => array('label' => $this->l('Ecotax')), 'weight' => array('label' => $this->l('Weight')), 'quantity' => array('label' => $this->l('Quantity')), 'description_short' => array('label' => $this->l('Short description')), 'description' => array('label' => $this->l('Description')), 'tags' => array('label' => $this->l('Tags (x,y,z...)')), 'meta_title' => array('label' => $this->l('Meta-title')), 'meta_keywords' => array('label' => $this->l('Meta-keywords')), 'meta_description' => array('label' => $this->l('Meta-description')), 'link_rewrite' => array('label' => $this->l('URL rewritten')), 'available_now' => array('label' => $this->l('Text when in stock')), 'available_later' => array('label' => $this->l('Text when backorder allowed')), 'available_for_order' => array('label' => $this->l('Available for order (0 = No, 1 = Yes)')), 'date_add' => array('label' => $this->l('Product creation date')), 'show_price' => array('label' => $this->l('Show price (0 = No, 1 = Yes)')), 'image' => array('label' => $this->l('Image URLs (x,y,z...)')), 'delete_existing_images' => array( 'label' => $this->l('Delete existing images (0 = No, 1 = Yes)') ), 'features' => array('label' => $this->l('Feature(Name:Value:Position)')), 'online_only' => array('label' => $this->l('Available online only (0 = No, 1 = Yes)')), 'condition' => array('label' => $this->l('Condition')), 'shop' => array( '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, default shop will be used'), ) ); self::$default_values = array( 'id_category' => array((int)Configuration::get('PS_HOME_CATEGORY')), 'id_category_default' => (int)Configuration::get('PS_HOME_CATEGORY'), 'active' => '1', 'quantity' => 0, 'price' => 0, 'id_tax_rules_group' => 0, 'description_short' => array((int)Configuration::get('PS_LANG_DEFAULT') => ''), 'link_rewrite' => array((int)Configuration::get('PS_LANG_DEFAULT') => ''), 'online_only' => 0, 'condition' => 'new', 'date_add' => date('Y-m-d H:i:s'), 'condition' => 'new', ); break; case $this->entities[$this->l('Customers')]: //Overwrite required_fields AS only email is required whereas other entities $this->required_fields = array('email', 'passwd', 'lastname', 'firstname'); $this->available_fields = array( 'no' => array('label' => $this->l('Ignore this column')), 'id' => array('label' => $this->l('ID')), 'active' => array('label' => $this->l('Active (0/1)')), 'id_gender' => array('label' => $this->l('Titles ID (Mr = 1, Ms = 2, else 0)')), 'email' => array('label' => $this->l('E-mail *')), 'passwd' => array('label' => $this->l('Password *')), 'birthday' => array('label' => $this->l('Birthday (yyyy-mm-dd)')), 'lastname' => array('label' => $this->l('Lastname *')), 'firstname' => array('label' => $this->l('Firstname *')), 'newsletter' => array('label' => $this->l('Newsletter (0/1)')), 'optin' => array('label' => $this->l('Opt-in (0/1)')), 'id_shop' => array( '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, default shop will be used'), ), ); self::$default_values = array( 'active' => '1', 'id_shop' => Configuration::get('PS_SHOP_DEFAULT'), ); break; case $this->entities[$this->l('Addresses')]: //Overwrite required_fields $this->required_fields = array( 'lastname', 'firstname', 'address1', 'postcode', 'country', 'customer_email', 'city' ); $this->available_fields = array( 'no' => array('label' => $this->l('Ignore this column')), 'id' => array('label' => $this->l('ID')), 'alias' => array('label' => $this->l('Alias *')), 'active' => array('label' => $this->l('Active (0/1)')), 'customer_email' => array('label' => $this->l('Customer e-mail')), 'id_customer' => array('label' => $this->l('Customer ID')), 'manufacturer' => array('label' => $this->l('Manufacturer')), 'supplier' => array('label' => $this->l('Supplier')), 'company' => array('label' => $this->l('Company')), 'lastname' => array('label' => $this->l('Lastname *')), 'firstname' => array('label' => $this->l('Firstname *')), 'address1' => array('label' => $this->l('Address 1 *')), 'address2' => array('label' => $this->l('Address 2')), 'postcode' => array('label' => $this->l('Postcode*/ Zipcode*')), 'city' => array('label' => $this->l('City *')), 'country' => array('label' => $this->l('Country *')), 'state' => array('label' => $this->l('State')), 'other' => array('label' => $this->l('Other')), 'phone' => array('label' => $this->l('Phone')), 'phone_mobile' => array('label' => $this->l('Mobile Phone')), 'vat_number' => array('label' => $this->l('VAT number')), ); self::$default_values = array( 'alias' => 'Alias', 'postcode' => 'X' ); break; case $this->entities[$this->l('Manufacturers')]: case $this->entities[$this->l('Suppliers')]: //Overwrite validators AS name is not MultiLangField self::$validators = array( 'description' => array('AdminImportController', 'createMultiLangField'), 'short_description' => array('AdminImportController', 'createMultiLangField'), 'meta_title' => array('AdminImportController', 'createMultiLangField'), 'meta_keywords' => array('AdminImportController', 'createMultiLangField'), 'meta_description' => array('AdminImportController', 'createMultiLangField'), ); $this->available_fields = array( 'no' => array('label' => $this->l('Ignore this column')), 'id' => array('label' => $this->l('ID')), 'active' => array('label' => $this->l('Active (0/1)')), 'name' => array('label' => $this->l('Name *')), 'description' => array('label' => $this->l('Description')), 'short_description' => array('label' => $this->l('Short description')), 'meta_title' => array('label' => $this->l('Meta-title')), 'meta_keywords' => array('label' => $this->l('Meta-keywords')), 'meta_description' => array('label' => $this->l('Meta-description')), 'shop' => array( '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, default shop will be used'), ), ); self::$default_values = array( 'shop' => Shop::getGroupFromShop(Configuration::get('PS_SHOP_DEFAULT')), ); break; // @since 1.5.0 case $this->entities[$this->l('SupplyOrders')]: if (Configuration::get('PS_ADVANCED_STOCK_MANAGEMENT')) { // required fields $this->required_fields = array( 'id_supplier', 'id_warehouse', 'reference', 'date_delivery_expected', ); // available fields $this->available_fields = array( 'no' => array('label' => $this->l('Ignore this column')), 'id' => array('label' => $this->l('ID')), 'id_supplier' => array('label' => $this->l('Supplier ID *')), 'id_lang' => array('label' => $this->l('Lang ID')), 'id_warehouse' => array('label' => $this->l('Warehouse ID *')), 'id_currency' => array('label' => $this->l('Currency ID *')), 'reference' => array('label' => $this->l('Supply Order Reference *')), 'date_delivery_expected' => array('label' => $this->l('Delivery Date (Y-M-D)*')), 'discount_rate' => array('label' => $this->l('Discount Rate')), 'is_template' => array('label' => $this->l('Template')), ); // default values self::$default_values = array( 'id_lang' => (int)Configuration::get('PS_LANG_DEFAULT'), 'id_currency' => Currency::getDefaultCurrency()->id, 'discount_rate' => '0', 'is_template' => '0', ); } break; // @since 1.5.0 case $this->entities[$this->l('SupplyOrdersDetails')]: if (Configuration::get('PS_ADVANCED_STOCK_MANAGEMENT')) { // required fields $this->required_fields = array( 'supply_order_reference', 'id_product', 'unit_price_te', 'quantity_expected', ); // available fields $this->available_fields = array( 'no' => array('label' => $this->l('Ignore this column')), 'supply_order_reference' => array('label' => $this->l('Supply Order Reference *')), 'id_product' => array('label' => $this->l('Product ID *')), 'id_product_attribute' => array('label' => $this->l('Product Attribute ID')), 'unit_price_te' => array('label' => $this->l('Unit Price (tax excl.)*')), 'quantity_expected' => array('label' => $this->l('Quantity Expected *')), 'discount_rate' => array('label' => $this->l('Discount Rate')), 'tax_rate' => array('label' => $this->l('Tax Rate')), ); // default values self::$default_values = array( 'discount_rate' => '0', 'tax_rate' => '0', ); } } $this->separator = strval(trim(Tools::getValue('separator', ';'))); if (is_null(Tools::getValue('multiple_value_separator')) || trim(Tools::getValue('multiple_value_separator')) == '') $this->multiple_value_separator = ','; else $this->multiple_value_separator = Tools::getValue('multiple_value_separator'); parent::__construct(); } public function renderForm() { if (!is_writable(_PS_ADMIN_DIR_.'/import/')) $this->displayWarning($this->l('directory import on admin directory must be writable (CHMOD 755 / 777)')); if (isset($this->warnings) && count($this->warnings)) { $warnings = array(); foreach ($this->warnings as $warning) $warnings[] = $warning; } $files_to_import = scandir(_PS_ADMIN_DIR_.'/import/'); uasort($files_to_import, array('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)) unset($files_to_import[$k]); unset($filename); $this->fields_form = array(''); $this->toolbar_scroll = false; $this->toolbar_btn = array(); // adds fancybox $this->addCSS(_PS_CSS_DIR_.'jquery.fancybox-1.3.4.css', 'screen'); $this->addJqueryPlugin(array('fancybox')); $this->tpl_form_vars = array( 'module_confirmation' => (Tools::getValue('import')) && (isset($this->warnings) && !count($this->warnings)), 'path_import' => _PS_ADMIN_DIR_.'/import/', 'entities' => $this->entities, 'entity' => Tools::getValue('entity'), 'files_to_import' => $files_to_import, 'languages' => Language::getLanguages(false), 'id_language' => $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 renderView() { $this->addJS(_PS_JS_DIR_.'adminImport.js'); $handle = $this->openCsvFile(); $nb_column = $this->getNbrColumn($handle, $this->separator); $nb_table = ceil($nb_column / MAX_COLUMNS); $res = array(); foreach ($this->required_fields as $elem) $res[] = '\''.$elem.'\''; $data = array(); for ($i = 0; $i < $nb_table; $i++) $data[$i] = $this->generateContentTable($i, $nb_column, $handle, $this->separator); $this->tpl_view_vars = array( 'import_matchs' => Db::getInstance()->executeS('SELECT * FROM '._DB_PREFIX_.'import_match'), 'fields_value' => array( 'csv' => Tools::getValue('csv'), 'convert' => Tools::getValue('convert'), 'entity' => (int)Tools::getValue('entity'), 'iso_lang' => Tools::getValue('iso_lang'), 'truncate' => Tools::getValue('truncate'), 'forceIDs' => Tools::getValue('forceIDs'), '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' => array('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'] = array( 'href' => $back, 'desc' => $this->l('Cancel') ); // Default save button - action dynamically handled in javascript $this->toolbar_btn['save-import'] = array( 'href' => '#', 'desc' => $this->l('Import .CSV data') ); 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 (Tools::getValue('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() { // toolbar (save, cancel, new, ..) $this->initToolbar(); if ($this->display == 'import') if (Tools::getValue('csv')) $this->content .= $this->renderView(); else { $this->errors[] = $this->l('You must upload a file for go to the next step'); $this->content .= $this->renderForm(); } else $this->content .= $this->renderForm(); $this->context->smarty->assign(array( 'content' => $this->content, 'url_post' => self::$currentIndex.'&token='.$this->token, )); } protected static function rewindBomAware($handle) { // A rewind wrapper that skip BOM signature wrongly rewind($handle); if (($bom = fread($handle, 3)) != "\xEF\xBB\xBF") rewind($handle); } protected static function getBoolean($field) { return (boolean)$field; } protected static function getPrice($field) { $field = ((float)str_replace(',', '.', $field)); $field = ((float)str_replace('%', '', $field)); return $field; } protected static function split($field) { if (empty($field)) return array(); $separator = Tools::getValue('multiple_value_separator'); if (is_null($separator) || trim($separator) == '') $separator = ','; do $uniqid = uniqid(); while (file_exists(_PS_UPLOAD_DIR_.$uniqid)); $tmp_file = file_put_contents(_PS_UPLOAD_DIR_.$uniqid, $field); $fd = fopen($temp, 'r'); $tab = fgetcsv($fd, MAX_LINE_SIZE, $separator); fclose($fd); unlink($tmp_file); if (empty($tab) || (!is_array($tab))) return array(); return $tab; } protected static function createMultiLangField($field) { $languages = Language::getLanguages(false); $res = array(); foreach ($languages as $lang) $res[$lang['id_lang']] = $field; return $res; } protected function getTypeValuesOptions($nb_c) { $i = 0; $no_pre_select = array('price_tin', 'feature'); $options = ''; foreach ($this->available_fields as $k => $field) { $options .= '