PageRenderTime 48ms CodeModel.GetById 21ms RepoModel.GetById 0ms app.codeStats 2ms

/controllers/admin/AdminProductsController.php

https://github.com/netplayer/PrestaShop
PHP | 4756 lines | 3910 code | 545 blank | 301 comment | 831 complexity | 89cdea2af3e401ff2a074e05f8a33493 MD5 | raw file
Possible License(s): CC-BY-SA-3.0, LGPL-2.1, LGPL-3.0
  1. <?php
  2. /*
  3. * 2007-2014 PrestaShop
  4. *
  5. * NOTICE OF LICENSE
  6. *
  7. * This source file is subject to the Open Software License (OSL 3.0)
  8. * that is bundled with this package in the file LICENSE.txt.
  9. * It is also available through the world-wide-web at this URL:
  10. * http://opensource.org/licenses/osl-3.0.php
  11. * If you did not receive a copy of the license and are unable to
  12. * obtain it through the world-wide-web, please send an email
  13. * to license@prestashop.com so we can send you a copy immediately.
  14. *
  15. * DISCLAIMER
  16. *
  17. * Do not edit or add to this file if you wish to upgrade PrestaShop to newer
  18. * versions in the future. If you wish to customize PrestaShop for your
  19. * needs please refer to http://www.prestashop.com for more information.
  20. *
  21. * @author PrestaShop SA <contact@prestashop.com>
  22. * @copyright 2007-2014 PrestaShop SA
  23. * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
  24. * International Registered Trademark & Property of PrestaShop SA
  25. */
  26. class AdminProductsControllerCore extends AdminController
  27. {
  28. /** @var integer Max image size for upload
  29. * As of 1.5 it is recommended to not set a limit to max image size
  30. */
  31. protected $max_file_size = null;
  32. protected $max_image_size = null;
  33. protected $_category;
  34. /**
  35. * @var string name of the tab to display
  36. */
  37. protected $tab_display;
  38. protected $tab_display_module;
  39. /**
  40. * The order in the array decides the order in the list of tab. If an element's value is a number, it will be preloaded.
  41. * The tabs are preloaded from the smallest to the highest number.
  42. * @var array Product tabs.
  43. */
  44. protected $available_tabs = array();
  45. protected $default_tab = 'Informations';
  46. protected $available_tabs_lang = array();
  47. protected $position_identifier = 'id_product';
  48. protected $submitted_tabs;
  49. protected $id_current_category;
  50. public function __construct()
  51. {
  52. $this->bootstrap = true;
  53. $this->table = 'product';
  54. $this->className = 'Product';
  55. $this->lang = true;
  56. $this->explicitSelect = true;
  57. $this->bulk_actions = array(
  58. 'delete' => array(
  59. 'text' => $this->l('Delete selected'),
  60. 'icon' => 'icon-trash',
  61. 'confirm' => $this->l('Delete selected items?')
  62. )
  63. );
  64. if (!Tools::getValue('id_product'))
  65. $this->multishop_context_group = false;
  66. parent::__construct();
  67. $this->imageType = 'jpg';
  68. $this->_defaultOrderBy = 'position';
  69. $this->max_file_size = (int)(Configuration::get('PS_LIMIT_UPLOAD_FILE_VALUE') * 1000000);
  70. $this->max_image_size = (int)Configuration::get('PS_PRODUCT_PICTURE_MAX_SIZE');
  71. $this->allow_export = true;
  72. // @since 1.5 : translations for tabs
  73. $this->available_tabs_lang = array(
  74. 'Informations' => $this->l('Information'),
  75. 'Pack' => $this->l('Pack'),
  76. 'VirtualProduct' => $this->l('Virtual Product'),
  77. 'Prices' => $this->l('Prices'),
  78. 'Seo' => $this->l('SEO'),
  79. 'Images' => $this->l('Images'),
  80. 'Associations' => $this->l('Associations'),
  81. 'Shipping' => $this->l('Shipping'),
  82. 'Combinations' => $this->l('Combinations'),
  83. 'Features' => $this->l('Features'),
  84. 'Customization' => $this->l('Customization'),
  85. 'Attachments' => $this->l('Attachments'),
  86. 'Quantities' => $this->l('Quantities'),
  87. 'Suppliers' => $this->l('Suppliers'),
  88. 'Warehouses' => $this->l('Warehouses'),
  89. );
  90. $this->available_tabs = array('Quantities' => 6, 'Warehouses' => 14);
  91. if ($this->context->shop->getContext() != Shop::CONTEXT_GROUP)
  92. $this->available_tabs = array_merge($this->available_tabs, array(
  93. 'Informations' => 0,
  94. 'Pack' => 7,
  95. 'VirtualProduct' => 8,
  96. 'Prices' => 1,
  97. 'Seo' => 2,
  98. 'Associations' => 3,
  99. 'Images' => 9,
  100. 'Shipping' => 4,
  101. 'Combinations' => 5,
  102. 'Features' => 10,
  103. 'Customization' => 11,
  104. 'Attachments' => 12,
  105. 'Suppliers' => 13,
  106. ));
  107. // Sort the tabs that need to be preloaded by their priority number
  108. asort($this->available_tabs, SORT_NUMERIC);
  109. /* Adding tab if modules are hooked */
  110. $modules_list = Hook::getHookModuleExecList('displayAdminProductsExtra');
  111. if (is_array($modules_list) && count($modules_list) > 0)
  112. foreach ($modules_list as $m)
  113. {
  114. $this->available_tabs['Module'.ucfirst($m['module'])] = 23;
  115. $this->available_tabs_lang['Module'.ucfirst($m['module'])] = Module::getModuleName($m['module']);
  116. }
  117. if (Tools::getValue('reset_filter_category'))
  118. $this->context->cookie->id_category_products_filter = false;
  119. if (Shop::isFeatureActive() && $this->context->cookie->id_category_products_filter)
  120. {
  121. $category = new Category((int)$this->context->cookie->id_category_products_filter);
  122. if (!$category->inShop())
  123. {
  124. $this->context->cookie->id_category_products_filter = false;
  125. Tools::redirectAdmin($this->context->link->getAdminLink('AdminProducts'));
  126. }
  127. }
  128. /* Join categories table */
  129. if ($id_category = (int)Tools::getValue('productFilter_cl!name'))
  130. {
  131. $this->_category = new Category((int)$id_category);
  132. $_POST['productFilter_cl!name'] = $this->_category->name[$this->context->language->id];
  133. }
  134. else
  135. {
  136. if ($id_category = (int)Tools::getValue('id_category'))
  137. {
  138. $this->id_current_category = $id_category;
  139. $this->context->cookie->id_category_products_filter = $id_category;
  140. }
  141. elseif ($id_category = $this->context->cookie->id_category_products_filter)
  142. $this->id_current_category = $id_category;
  143. if ($this->id_current_category)
  144. $this->_category = new Category((int)$this->id_current_category);
  145. else
  146. $this->_category = new Category();
  147. }
  148. $join_category = false;
  149. if (Validate::isLoadedObject($this->_category) && empty($this->_filter))
  150. $join_category = true;
  151. $this->_join .= '
  152. LEFT JOIN `'._DB_PREFIX_.'image` i ON (i.`id_product` = a.`id_product`)
  153. LEFT JOIN `'._DB_PREFIX_.'stock_available` sav ON (sav.`id_product` = a.`id_product` AND sav.`id_product_attribute` = 0
  154. '.StockAvailable::addSqlShopRestriction(null, null, 'sav').') ';
  155. $alias = 'sa';
  156. $alias_image = 'image_shop';
  157. $id_shop = Shop::isFeatureActive() && Shop::getContext() == Shop::CONTEXT_SHOP? (int)$this->context->shop->id : 'a.id_shop_default';
  158. $this->_join .= ' JOIN `'._DB_PREFIX_.'product_shop` sa ON (a.`id_product` = sa.`id_product` AND sa.id_shop = '.$id_shop.')
  159. LEFT JOIN `'._DB_PREFIX_.'category_lang` cl ON ('.$alias.'.`id_category_default` = cl.`id_category` AND b.`id_lang` = cl.`id_lang` AND cl.id_shop = '.$id_shop.')
  160. LEFT JOIN `'._DB_PREFIX_.'shop` shop ON (shop.id_shop = '.$id_shop.')
  161. LEFT JOIN `'._DB_PREFIX_.'image_shop` image_shop ON (image_shop.`id_image` = i.`id_image` AND image_shop.`cover` = 1 AND image_shop.id_shop = '.$id_shop.')
  162. LEFT JOIN `'._DB_PREFIX_.'product_download` pd ON (pd.`id_product` = a.`id_product`)';
  163. $this->_select .= 'shop.name as shopname, a.id_shop_default, ';
  164. $this->_select .= 'MAX('.$alias_image.'.id_image) id_image, cl.name `name_category`, '.$alias.'.`price`, 0 AS price_final, a.`is_virtual`, pd.`nb_downloadable`, sav.`quantity` as sav_quantity, '.$alias.'.`active`, IF(sav.`quantity`<=0, 1, 0) badge_danger';
  165. if ($join_category)
  166. {
  167. $this->_join .= ' INNER JOIN `'._DB_PREFIX_.'category_product` cp ON (cp.`id_product` = a.`id_product` AND cp.`id_category` = '.(int)$this->_category->id.') ';
  168. $this->_select .= ' , cp.`position`, ';
  169. }
  170. $this->_group = 'GROUP BY '.$alias.'.id_product';
  171. $this->fields_list = array();
  172. $this->fields_list['id_product'] = array(
  173. 'title' => $this->l('ID'),
  174. 'align' => 'center',
  175. 'class' => 'fixed-width-xs',
  176. 'type' => 'int'
  177. );
  178. $this->fields_list['image'] = array(
  179. 'title' => $this->l('Photo'),
  180. 'align' => 'center',
  181. 'image' => 'p',
  182. 'orderby' => false,
  183. 'filter' => false,
  184. 'search' => false
  185. );
  186. $this->fields_list['name'] = array(
  187. 'title' => $this->l('Name'),
  188. 'filter_key' => 'b!name'
  189. );
  190. $this->fields_list['reference'] = array(
  191. 'title' => $this->l('Reference'),
  192. 'align' => 'left',
  193. );
  194. if (Shop::isFeatureActive() && Shop::getContext() != Shop::CONTEXT_SHOP)
  195. $this->fields_list['shopname'] = array(
  196. 'title' => $this->l('Default shop'),
  197. 'filter_key' => 'shop!name',
  198. );
  199. else
  200. $this->fields_list['name_category'] = array(
  201. 'title' => $this->l('Category'),
  202. 'filter_key' => 'cl!name',
  203. );
  204. $this->fields_list['price'] = array(
  205. 'title' => $this->l('Base price'),
  206. 'type' => 'price',
  207. 'align' => 'text-right',
  208. 'filter_key' => 'a!price'
  209. );
  210. $this->fields_list['price_final'] = array(
  211. 'title' => $this->l('Final price'),
  212. 'type' => 'price',
  213. 'align' => 'text-right',
  214. 'havingFilter' => true,
  215. 'orderby' => false,
  216. 'search' => false
  217. );
  218. if (Configuration::get('PS_STOCK_MANAGEMENT'))
  219. $this->fields_list['sav_quantity'] = array(
  220. 'title' => $this->l('Quantity'),
  221. 'type' => 'int',
  222. 'align' => 'text-right',
  223. 'filter_key' => 'sav!quantity',
  224. 'orderby' => true,
  225. 'badge_danger' => true,
  226. //'hint' => $this->l('This is the quantity available in the current shop/group.'),
  227. );
  228. $this->fields_list['active'] = array(
  229. 'title' => $this->l('Status'),
  230. 'active' => 'status',
  231. 'filter_key' => $alias.'!active',
  232. 'align' => 'text-center',
  233. 'type' => 'bool',
  234. 'class' => 'fixed-width-sm',
  235. 'orderby' => false
  236. );
  237. if ($join_category && (int)$this->id_current_category)
  238. $this->fields_list['position'] = array(
  239. 'title' => $this->l('Position'),
  240. 'filter_key' => 'cp!position',
  241. 'align' => 'center',
  242. 'position' => 'position'
  243. );
  244. }
  245. public static function getQuantities($echo, $tr)
  246. {
  247. if ((int)$tr['is_virtual'] == 1 && $tr['nb_downloadable'] == 0)
  248. return '&infin;';
  249. else
  250. return $echo;
  251. }
  252. public function setMedia()
  253. {
  254. parent::setMedia();
  255. $bo_theme = ((Validate::isLoadedObject($this->context->employee)
  256. && $this->context->employee->bo_theme) ? $this->context->employee->bo_theme : 'default');
  257. if (!file_exists(_PS_BO_ALL_THEMES_DIR_.$bo_theme.DIRECTORY_SEPARATOR
  258. .'template'))
  259. $bo_theme = 'default';
  260. $this->addJs(__PS_BASE_URI__.$this->admin_webpath.'/themes/'.$bo_theme.'/js/jquery.iframe-transport.js');
  261. $this->addJs(__PS_BASE_URI__.$this->admin_webpath.'/themes/'.$bo_theme.'/js/jquery.fileupload.js');
  262. $this->addJs(__PS_BASE_URI__.$this->admin_webpath.'/themes/'.$bo_theme.'/js/jquery.fileupload-process.js');
  263. $this->addJs(__PS_BASE_URI__.$this->admin_webpath.'/themes/'.$bo_theme.'/js/jquery.fileupload-validate.js');
  264. $this->addJs(__PS_BASE_URI__.'js/vendor/spin.js');
  265. $this->addJs(__PS_BASE_URI__.'js/vendor/ladda.js');
  266. }
  267. protected function _cleanMetaKeywords($keywords)
  268. {
  269. if (!empty($keywords) && $keywords != '')
  270. {
  271. $out = array();
  272. $words = explode(',', $keywords);
  273. foreach ($words as $word_item)
  274. {
  275. $word_item = trim($word_item);
  276. if (!empty($word_item) && $word_item != '')
  277. $out[] = $word_item;
  278. }
  279. return ((count($out) > 0) ? implode(',', $out) : '');
  280. }
  281. else
  282. return '';
  283. }
  284. protected function copyFromPost(&$object, $table)
  285. {
  286. parent::copyFromPost($object, $table);
  287. if (get_class($object) != 'Product')
  288. return;
  289. /* Additional fields */
  290. $languages = Language::getLanguages(false);
  291. foreach ($languages as $language)
  292. if (isset($_POST['meta_keywords_'.$language['id_lang']]))
  293. {
  294. $_POST['meta_keywords_'.$language['id_lang']] = $this->_cleanMetaKeywords(Tools::strtolower($_POST['meta_keywords_'.$language['id_lang']]));
  295. // preg_replace('/ *,? +,* /', ',', strtolower($_POST['meta_keywords_'.$language['id_lang']]));
  296. $object->meta_keywords[$language['id_lang']] = $_POST['meta_keywords_'.$language['id_lang']];
  297. }
  298. $_POST['width'] = empty($_POST['width']) ? '0' : str_replace(',', '.', $_POST['width']);
  299. $_POST['height'] = empty($_POST['height']) ? '0' : str_replace(',', '.', $_POST['height']);
  300. $_POST['depth'] = empty($_POST['depth']) ? '0' : str_replace(',', '.', $_POST['depth']);
  301. $_POST['weight'] = empty($_POST['weight']) ? '0' : str_replace(',', '.', $_POST['weight']);
  302. if (Tools::getIsset('unit_price') != null)
  303. $object->unit_price = str_replace(',', '.', Tools::getValue('unit_price'));
  304. if (Tools::getIsset('ecotax') != null)
  305. $object->ecotax = str_replace(',', '.', Tools::getValue('ecotax'));
  306. $object->available_for_order = (int)Tools::getValue('available_for_order');
  307. $object->show_price = $object->available_for_order ? 1 : (int)Tools::getValue('show_price');
  308. $object->on_sale = (int)Tools::getValue('on_sale');
  309. $object->online_only = (int)Tools::getValue('online_only');
  310. }
  311. public function getList($id_lang, $orderBy = null, $orderWay = null, $start = 0, $limit = null, $id_lang_shop = null)
  312. {
  313. $orderByPriceFinal = (empty($orderBy) ? ($this->context->cookie->__get($this->table.'Orderby') ? $this->context->cookie->__get($this->table.'Orderby') : 'id_'.$this->table) : $orderBy);
  314. $orderWayPriceFinal = (empty($orderWay) ? ($this->context->cookie->__get($this->table.'Orderway') ? $this->context->cookie->__get($this->table.'Orderby') : 'ASC') : $orderWay);
  315. if ($orderByPriceFinal == 'price_final')
  316. {
  317. $orderBy = 'id_'.$this->table;
  318. $orderWay = 'ASC';
  319. }
  320. parent::getList($id_lang, $orderBy, $orderWay, $start, $limit, $this->context->shop->id);
  321. /* update product quantity with attributes ...*/
  322. $nb = count($this->_list);
  323. if ($this->_list)
  324. {
  325. $context = $this->context->cloneContext();
  326. $context->shop = clone($context->shop);
  327. /* update product final price */
  328. for ($i = 0; $i < $nb; $i++)
  329. {
  330. if (Context::getContext()->shop->getContext() != Shop::CONTEXT_SHOP)
  331. $context->shop = new Shop((int)$this->_list[$i]['id_shop_default']);
  332. // convert price with the currency from context
  333. $this->_list[$i]['price'] = Tools::convertPrice($this->_list[$i]['price'], $this->context->currency, true, $this->context);
  334. $this->_list[$i]['price_tmp'] = Product::getPriceStatic($this->_list[$i]['id_product'], true, null, 2, null, false, true, 1, true, null, null, null, $nothing, true, true, $context);
  335. }
  336. }
  337. if ($orderByPriceFinal == 'price_final')
  338. {
  339. if (strtolower($orderWayPriceFinal) == 'desc')
  340. uasort($this->_list, 'cmpPriceDesc');
  341. else
  342. uasort($this->_list, 'cmpPriceAsc');
  343. }
  344. for ($i = 0; $this->_list && $i < $nb; $i++)
  345. {
  346. $this->_list[$i]['price_final'] = $this->_list[$i]['price_tmp'];
  347. unset($this->_list[$i]['price_tmp']);
  348. }
  349. }
  350. protected function loadObject($opt = false)
  351. {
  352. $result = parent::loadObject($opt);
  353. if ($result && Validate::isLoadedObject($this->object))
  354. {
  355. if (Shop::getContext() == Shop::CONTEXT_SHOP && Shop::isFeatureActive() && !$this->object->isAssociatedToShop())
  356. {
  357. $default_product = new Product((int)$this->object->id, false, null, (int)$this->object->id_shop_default);
  358. $def = ObjectModel::getDefinition($this->object);
  359. foreach ($def['fields'] as $field_name => $row)
  360. {
  361. if (is_array($default_product->$field_name))
  362. foreach ($default_product->$field_name as $key => $value)
  363. $this->object->{$field_name}[$key] = $value;
  364. else
  365. $this->object->$field_name = $default_product->$field_name;
  366. }
  367. }
  368. $this->object->loadStockData();
  369. }
  370. return $result;
  371. }
  372. public function ajaxProcessGetCountriesOptions()
  373. {
  374. if (!$res = Country::getCountriesByIdShop((int)Tools::getValue('id_shop'), (int)$this->context->language->id))
  375. return ;
  376. $tpl = $this->createTemplate('specific_prices_shop_update.tpl');
  377. $tpl->assign(array(
  378. 'option_list' => $res,
  379. 'key_id' => 'id_country',
  380. 'key_value' => 'name'
  381. )
  382. );
  383. $this->content = $tpl->fetch();
  384. }
  385. public function ajaxProcessGetCurrenciesOptions()
  386. {
  387. if (!$res = Currency::getCurrenciesByIdShop((int)Tools::getValue('id_shop')))
  388. return ;
  389. $tpl = $this->createTemplate('specific_prices_shop_update.tpl');
  390. $tpl->assign(array(
  391. 'option_list' => $res,
  392. 'key_id' => 'id_currency',
  393. 'key_value' => 'name'
  394. )
  395. );
  396. $this->content = $tpl->fetch();
  397. }
  398. public function ajaxProcessGetGroupsOptions()
  399. {
  400. if (!$res = Group::getGroups((int)$this->context->language->id, (int)Tools::getValue('id_shop')))
  401. return ;
  402. $tpl = $this->createTemplate('specific_prices_shop_update.tpl');
  403. $tpl->assign(array(
  404. 'option_list' => $res,
  405. 'key_id' => 'id_group',
  406. 'key_value' => 'name'
  407. )
  408. );
  409. $this->content = $tpl->fetch();
  410. }
  411. public function processDeleteVirtualProduct()
  412. {
  413. if (!($id_product_download = ProductDownload::getIdFromIdProduct((int)Tools::getValue('id_product'))))
  414. $this->errors[] = Tools::displayError('Cannot retrieve file');
  415. else
  416. {
  417. $product_download = new ProductDownload((int)$id_product_download);
  418. if (!$product_download->deleteFile((int)$id_product_download))
  419. $this->errors[] = Tools::displayError('Cannot delete file');
  420. else
  421. $this->redirect_after = self::$currentIndex.'&id_product='.(int)Tools::getValue('id_product').'&updateproduct&key_tab=VirtualProduct&conf=1&token='.$this->token;
  422. }
  423. $this->display = 'edit';
  424. $this->tab_display = 'VirtualProduct';
  425. }
  426. public function ajaxProcessAddAttachment()
  427. {
  428. if (isset($_FILES['attachment_file']))
  429. {
  430. if ((int)$_FILES['attachment_file']['error'] === 1)
  431. {
  432. $_FILES['attachment_file']['error'] = array();
  433. $max_upload = (int)ini_get('upload_max_filesize');
  434. $max_post = (int)ini_get('post_max_size');
  435. $upload_mb = min($max_upload, $max_post);
  436. $_FILES['attachment_file']['error'][] = sprintf(
  437. $this->l('File %1$s exceeds the size allowed by the server. The limit is set to %2$d MB.'),
  438. '<b>'.$_FILES['attachment_file']['name'].'</b> ',
  439. '<b>'.$upload_mb.'</b>'
  440. );
  441. }
  442. $_FILES['attachment_file']['error'] = array();
  443. $is_attachment_name_valid = false;
  444. $attachment_names = Tools::getValue('attachment_name');
  445. $attachment_descriptions = Tools::getValue('attachment_description');
  446. if (!isset($attachment_names) || !$attachment_names)
  447. $attachment_names = array();
  448. if (!isset($attachment_descriptions) || !$attachment_descriptions)
  449. $attachment_descriptions = array();
  450. foreach ($attachment_names as $lang => $name)
  451. {
  452. $language = Language::getLanguage((int)$lang);
  453. if (Tools::strlen($name) > 0)
  454. $is_attachment_name_valid = true;
  455. if (!Validate::isGenericName($name))
  456. $_FILES['attachment_file']['error'][] = sprintf(Tools::displayError('Invalid name for %s language'), $language['name']);
  457. elseif (Tools::strlen($name) > 32)
  458. $_FILES['attachment_file']['error'][] = sprintf(Tools::displayError('The name for %1s language is too long (%2d chars max).'), $language['name'], 32);
  459. }
  460. foreach ($attachment_descriptions as $lang => $description)
  461. {
  462. $language = Language::getLanguage((int)$lang);
  463. if (!Validate::isCleanHtml($description))
  464. $_FILES['attachment_file']['error'][] = sprintf(Tools::displayError('Invalid description for %s language'), $language['name']);
  465. }
  466. if (!$is_attachment_name_valid)
  467. $_FILES['attachment_file']['error'][] = Tools::displayError('An attachment name is required.');
  468. if (empty($_FILES['attachment_file']['error']))
  469. {
  470. if (is_uploaded_file($_FILES['attachment_file']['tmp_name']))
  471. {
  472. if ($_FILES['attachment_file']['size'] > (Configuration::get('PS_ATTACHMENT_MAXIMUM_SIZE') * 1024 * 1024))
  473. $_FILES['attachment_file']['error'][] = sprintf(
  474. $this->l('The file is too large. Maximum size allowed is: %1$d kB. The file you\'re trying to upload is: %2$d kB.'),
  475. (Configuration::get('PS_ATTACHMENT_MAXIMUM_SIZE') * 1024),
  476. number_format(($_FILES['attachment_file']['size'] / 1024), 2, '.', '')
  477. );
  478. else
  479. {
  480. do $uniqid = sha1(microtime());
  481. while (file_exists(_PS_DOWNLOAD_DIR_.$uniqid));
  482. if (!copy($_FILES['attachment_file']['tmp_name'], _PS_DOWNLOAD_DIR_.$uniqid))
  483. $_FILES['attachment_file']['error'][] = $this->l('File copy failed');
  484. @unlink($_FILES['attachment_file']['tmp_name']);
  485. }
  486. }
  487. else
  488. $_FILES['attachment_file']['error'][] = Tools::displayError('The file is missing.');
  489. if (empty($_FILES['attachment_file']['error']) && isset($uniqid))
  490. {
  491. $attachment = new Attachment();
  492. foreach ($attachment_names as $lang => $name)
  493. $attachment->name[(int)$lang] = $name;
  494. foreach ($attachment_descriptions as $lang => $description)
  495. $attachment->description[(int)$lang] = $description;
  496. $attachment->file = $uniqid;
  497. $attachment->mime = $_FILES['attachment_file']['type'];
  498. $attachment->file_name = $_FILES['attachment_file']['name'];
  499. if (empty($attachment->mime) || Tools::strlen($attachment->mime) > 128)
  500. $_FILES['attachment_file']['error'][] = Tools::displayError('Invalid file extension');
  501. if (!Validate::isGenericName($attachment->file_name))
  502. $_FILES['attachment_file']['error'][] = Tools::displayError('Invalid file name');
  503. if (Tools::strlen($attachment->file_name) > 128)
  504. $_FILES['attachment_file']['error'][] = Tools::displayError('The file name is too long.');
  505. if (empty($this->errors))
  506. {
  507. $res = $attachment->add();
  508. if (!$res)
  509. $_FILES['attachment_file']['error'][] = Tools::displayError('This attachment was unable to be loaded into the database.');
  510. else
  511. {
  512. $_FILES['attachment_file']['id_attachment'] = $attachment->id;
  513. $_FILES['attachment_file']['filename'] = $attachment->name[$this->context->employee->id_lang];
  514. $id_product = (int)Tools::getValue($this->identifier);
  515. $res = $attachment->attachProduct($id_product);
  516. if (!$res)
  517. $_FILES['attachment_file']['error'][] = Tools::displayError('We were unable to associate this attachment to a product.');
  518. }
  519. }
  520. else
  521. $_FILES['attachment_file']['error'][] = Tools::displayError('Invalid file');
  522. }
  523. }
  524. die(Tools::jsonEncode($_FILES));
  525. }
  526. }
  527. /**
  528. * Attach an existing attachment to the product
  529. *
  530. * @return void
  531. */
  532. public function processAttachments()
  533. {
  534. if ($id = (int)Tools::getValue($this->identifier))
  535. {
  536. $attachments = trim(Tools::getValue('arrayAttachments'), ',');
  537. $attachments = explode(',', $attachments);
  538. if (!Attachment::attachToProduct($id, $attachments))
  539. $this->errors[] = Tools::displayError('An error occurred while saving product attachments.');
  540. }
  541. }
  542. public function processDuplicate()
  543. {
  544. if (Validate::isLoadedObject($product = new Product((int)Tools::getValue('id_product'))))
  545. {
  546. $id_product_old = $product->id;
  547. if (empty($product->price) && Shop::getContext() == Shop::CONTEXT_GROUP)
  548. {
  549. $shops = ShopGroup::getShopsFromGroup(Shop::getContextShopGroupID());
  550. foreach ($shops as $shop)
  551. if ($product->isAssociatedToShop($shop['id_shop']))
  552. {
  553. $product_price = new Product($id_product_old, false, null, $shop['id_shop']);
  554. $product->price = $product_price->price;
  555. }
  556. }
  557. unset($product->id);
  558. unset($product->id_product);
  559. $product->indexed = 0;
  560. $product->active = 0;
  561. if ($product->add()
  562. && Category::duplicateProductCategories($id_product_old, $product->id)
  563. && ($combination_images = Product::duplicateAttributes($id_product_old, $product->id)) !== false
  564. && GroupReduction::duplicateReduction($id_product_old, $product->id)
  565. && Product::duplicateAccessories($id_product_old, $product->id)
  566. && Product::duplicateFeatures($id_product_old, $product->id)
  567. && Product::duplicateSpecificPrices($id_product_old, $product->id)
  568. && Pack::duplicate($id_product_old, $product->id)
  569. && Product::duplicateCustomizationFields($id_product_old, $product->id)
  570. && Product::duplicateTags($id_product_old, $product->id)
  571. && Product::duplicateDownload($id_product_old, $product->id))
  572. {
  573. if ($product->hasAttributes())
  574. Product::updateDefaultAttribute($product->id);
  575. if (!Tools::getValue('noimage') && !Image::duplicateProductImages($id_product_old, $product->id, $combination_images))
  576. $this->errors[] = Tools::displayError('An error occurred while copying images.');
  577. else
  578. {
  579. Hook::exec('actionProductAdd', array('product' => $product));
  580. if (in_array($product->visibility, array('both', 'search')) && Configuration::get('PS_SEARCH_INDEXATION'))
  581. Search::indexation(false, $product->id);
  582. $this->redirect_after = self::$currentIndex.(Tools::getIsset('id_category') ? '&id_category='.(int)Tools::getValue('id_category') : '').'&conf=19&token='.$this->token;
  583. }
  584. }
  585. else
  586. $this->errors[] = Tools::displayError('An error occurred while creating an object.');
  587. }
  588. }
  589. public function processDelete()
  590. {
  591. if (Validate::isLoadedObject($object = $this->loadObject()) && isset($this->fieldImageSettings))
  592. {
  593. // check if request at least one object with noZeroObject
  594. if (isset($object->noZeroObject) && count($taxes = call_user_func(array($this->className, $object->noZeroObject))) <= 1)
  595. $this->errors[] = Tools::displayError('You need at least one object.').' <b>'.$this->table.'</b><br />'.Tools::displayError('You cannot delete all of the items.');
  596. else
  597. {
  598. /*
  599. * @since 1.5.0
  600. * It is NOT possible to delete a product if there are currently:
  601. * - physical stock for this product
  602. * - supply order(s) for this product
  603. */
  604. if (Configuration::get('PS_ADVANCED_STOCK_MANAGEMENT') && $object->advanced_stock_management)
  605. {
  606. $stock_manager = StockManagerFactory::getManager();
  607. $physical_quantity = $stock_manager->getProductPhysicalQuantities($object->id, 0);
  608. $real_quantity = $stock_manager->getProductRealQuantities($object->id, 0);
  609. if ($physical_quantity > 0 || $real_quantity > $physical_quantity)
  610. $this->errors[] = Tools::displayError('You cannot delete this product because there\'s physical stock left.');
  611. }
  612. if (!count($this->errors))
  613. {
  614. if ($object->delete())
  615. {
  616. $id_category = (int)Tools::getValue('id_category');
  617. $category_url = empty($id_category) ? '' : '&id_category='.(int)$id_category;
  618. PrestaShopLogger::addLog(sprintf($this->l('%s deletion', 'AdminTab', false, false), $this->className), 1, null, $this->className, (int)$object->id, true, (int)$this->context->employee->id);
  619. $this->redirect_after = self::$currentIndex.'&conf=1&token='.$this->token.$category_url;
  620. }
  621. else
  622. $this->errors[] = Tools::displayError('An error occurred during deletion.');
  623. }
  624. }
  625. }
  626. else
  627. $this->errors[] = Tools::displayError('An error occurred while deleting the object.').' <b>'.$this->table.'</b> '.Tools::displayError('(cannot load object)');
  628. }
  629. public function processImage()
  630. {
  631. $id_image = (int)Tools::getValue('id_image');
  632. $image = new Image((int)$id_image);
  633. if (Validate::isLoadedObject($image))
  634. {
  635. /* Update product image/legend */
  636. // @todo : move in processEditProductImage
  637. if (Tools::getIsset('editImage'))
  638. {
  639. if ($image->cover)
  640. $_POST['cover'] = 1;
  641. $_POST['id_image'] = $image->id;
  642. }
  643. /* Choose product cover image */
  644. elseif (Tools::getIsset('coverImage'))
  645. {
  646. Image::deleteCover($image->id_product);
  647. $image->cover = 1;
  648. if (!$image->update())
  649. $this->errors[] = Tools::displayError('You cannot change the product\'s cover image.');
  650. else
  651. {
  652. $productId = (int)Tools::getValue('id_product');
  653. @unlink(_PS_TMP_IMG_DIR_.'product_'.$productId.'.jpg');
  654. @unlink(_PS_TMP_IMG_DIR_.'product_mini_'.$productId.'_'.$this->context->shop->id.'.jpg');
  655. $this->redirect_after = self::$currentIndex.'&id_product='.$image->id_product.'&id_category='.(Tools::getIsset('id_category') ? '&id_category='.(int)Tools::getValue('id_category') : '').'&action=Images&addproduct'.'&token='.$this->token;
  656. }
  657. }
  658. /* Choose product image position */
  659. elseif (Tools::getIsset('imgPosition') && Tools::getIsset('imgDirection'))
  660. {
  661. $image->updatePosition(Tools::getValue('imgDirection'), Tools::getValue('imgPosition'));
  662. $this->redirect_after = self::$currentIndex.'&id_product='.$image->id_product.'&id_category='.(Tools::getIsset('id_category') ? '&id_category='.(int)Tools::getValue('id_category') : '').'&add'.$this->table.'&action=Images&token='.$this->token;
  663. }
  664. }
  665. else
  666. $this->errors[] = Tools::displayError('The image could not be found. ');
  667. }
  668. protected function processBulkDelete()
  669. {
  670. if ($this->tabAccess['delete'] === '1')
  671. {
  672. if (is_array($this->boxes) && !empty($this->boxes))
  673. {
  674. $object = new $this->className();
  675. if (isset($object->noZeroObject) &&
  676. // Check if all object will be deleted
  677. (count(call_user_func(array($this->className, $object->noZeroObject))) <= 1 || count($_POST[$this->table.'Box']) == count(call_user_func(array($this->className, $object->noZeroObject)))))
  678. $this->errors[] = Tools::displayError('You need at least one object.').' <b>'.$this->table.'</b><br />'.Tools::displayError('You cannot delete all of the items.');
  679. else
  680. {
  681. $success = 1;
  682. $products = Tools::getValue($this->table.'Box');
  683. if (is_array($products) && ($count = count($products)))
  684. {
  685. // Deleting products can be quite long on a cheap server. Let's say 1.5 seconds by product (I've seen it!).
  686. if (intval(ini_get('max_execution_time')) < round($count * 1.5))
  687. ini_set('max_execution_time', round($count * 1.5));
  688. if (Configuration::get('PS_ADVANCED_STOCK_MANAGEMENT'))
  689. $stock_manager = StockManagerFactory::getManager();
  690. foreach ($products as $id_product)
  691. {
  692. $product = new Product((int)$id_product);
  693. /*
  694. * @since 1.5.0
  695. * It is NOT possible to delete a product if there are currently:
  696. * - physical stock for this product
  697. * - supply order(s) for this product
  698. */
  699. if (Configuration::get('PS_ADVANCED_STOCK_MANAGEMENT') && $product->advanced_stock_management)
  700. {
  701. $physical_quantity = $stock_manager->getProductPhysicalQuantities($product->id, 0);
  702. $real_quantity = $stock_manager->getProductRealQuantities($product->id, 0);
  703. if ($physical_quantity > 0 || $real_quantity > $physical_quantity)
  704. $this->errors[] = sprintf(Tools::displayError('You cannot delete the product #%d because there is physical stock left.'), $product->id);
  705. }
  706. if (!count($this->errors))
  707. {
  708. if ($product->delete())
  709. PrestaShopLogger::addLog(sprintf($this->l('%s deletion', 'AdminTab', false, false), $this->className), 1, null, $this->className, (int)$product->id, true, (int)$this->context->employee->id);
  710. else
  711. $success = false;
  712. }
  713. else
  714. $success = 0;
  715. }
  716. }
  717. if ($success)
  718. {
  719. $id_category = (int)Tools::getValue('id_category');
  720. $category_url = empty($id_category) ? '' : '&id_category='.(int)$id_category;
  721. $this->redirect_after = self::$currentIndex.'&conf=2&token='.$this->token.$category_url;
  722. }
  723. else
  724. $this->errors[] = Tools::displayError('An error occurred while deleting this selection.');
  725. }
  726. }
  727. else
  728. $this->errors[] = Tools::displayError('You must select at least one element to delete.');
  729. }
  730. else
  731. $this->errors[] = Tools::displayError('You do not have permission to delete this.');
  732. }
  733. public function processProductAttribute()
  734. {
  735. // Don't process if the combination fields have not been submitted
  736. if (!Combination::isFeatureActive() || !Tools::getValue('attribute_combination_list'))
  737. return;
  738. if (Validate::isLoadedObject($product = $this->object))
  739. {
  740. if ($this->isProductFieldUpdated('attribute_price') && (!Tools::getIsset('attribute_price') || Tools::getIsset('attribute_price') == null))
  741. $this->errors[] = Tools::displayError('The price attribute is required.');
  742. if (!Tools::getIsset('attribute_combination_list') || Tools::isEmpty(Tools::getValue('attribute_combination_list')))
  743. $this->errors[] = Tools::displayError('You must add at least one attribute.');
  744. $array_checks = array(
  745. 'reference' => 'isReference',
  746. 'supplier_reference' => 'isReference',
  747. 'location' => 'isReference',
  748. 'ean13' => 'isEan13',
  749. 'upc' => 'isUpc',
  750. 'wholesale_price' => 'isPrice',
  751. 'price' => 'isPrice',
  752. 'ecotax' => 'isPrice',
  753. 'quantity' => 'isInt',
  754. 'weight' => 'isUnsignedFloat',
  755. 'unit_price_impact' => 'isPrice',
  756. 'default_on' => 'isBool',
  757. 'minimal_quantity' => 'isUnsignedInt',
  758. 'available_date' => 'isDateFormat'
  759. );
  760. foreach ($array_checks as $property => $check)
  761. if (Tools::getValue('attribute_'.$property) !== false && !call_user_func(array('Validate', $check), Tools::getValue('attribute_'.$property)))
  762. $this->errors[] = sprintf(Tools::displayError('Field %s is not valid'), $property);
  763. if (!count($this->errors))
  764. {
  765. if (!isset($_POST['attribute_wholesale_price'])) $_POST['attribute_wholesale_price'] = 0;
  766. if (!isset($_POST['attribute_price_impact'])) $_POST['attribute_price_impact'] = 0;
  767. if (!isset($_POST['attribute_weight_impact'])) $_POST['attribute_weight_impact'] = 0;
  768. if (!isset($_POST['attribute_ecotax'])) $_POST['attribute_ecotax'] = 0;
  769. if (Tools::getValue('attribute_default'))
  770. $product->deleteDefaultAttributes();
  771. // Change existing one
  772. if (($id_product_attribute = (int)Tools::getValue('id_product_attribute')) || ($id_product_attribute = $product->productAttributeExists(Tools::getValue('attribute_combination_list'), false, null, true, true)))
  773. {
  774. if ($this->tabAccess['edit'] === '1')
  775. {
  776. if ($this->isProductFieldUpdated('available_date_attribute') && (Tools::getValue('available_date_attribute') != '' &&!Validate::isDateFormat(Tools::getValue('available_date_attribute'))))
  777. $this->errors[] = Tools::displayError('Invalid date format.');
  778. else
  779. {
  780. $product->updateAttribute((int)$id_product_attribute,
  781. $this->isProductFieldUpdated('attribute_wholesale_price') ? Tools::getValue('attribute_wholesale_price') : null,
  782. $this->isProductFieldUpdated('attribute_price_impact') ? Tools::getValue('attribute_price') * Tools::getValue('attribute_price_impact') : null,
  783. $this->isProductFieldUpdated('attribute_weight_impact') ? Tools::getValue('attribute_weight') * Tools::getValue('attribute_weight_impact') : null,
  784. $this->isProductFieldUpdated('attribute_unit_impact') ? Tools::getValue('attribute_unity') * Tools::getValue('attribute_unit_impact') : null,
  785. $this->isProductFieldUpdated('attribute_ecotax') ? Tools::getValue('attribute_ecotax') : null,
  786. Tools::getValue('id_image_attr'),
  787. Tools::getValue('attribute_reference'),
  788. Tools::getValue('attribute_ean13'),
  789. $this->isProductFieldUpdated('attribute_default') ? Tools::getValue('attribute_default') : null,
  790. Tools::getValue('attribute_location'),
  791. Tools::getValue('attribute_upc'),
  792. $this->isProductFieldUpdated('attribute_minimal_quantity') ? Tools::getValue('attribute_minimal_quantity') : null,
  793. $this->isProductFieldUpdated('available_date_attribute') ? Tools::getValue('available_date_attribute') : null, false);
  794. StockAvailable::setProductDependsOnStock((int)$product->id, $product->depends_on_stock, null, (int)$id_product_attribute);
  795. StockAvailable::setProductOutOfStock((int)$product->id, $product->out_of_stock, null, (int)$id_product_attribute);
  796. }
  797. }
  798. else
  799. $this->errors[] = Tools::displayError('You do not have permission to add this.');
  800. }
  801. // Add new
  802. else
  803. {
  804. if ($this->tabAccess['add'] === '1')
  805. {
  806. if ($product->productAttributeExists(Tools::getValue('attribute_combination_list')))
  807. $this->errors[] = Tools::displayError('This combination already exists.');
  808. else
  809. {
  810. $id_product_attribute = $product->addCombinationEntity(
  811. Tools::getValue('attribute_wholesale_price'),
  812. Tools::getValue('attribute_price') * Tools::getValue('attribute_price_impact'),
  813. Tools::getValue('attribute_weight') * Tools::getValue('attribute_weight_impact'),
  814. Tools::getValue('attribute_unity') * Tools::getValue('attribute_unit_impact'),
  815. Tools::getValue('attribute_ecotax'),
  816. 0,
  817. Tools::getValue('id_image_attr'),
  818. Tools::getValue('attribute_reference'),
  819. null,
  820. Tools::getValue('attribute_ean13'),
  821. Tools::getValue('attribute_default'),
  822. Tools::getValue('attribute_location'),
  823. Tools::getValue('attribute_upc'),
  824. Tools::getValue('attribute_minimal_quantity'),
  825. Array(),
  826. Tools::getValue('available_date_attribute')
  827. );
  828. StockAvailable::setProductDependsOnStock((int)$product->id, $product->depends_on_stock, null, (int)$id_product_attribute);
  829. StockAvailable::setProductOutOfStock((int)$product->id, $product->out_of_stock, null, (int)$id_product_attribute);
  830. }
  831. }
  832. else
  833. $this->errors[] = Tools::displayError('You do not have permission to').'<hr>'.Tools::displayError('edit here.');
  834. }
  835. if (!count($this->errors))
  836. {
  837. $combination = new Combination((int)$id_product_attribute);
  838. $combination->setAttributes(Tools::getValue('attribute_combination_list'));
  839. // images could be deleted before
  840. $id_images = Tools::getValue('id_image_attr');
  841. if (!empty($id_images))
  842. $combination->setImages($id_images);
  843. $product->checkDefaultAttributes();
  844. if (Tools::getValue('attribute_default'))
  845. {
  846. Product::updateDefaultAttribute((int)$product->id);
  847. if(isset($id_product_attribute))
  848. $product->cache_default_attribute = (int)$id_product_attribute;
  849. if ($available_date = Tools::getValue('available_date_attribute'))
  850. $product->setAvailableDate($available_date);
  851. }
  852. }
  853. }
  854. }
  855. }
  856. public function processFeatures()
  857. {
  858. if (!Feature::isFeatureActive())
  859. return;
  860. if (Validate::isLoadedObject($product = new Product((int)Tools::getValue('id_product'))))
  861. {
  862. // delete all objects
  863. $product->deleteFeatures();
  864. // add new objects
  865. $languages = Language::getLanguages(false);
  866. foreach ($_POST as $key => $val)
  867. {
  868. if (preg_match('/^feature_([0-9]+)_value/i', $key, $match))
  869. {
  870. if ($val)
  871. $product->addFeaturesToDB($match[1], $val);
  872. else
  873. {
  874. if ($default_value = $this->checkFeatures($languages, $match[1]))
  875. {
  876. $id_value = $product->addFeaturesToDB($match[1], 0, 1);
  877. foreach ($languages as $language)
  878. {
  879. if ($cust = Tools::getValue('custom_'.$match[1].'_'.(int)$language['id_lang']))
  880. $product->addFeaturesCustomToDB($id_value, (int)$language['id_lang'], $cust);
  881. else
  882. $product->addFeaturesCustomToDB($id_value, (int)$language['id_lang'], $default_value);
  883. }
  884. }
  885. }
  886. }
  887. }
  888. }
  889. else
  890. $this->errors[] = Tools::displayError('A product must be created before adding features.');
  891. }
  892. /**
  893. * This function is never called at the moment (specific prices cannot be edited)
  894. */
  895. public function processPricesModification()
  896. {
  897. $id_specific_prices = Tools::getValue('spm_id_specific_price');
  898. $id_combinations = Tools::getValue('spm_id_product_attribute');
  899. $id_shops = Tools::getValue('spm_id_shop');
  900. $id_currencies = Tools::getValue('spm_id_currency');
  901. $id_countries = Tools::getValue('spm_id_country');
  902. $id_groups = Tools::getValue('spm_id_group');
  903. $id_customers = Tools::getValue('spm_id_customer');
  904. $prices = Tools::getValue('spm_price');
  905. $from_quantities = Tools::getValue('spm_from_quantity');
  906. $reductions = Tools::getValue('spm_reduction');
  907. $reduction_types = Tools::getValue('spm_reduction_type');
  908. $froms = Tools::getValue('spm_from');
  909. $tos = Tools::getValue('spm_to');
  910. foreach ($id_specific_prices as $key => $id_specific_price)
  911. if ($reduction_types[$key] == 'percentage' && ((float)$reductions[$key] <= 0 || (float)$reductions[$key] > 100))
  912. $this->errors[] = Tools::displayError('Submitted reduction value (0-100) is out-of-range');
  913. elseif ($this->_validateSpecificPrice($id_shops[$key], $id_currencies[$key], $id_countries[$key], $id_groups[$key], $id_customers[$key], $prices[$key], $from_quantities[$key], $reductions[$key], $reduction_types[$key], $froms[$key], $tos[$key], $id_combinations[$key]))
  914. {
  915. $specific_price = new SpecificPrice((int)($id_specific_price));
  916. $specific_price->id_shop = (int)$id_shops[$key];
  917. $specific_price->id_product_attribute = (int)$id_combinations[$key];
  918. $specific_price->id_currency = (int)($id_currencies[$key]);
  919. $specific_price->id_country = (int)($id_countries[$key]);
  920. $specific_price->id_group = (int)($id_groups[$key]);
  921. $specific_price->id_customer = (int)$id_customers[$key];
  922. $specific_price->price = (float)($prices[$key]);
  923. $specific_price->from_quantity = (int)($from_quantities[$key]);
  924. $specific_price->reduction = (float)($reduction_types[$key] == 'percentage' ? ($reductions[$key] / 100) : $reductions[$key]);
  925. $specific_price->reduction_type = !$reductions[$key] ? 'amount' : $reduction_types[$key];
  926. $specific_price->from = !$froms[$key] ? '0000-00-00 00:00:00' : $froms[$key];
  927. $specific_price->to = !$tos[$key] ? '0000-00-00 00:00:00' : $tos[$key];
  928. if (!$specific_price->update())
  929. $this->errors[] = Tools::displayError('An error occurred while updating the specific price.');
  930. }
  931. if (!count($this->errors))
  932. $this->redirect_after = self::$currentIndex.'&id_product='.(int)(Tools::getValue('id_product')).(Tools::getIsset('id_category') ? '&id_category='.(int)Tools::getValue('id_category') : '').'&update'.$this->table.'&action=Prices&token='.$this->token;
  933. }
  934. public function processPriceAddition()
  935. {
  936. // Check if a specific price has been submitted
  937. if (!Tools::getIsset('submitPriceAddition'))
  938. return;
  939. $id_product = Tools::getValue('id_product');
  940. $id_product_attribute = Tools::getValue('sp_id_product_attribute');
  941. $id_shop = Tools::getValue('sp_id_shop');
  942. $id_currency = Tools::getValue('sp_id_currency');
  943. $id_country = Tools::getValue('sp_id_country');
  944. $id_group = Tools::getValue('sp_id_group');
  945. $id_customer = Tools::getValue('sp_id_customer');
  946. $price = Tools::getValue('leave_bprice') ? '-1' : Tools::getValue('sp_price');
  947. $from_quantity = Tools::getValue('sp_from_quantity');
  948. $reduction = (float)(Tools::getValue('sp_reduction'));
  949. $reduction_type = !$reduction ? 'amount' : Tools::getValue('sp_reduction_type');
  950. $from = Tools::getValue('sp_from');
  951. if (!$from)
  952. $from = '0000-00-00 00:00:00';
  953. $to = Tools::getValue('sp_to');
  954. if (!$to)
  955. $to = '0000-00-00 00:00:00';
  956. if ($reduction_type == 'percentage' && ((float)$reduction <= 0 || (float)$reduction > 100))
  957. $this->errors[] = Tools::displayError('Submitted reduction value (0-100) is out-of-range');
  958. elseif ($this->_validateSpecificPrice($id_shop, $id_currency, $id_country, $id_group, $id_customer, $price, $from_quantity, $reduction, $reduction_type, $from, $to, $id_product_attribute))
  959. {
  960. $specificPrice = new SpecificPrice();
  961. $specificPrice->id_product = (int)$id_product;
  962. $specificPrice->id_product_attribute = (int)$id_product_attribute;
  963. $specificPrice->id_shop = (int)$id_shop;
  964. $specificPrice->id_currency = (int)($id_currency);
  965. $specificPrice->id_country = (int)($id_country);
  966. $specificPrice->id_group = (int)($id_group);
  967. $specificPrice->id_customer = (int)$id_customer;
  968. $specificPrice->price = (float)($price);
  969. $specificPrice->from_quantity = (int)($from_quantity);
  970. $specificPrice->reduction = (float)($reduction_type == 'percentage' ? $reduction / 100 : $reduction);
  971. $specificPrice->reduction_type = $reduction_type;
  972. $specificPrice->from = $from;
  973. $specificPrice->to = $to;
  974. if (!$specificPrice->add())
  975. $this->errors[] = Tools::displayError('An error occurred while updating the specific price.');
  976. }
  977. }
  978. public function ajaxProcessDeleteSpecificPrice()
  979. {
  980. if ($this->tabAccess['delete'] === '1')
  981. {
  982. $id_specific_price = (int)Tools::getValue('id_specific_price');
  983. if (!$id_specific_price || !Validate::isUnsignedId($id_specific_price))
  984. $error = Tools::displayError('The specific price ID is invalid.');
  985. else
  986. {
  987. $specificPrice = new SpecificPrice((int)$id_specific_price);
  988. if (!$specificPrice->delete())
  989. $error = Tools::displayError('An error occurred while attempting to delete the specific price.');
  990. }
  991. }
  992. else
  993. $error = Tools::displayError('You do not have permission to delete this.');
  994. if (isset($error))
  995. $json = array(
  996. 'status' => 'error',
  997. 'message'=> $error
  998. );
  999. else
  1000. $json = array(
  1001. 'status' => 'ok',
  1002. 'message'=> $this->_conf[1]
  1003. );
  1004. die(Tools::jsonEncode($json));
  1005. }
  1006. public function processSpecificPricePriorities()
  1007. {
  1008. if (!($obj = $this->loadObject()))
  1009. return;
  1010. if (!$priorities = Tools::getValue('specificPricePriority'))
  1011. $this->errors[] = Tools::displayError('Please specify priorities.');
  1012. elseif (Tools::isSubmit('specificPricePriorityToAll'))
  1013. {
  1014. if (!SpecificPrice::setPriorities($priorities))
  1015. $this->errors[] = Tools::displayError('An error occurred while updating priorities.');
  1016. else
  1017. $this->confirmations[] = $this->l('The price rule has successfully updated');
  1018. }
  1019. elseif (!SpecificPrice::setSpecificPriority((int)$obj->id, $priorities))
  1020. $this->errors[] = Tools::displayError('An error occurred while setting priorities.');
  1021. }
  1022. public function processCustomizationConfiguration()
  1023. {
  1024. $product = $this->object;
  1025. // Get the number of existing customization fields ($product->text_fields is the updated value, not the existing value)
  1026. $current_customization = $product->getCustomizationFieldIds();
  1027. $files_count = 0;
  1028. $text_count = 0;
  1029. if (is_array($current_customization))
  1030. {
  1031. foreach ($current_customization as $field)
  1032. {
  1033. if ($field['type'] == 1)
  1034. $text_count++;
  1035. else
  1036. $files_count++;
  1037. }
  1038. }
  1039. if (!$product->createLabels((int)$product->uploadable_files - $files_count, (int)$product->text_fields - $text_count))
  1040. $this->errors[] = Tools::displayError('An error occurred while creating customization fields.');
  1041. if (!count($this->errors) && !$product->updateLabels())
  1042. $this->errors[] = Tools::displayError('An error occurred while updating customization fields.');
  1043. $product->customizable = ($product->uploadable_files > 0 || $product->text_fields > 0) ? 1 : 0;
  1044. if (!count($this->errors) && !$product->update())
  1045. $this->errors[] = Tools::displayError('An error occurred while updating the custom configuration.');
  1046. }
  1047. public function processProductCustomization()
  1048. {
  1049. if (Validate::isLoadedObject($product = new Product((int)Tools::getValue('id_product'))))
  1050. {
  1051. foreach ($_POST as $field => $value)
  1052. if (strncmp($field, 'label_', 6) == 0 && !Validate::isLabel($value))
  1053. $this->errors[] = Tools::displayError('The label fields defined are invalid.');
  1054. if (empty($this->errors) && !$product->updateLabels())
  1055. $this->errors[] = Tools::displayError('An error occurred while updating customization fields.');
  1056. if (empty($this->errors))
  1057. $this->confirmations[] = $this->l('Update successful');
  1058. }
  1059. else
  1060. $this->errors[] = Tools::displayError('A product must be created before adding customization.');
  1061. }
  1062. /**
  1063. * Overrides parent for custom redirect link
  1064. */
  1065. public function processPosition()
  1066. {
  1067. if (!Validate::isLoadedObject($object = $this->loadObject()))
  1068. {
  1069. $this->errors[] = Tools::displayError('An error occurred while updating the status for an object.').
  1070. ' <b>'.$this->table.'</b> '.Tools::displayError('(cannot load object)');
  1071. }
  1072. elseif (!$object->updatePosition((int)Tools::getValue('way'), (int)Tools::getValue('position')))
  1073. $this->errors[] = Tools::displayError('Failed to update the position.');
  1074. else
  1075. {
  1076. $category = new Category((int)Tools::getValue('id_category'));
  1077. if (Validate::isLoadedObject($category))
  1078. Hook::exec('actionCategoryUpdate', array('category' => $category));
  1079. $this->redirect_after = self::$currentIndex.'&'.$this->table.'Orderby=position&'.$this->table.'Orderway=asc&action=Customization&conf=5'.(($id_category = (Tools::getIsset('id_category') ? (int)Tools::getValue('id_category') : '')) ? ('&id_category='.$id_category) : '').'&token='.Tools::getAdminTokenLite('AdminProducts');
  1080. }
  1081. }
  1082. public function initProcess()
  1083. {
  1084. if (Tools::isSubmit('submitAddproductAndStay') || Tools::isSubmit('submitAddproduct'))
  1085. {
  1086. $this->id_object = (int)Tools::getValue('id_product');
  1087. $this->object = new Product($this->id_object);
  1088. if ((bool)$this->object->is_virtual && (int)Tools::getValue('type_product') != 2)
  1089. {
  1090. if (!($id_product_download = ProductDownload::getIdFromIdProduct($this->id_object)))
  1091. $this->errors[] = Tools::displayError('Cannot retrieve file');
  1092. else
  1093. {
  1094. $product_download = new ProductDownload((int)$id_product_download);
  1095. if (!$product_download->deleteFile((int)$id_product_download))
  1096. $this->errors[] = Tools::displayError('Cannot delete file');
  1097. }
  1098. }
  1099. }
  1100. // Delete a product in the download folder
  1101. if (Tools::getValue('deleteVirtualProduct'))
  1102. {
  1103. if ($this->tabAccess['delete'] === '1')
  1104. $this->action = 'deleteVirtualProduct';
  1105. else
  1106. $this->errors[] = Tools::displayError('You do not have permission to delete this.');
  1107. }
  1108. // Product preview
  1109. elseif (Tools::isSubmit('submitAddProductAndPreview'))
  1110. {
  1111. $this->display = 'edit';
  1112. $this->action = 'save';
  1113. if (Tools::getValue('id_product'))
  1114. {
  1115. $this->id_object = Tools::getValue('id_product');
  1116. $this->object = new Product((int)Tools::getValue('id_product'));
  1117. }
  1118. }
  1119. elseif (Tools::isSubmit('submitAttachments'))
  1120. {
  1121. if ($this->tabAccess['edit'] === '1')
  1122. {
  1123. $this->action = 'attachments';
  1124. $this->tab_display = 'attachments';
  1125. }
  1126. else
  1127. $this->errors[] = Tools::displayError('You do not have permission to edit this.');
  1128. }
  1129. // Product duplication
  1130. elseif (Tools::getIsset('duplicate'.$this->table))
  1131. {
  1132. if ($this->tabAccess['add'] === '1')
  1133. $this->action = 'duplicate';
  1134. else
  1135. $this->errors[] = Tools::displayError('You do not have permission to add this.');
  1136. }
  1137. // Product images management
  1138. elseif (Tools::getValue('id_image') && Tools::getValue('ajax'))
  1139. {
  1140. if ($this->tabAccess['edit'] === '1')
  1141. $this->action = 'image';
  1142. else
  1143. $this->errors[] = Tools::displayError('You do not have permission to edit this.');
  1144. }
  1145. // Product attributes management
  1146. elseif (Tools::isSubmit('submitProductAttribute'))
  1147. {
  1148. if ($this->tabAccess['edit'] === '1')
  1149. $this->action = 'productAttribute';
  1150. else
  1151. $this->errors[] = Tools::displayError('You do not have permission to edit this.');
  1152. }
  1153. // Product features management
  1154. elseif (Tools::isSubmit('submitFeatures') || Tools::isSubmit('submitFeaturesAndStay'))
  1155. {
  1156. if ($this->tabAccess['edit'] === '1')
  1157. $this->action = 'features';
  1158. else
  1159. $this->errors[] = Tools::displayError('You do not have permission to edit this.');
  1160. }
  1161. // Product specific prices management NEVER USED
  1162. elseif (Tools::isSubmit('submitPricesModification'))
  1163. {
  1164. if ($this->tabAccess['add'] === '1')
  1165. $this->action = 'pricesModification';
  1166. else
  1167. $this->errors[] = Tools::displayError('You do not have permission to add this.');
  1168. }
  1169. elseif (Tools::isSubmit('deleteSpecificPrice'))
  1170. {
  1171. if ($this->tabAccess['delete'] === '1')
  1172. $this->action = 'deleteSpecificPrice';
  1173. else
  1174. $this->errors[] = Tools::displayError('You do not have permission to delete this.');
  1175. }
  1176. elseif (Tools::isSubmit('submitSpecificPricePriorities'))
  1177. {
  1178. if ($this->tabAccess['edit'] === '1')
  1179. {
  1180. $this->action = 'specificPricePriorities';
  1181. $this->tab_display = 'prices';
  1182. }
  1183. else
  1184. $this->errors[] = Tools::displayError('You do not have permission to edit this.');
  1185. }
  1186. // Customization management
  1187. elseif (Tools::isSubmit('submitCustomizationConfiguration'))
  1188. {
  1189. if ($this->tabAccess['edit'] === '1')
  1190. {
  1191. $this->action = 'customizationConfiguration';
  1192. $this->tab_display = 'customization';
  1193. $this->display = 'edit';
  1194. }
  1195. else
  1196. $this->errors[] = Tools::displayError('You do not have permission to edit this.');
  1197. }
  1198. elseif (Tools::isSubmit('submitProductCustomization'))
  1199. {
  1200. if ($this->tabAccess['edit'] === '1')
  1201. {
  1202. $this->action = 'productCustomization';
  1203. $this->tab_display = 'customization';
  1204. $this->display = 'edit';
  1205. }
  1206. else
  1207. $this->errors[] = Tools::displayError('You do not have permission to edit this.');
  1208. }
  1209. if (!$this->action)
  1210. parent::initProcess();
  1211. else
  1212. $this->id_object = (int)Tools::getValue($this->identifier);
  1213. if (isset($this->available_tabs[Tools::getValue('key_tab')]))
  1214. $this->tab_display = Tools::getValue('key_tab');
  1215. // Set tab to display if not decided already
  1216. if (!$this->tab_display && $this->action)
  1217. if (in_array($this->action, array_keys($this->available_tabs)))
  1218. $this->tab_display = $this->action;
  1219. // And if still not set, use default
  1220. if (!$this->tab_display)
  1221. {
  1222. if (in_array($this->default_tab, $this->available_tabs))
  1223. $this->tab_display = $this->default_tab;
  1224. else
  1225. $this->tab_display = key($this->available_tabs);
  1226. }
  1227. }
  1228. /**
  1229. * postProcess handle every checks before saving products information
  1230. *
  1231. * @return void
  1232. */
  1233. public function postProcess()
  1234. {
  1235. if (!$this->redirect_after)
  1236. parent::postProcess();
  1237. if ($this->display == 'edit' || $this->display == 'add')
  1238. {
  1239. $this->addJqueryUI(array(
  1240. 'ui.core',
  1241. 'ui.widget'
  1242. ));
  1243. $this->addjQueryPlugin(array(
  1244. 'autocomplete',
  1245. 'tablednd',
  1246. 'thickbox',
  1247. 'ajaxfileupload',
  1248. 'date',
  1249. 'tagify',
  1250. 'select2',
  1251. 'validate'
  1252. ));
  1253. $this->addJS(array(
  1254. _PS_JS_DIR_.'admin-products.js',
  1255. _PS_JS_DIR_.'attributesBack.js',
  1256. _PS_JS_DIR_.'price.js',
  1257. _PS_JS_DIR_.'tiny_mce/tiny_mce.js',
  1258. _PS_JS_DIR_.'tinymce.inc.js',
  1259. _PS_JS_DIR_.'admin-dnd.js',
  1260. _PS_JS_DIR_.'jquery/ui/jquery.ui.progressbar.min.js',
  1261. _PS_JS_DIR_.'vendor/spin.js',
  1262. _PS_JS_DIR_.'vendor/ladda.js'
  1263. ));
  1264. $this->addJS(_PS_JS_DIR_.'jquery/plugins/select2/select2_locale_'.$this->context->language->iso_code.'.js');
  1265. $this->addJS(_PS_JS_DIR_.'jquery/plugins/validate/localization/messages_'.$this->context->language->iso_code.'.js');
  1266. $this->addCSS(array(
  1267. _PS_JS_DIR_.'jquery/plugins/timepicker/jquery-ui-timepicker-addon.css'
  1268. ));
  1269. }
  1270. }
  1271. public function ajaxProcessDeleteProductAttribute()
  1272. {
  1273. if (!Combination::isFeatureActive())
  1274. return;
  1275. if ($this->tabAccess['delete'] === '1')
  1276. {
  1277. $id_product = (int)Tools::getValue('id_product');
  1278. $id_product_attribute = (int)Tools::getValue('id_product_attribute');
  1279. if ($id_product && Validate::isUnsignedId($id_product) && Validate::isLoadedObject($product = new Product($id_product)))
  1280. {
  1281. $product->deleteAttributeCombination((int)$id_product_attribute);
  1282. $product->checkDefaultAttributes();
  1283. if (!$product->hasAttributes())
  1284. {
  1285. $product->cache_default_attribute = 0;
  1286. $product->update();
  1287. }
  1288. else
  1289. Product::updateDefaultAttribute($id_product);
  1290. $json = array(
  1291. 'status' => 'ok',
  1292. 'message'=> $this->_conf[1]
  1293. );
  1294. }
  1295. else
  1296. $json = array(
  1297. 'status' => 'error',
  1298. 'message'=> $this->l('You cannot delete this attribute.')
  1299. );
  1300. }
  1301. else
  1302. $json = array(
  1303. 'status' => 'error',
  1304. 'message'=> $this->l('You do not have permission to delete this.')
  1305. );
  1306. die(Tools::jsonEncode($json));
  1307. }
  1308. public function ajaxProcessDefaultProductAttribute()
  1309. {
  1310. if ($this->tabAccess['edit'] === '1')
  1311. {
  1312. if (!Combination::isFeatureActive())
  1313. return;
  1314. if (Validate::isLoadedObject($product = new Product((int)Tools::getValue('id_product'))))
  1315. {
  1316. $product->deleteDefaultAttributes();
  1317. $product->setDefaultAttribute((int)Tools::getValue('id_product_attribute'));
  1318. $json = array(
  1319. 'status' => 'ok',
  1320. 'message'=> $this->_conf[4]
  1321. );
  1322. }
  1323. else
  1324. $json = array(
  1325. 'status' => 'error',
  1326. 'message'=> $this->l('You cannot make this the default attribute.')
  1327. );
  1328. die(Tools::jsonEncode($json));
  1329. }
  1330. }
  1331. public function ajaxProcessEditProductAttribute()
  1332. {
  1333. if ($this->tabAccess['edit'] === '1')
  1334. {
  1335. $id_product = (int)Tools::getValue('id_product');
  1336. $id_product_attribute = (int)Tools::getValue('id_product_attribute');
  1337. if ($id_product && Validate::isUnsignedId($id_product) && Validate::isLoadedObject($product = new Product((int)$id_product)))
  1338. {
  1339. $combinations = $product->getAttributeCombinationsById($id_product_attribute, $this->context->language->id);
  1340. foreach ($combinations as $key => $combination)
  1341. $combinations[$key]['attributes'][] = array($combination['group_name'], $combination['attribute_name'], $combination['id_attribute']);
  1342. die(Tools::jsonEncode($combinations));
  1343. }
  1344. }
  1345. }
  1346. public function ajaxPreProcess()
  1347. {
  1348. if (Tools::getIsset('update'.$this->table) && Tools::getIsset('id_'.$this->table))
  1349. {
  1350. $this->display = 'edit';
  1351. $this->action = Tools::getValue('action');
  1352. }
  1353. }
  1354. public function ajaxProcessUpdateProductImageShopAsso()
  1355. {
  1356. $id_product = Tools::getValue('id_product');
  1357. if (($id_image = Tools::getValue('id_image')) && ($id_shop = (int)Tools::getValue('id_shop')))
  1358. if (Tools::getValue('active') == 'true')
  1359. $res = Db::getInstance()->execute('INSERT INTO '._DB_PREFIX_.'image_shop (`id_image`, `id_shop`, `cover`) VALUES('.(int)$id_image.', '.(int)$id_shop.', \'0\')');
  1360. else
  1361. $res = Db::getInstance()->execute('DELETE FROM '._DB_PREFIX_.'image_shop WHERE `id_image` = '.(int)$id_image.' AND `id_shop` = '.(int)$id_shop);
  1362. // Clean covers in image table
  1363. $count_cover_image = Db::getInstance()->getValue('
  1364. SELECT COUNT(*) FROM '._DB_PREFIX_.'image i
  1365. INNER JOIN '._DB_PREFIX_.'image_shop ish ON (i.id_image = ish.id_image AND ish.id_shop = '.(int)$id_shop.')
  1366. WHERE i.cover = 1 AND `id_product` = '.(int)$id_product);
  1367. $id_image = Db::getInstance()->getValue('
  1368. SELECT i.`id_image` FROM '._DB_PREFIX_.'image i
  1369. INNER JOIN '._DB_PREFIX_.'image_shop ish ON (i.id_image = ish.id_image AND ish.id_shop = '.(int)$id_shop.')
  1370. WHERE `id_product` = '.(int)$id_product);
  1371. if ($count_cover_image < 1)
  1372. Db::getInstance()->execute('UPDATE '._DB_PREFIX_.'image i SET i.cover = 1 WHERE i.id_image = '.(int)$id_image.' AND i.`id_product` = '.(int)$id_product.' LIMIT 1');
  1373. if ($count_cover_image > 1)
  1374. Db::getInstance()->execute('UPDATE '._DB_PREFIX_.'image i SET i.cover = 0 WHERE i.id_image <> '.(int)$id_image.' AND i.`id_product` = '.(int)$id_product);
  1375. // Clean covers in image_shop table
  1376. $count_cover_image_shop = Db::getInstance()->getValue('
  1377. SELECT COUNT(*)
  1378. FROM '._DB_PREFIX_.'image_shop ish
  1379. INNER JOIN '._DB_PREFIX_.'image i ON (i.id_image = ish.id_image AND i.`id_product` = '.(int)$id_product.')
  1380. WHERE ish.id_shop = '.(int)$id_shop.' AND ish.cover = 1');
  1381. if ($count_cover_image_shop < 1)
  1382. Db::getInstance()->execute('UPDATE '._DB_PREFIX_.'image_shop ish SET ish.cover = 1 WHERE ish.id_image = '.(int)$id_image.' AND ish.id_shop = '.(int)$id_shop.' LIMIT 1');
  1383. if ($count_cover_image_shop > 1)
  1384. Db::getInstance()->execute('UPDATE '._DB_PREFIX_.'image_shop ish SET ish.cover = 0 WHERE ish.id_image <> '.(int)$id_image.' AND ish.cover = 1 AND ish.id_shop = '.(int)$id_shop.' LIMIT '.intval($count_cover_image_shop - 1));
  1385. if ($res)
  1386. $this->jsonConfirmation($this->_conf[27]);
  1387. else
  1388. $this->jsonError(Tools::displayError('An error occurred while attempting to associate this image with your shop. '));
  1389. }
  1390. public function ajaxProcessUpdateImagePosition()
  1391. {
  1392. $res = false;
  1393. if ($json = Tools::getValue('json'))
  1394. {
  1395. $res = true;
  1396. $json = stripslashes($json);
  1397. $images = Tools::jsonDecode($json, true);
  1398. foreach ($images as $id => $position)
  1399. {
  1400. $img = new Image((int)$id);
  1401. $img->position = (int)$position;
  1402. $res &= $img->update();
  1403. }
  1404. }
  1405. if ($res)
  1406. $this->jsonConfirmation($this->_conf[25]);
  1407. else
  1408. $this->jsonError(Tools::displayError('An error occurred while attempting to move this picture.'));
  1409. }
  1410. public function ajaxProcessUpdateCover()
  1411. {
  1412. Image::deleteCover((int)Tools::getValue('id_product'));
  1413. $img = new Image((int)Tools::getValue('id_image'));
  1414. $img->cover = 1;
  1415. @unlink(_PS_TMP_IMG_DIR_.'product_'.(int)$img->id_product.'.jpg');
  1416. @unlink(_PS_TMP_IMG_DIR_.'product_mini_'.(int)$img->id_product.'_'.$this->context->shop->id.'.jpg');
  1417. if ($img->update())
  1418. $this->jsonConfirmation($this->_conf[26]);
  1419. else
  1420. $this->jsonError(Tools::displayError('An error occurred while attempting to move this picture.'));
  1421. }
  1422. public function ajaxProcessDeleteProductImage()
  1423. {
  1424. $this->display = 'content';
  1425. $res = true;
  1426. /* Delete product image */
  1427. $image = new Image((int)Tools::getValue('id_image'));
  1428. $this->content['id'] = $image->id;
  1429. $res &= $image->delete();
  1430. // if deleted image was the cover, change it to the first one
  1431. if (!Image::getCover($image->id_product))
  1432. {
  1433. $res &= Db::getInstance()->execute('
  1434. UPDATE `'._DB_PREFIX_.'image_shop` image_shop, '._DB_PREFIX_.'image i
  1435. SET image_shop.`cover` = 1,
  1436. i.cover = 1
  1437. WHERE image_shop.`id_image` = (SELECT id_image FROM
  1438. (SELECT image_shop.id_image
  1439. FROM '._DB_PREFIX_.'image i'.
  1440. Shop::addSqlAssociation('image', 'i').'
  1441. WHERE i.id_product ='.(int)$image->id_product.' LIMIT 1
  1442. ) tmpImage)
  1443. AND id_shop='.(int)$this->context->shop->id.'
  1444. AND i.id_image = image_shop.id_image
  1445. ');
  1446. }
  1447. if (file_exists(_PS_TMP_IMG_DIR_.'product_'.$image->id_product.'.jpg'))
  1448. $res &= @unlink(_PS_TMP_IMG_DIR_.'product_'.$image->id_product.'.jpg');
  1449. if (file_exists(_PS_TMP_IMG_DIR_.'product_mini_'.$image->id_product.'_'.$this->context->shop->id.'.jpg'))
  1450. $res &= @unlink(_PS_TMP_IMG_DIR_.'product_mini_'.$image->id_product.'_'.$this->context->shop->id.'.jpg');
  1451. if ($res)
  1452. $this->jsonConfirmation($this->_conf[7]);
  1453. else
  1454. $this->jsonError(Tools::displayError('An error occurred while attempting to delete the product image.'));
  1455. }
  1456. protected function _validateSpecificPrice($id_shop, $id_currency, $id_country, $id_group, $id_customer, $price, $from_quantity, $reduction, $reduction_type, $from, $to, $id_combination = 0)
  1457. {
  1458. if (!Validate::isUnsignedId($id_shop) || !Validate::isUnsignedId($id_currency) || !Validate::isUnsignedId($id_country) || !Validate::isUnsignedId($id_group) || !Validate::isUnsignedId($id_customer))
  1459. $this->errors[] = Tools::displayError('Wrong IDs');
  1460. elseif ((!isset($price) && !isset($reduction)) || (isset($price) && !Validate::isNegativePrice($price)) || (isset($reduction) && !Validate::isPrice($reduction)))
  1461. $this->errors[] = Tools::displayError('Invalid price/discount amount');
  1462. elseif (!Validate::isUnsignedInt($from_quantity))
  1463. $this->errors[] = Tools::displayError('Invalid quantity');
  1464. elseif ($reduction && !Validate::isReductionType($reduction_type))
  1465. $this->errors[] = Tools::displayError('Please select a discount type (amount or percentage).');
  1466. elseif ($from && $to && (!Validate::isDateFormat($from) || !Validate::isDateFormat($to)))
  1467. $this->errors[] = Tools::displayError('The from/to date is invalid.');
  1468. elseif (SpecificPrice::exists((int)$this->object->id, $id_combination, $id_shop, $id_group, $id_country, $id_currency, $id_customer, $from_quantity, $from, $to, false))
  1469. $this->errors[] = Tools::displayError('A specific price already exists for these parameters.');
  1470. else
  1471. return true;
  1472. return false;
  1473. }
  1474. /* Checking customs feature */
  1475. protected function checkFeatures($languages, $feature_id)
  1476. {
  1477. $rules = call_user_func(array('FeatureValue', 'getValidationRules'), 'FeatureValue');
  1478. $feature = Feature::getFeature((int)Configuration::get('PS_LANG_DEFAULT'), $feature_id);
  1479. $val = 0;
  1480. foreach ($languages as $language)
  1481. if ($val = Tools::getValue('custom_'.$feature_id.'_'.$language['id_lang']))
  1482. {
  1483. $current_language = new Language($language['id_lang']);
  1484. if (Tools::strlen($val) > $rules['sizeLang']['value'])
  1485. $this->errors[] = sprintf(
  1486. Tools::displayError('The name for feature %1$s is too long in %2$s.'),
  1487. ' <b>'.$feature['name'].'</b>',
  1488. $current_language->name
  1489. );
  1490. elseif (!call_user_func(array('Validate', $rules['validateLang']['value']), $val))
  1491. $this->errors[] = sprintf(
  1492. Tools::displayError('A valid name required for feature. %1$s in %2$s.'),
  1493. ' <b>'.$feature['name'].'</b>',
  1494. $current_language->name
  1495. );
  1496. if (count($this->errors))
  1497. return 0;
  1498. // Getting default language
  1499. if ($language['id_lang'] == Configuration::get('PS_LANG_DEFAULT'))
  1500. return $val;
  1501. }
  1502. return 0;
  1503. }
  1504. /**
  1505. * Add or update a product image
  1506. *
  1507. * @param object $product Product object to add image
  1508. */
  1509. public function addProductImage($product, $method = 'auto')
  1510. {
  1511. /* Updating an existing product image */
  1512. if ($id_image = (int)Tools::getValue('id_image'))
  1513. {
  1514. $image = new Image((int)$id_image);
  1515. if (!Validate::isLoadedObject($image))
  1516. $this->errors[] = Tools::displayError('An error occurred while loading the object image.');
  1517. else
  1518. {
  1519. if (($cover = Tools::getValue('cover')) == 1)
  1520. Image::deleteCover($product->id);
  1521. $image->cover = $cover;
  1522. $this->validateRules('Image');
  1523. $this->copyFromPost($image, 'image');
  1524. if (count($this->errors) || !$image->update())
  1525. $this->errors[] = Tools::displayError('An error occurred while updating the image.');
  1526. elseif (isset($_FILES['image_product']['tmp_name']) && $_FILES['image_product']['tmp_name'] != null)
  1527. $this->copyImage($product->id, $image->id, $method);
  1528. }
  1529. }
  1530. if (isset($image) && Validate::isLoadedObject($image) && !file_exists(_PS_PROD_IMG_DIR_.$image->getExistingImgPath().'.'.$image->image_format))
  1531. $image->delete();
  1532. if (count($this->errors))
  1533. return false;
  1534. @unlink(_PS_TMP_IMG_DIR_.'product_'.$product->id.'.jpg');
  1535. @unlink(_PS_TMP_IMG_DIR_.'product_mini_'.$product->id.'_'.$this->context->shop->id.'.jpg');
  1536. return ((isset($id_image) && is_int($id_image) && $id_image) ? $id_image : false);
  1537. }
  1538. /**
  1539. * Copy a product image
  1540. *
  1541. * @param integer $id_product Product Id for product image filename
  1542. * @param integer $id_image Image Id for product image filename
  1543. */
  1544. public function copyImage($id_product, $id_image, $method = 'auto')
  1545. {
  1546. if (!isset($_FILES['image_product']['tmp_name']))
  1547. return false;
  1548. if ($error = ImageManager::validateUpload($_FILES['image_product']))
  1549. $this->errors[] = $error;
  1550. else
  1551. {
  1552. $image = new Image($id_image);
  1553. if (!$new_path = $image->getPathForCreation())
  1554. $this->errors[] = Tools::displayError('An error occurred while attempting to create a new folder.');
  1555. if (!($tmpName = tempnam(_PS_TMP_IMG_DIR_, 'PS')) || !move_uploaded_file($_FILES['image_product']['tmp_name'], $tmpName))
  1556. $this->errors[] = Tools::displayError('An error occurred during the image upload process.');
  1557. elseif (!ImageManager::resize($tmpName, $new_path.'.'.$image->image_format))
  1558. $this->errors[] = Tools::displayError('An error occurred while copying the image.');
  1559. elseif ($method == 'auto')
  1560. {
  1561. $imagesTypes = ImageType::getImagesTypes('products');
  1562. foreach ($imagesTypes as $k => $image_type)
  1563. {
  1564. if (!ImageManager::resize($tmpName, $new_path.'-'.stripslashes($image_type['name']).'.'.$image->image_format, $image_type['width'], $image_type['height'], $image->image_format))
  1565. $this->errors[] = Tools::displayError('An error occurred while copying this image:').' '.stripslashes($image_type['name']);
  1566. }
  1567. }
  1568. @unlink($tmpName);
  1569. Hook::exec('actionWatermark', array('id_image' => $id_image, 'id_product' => $id_product));
  1570. }
  1571. }
  1572. protected function updateAssoShop($id_object)
  1573. {
  1574. //override AdminController::updateAssoShop() specifically for products because shop association is set with the context in ObjectModel
  1575. return;
  1576. }
  1577. public function processAdd()
  1578. {
  1579. $this->checkProduct();
  1580. if (!empty($this->errors))
  1581. {
  1582. $this->display = 'add';
  1583. return false;
  1584. }
  1585. $this->object = new $this->className();
  1586. $this->_removeTaxFromEcotax();
  1587. $this->copyFromPost($this->object, $this->table);
  1588. if ($this->object->add())
  1589. {
  1590. PrestaShopLogger::addLog(sprintf($this->l('%s addition', 'AdminTab', false, false), $this->className), 1, null, $this->className, (int)$this->object->id, true, (int)$this->context->employee->id);
  1591. $this->addCarriers($this->object);
  1592. $this->updateAccessories($this->object);
  1593. $this->updatePackItems($this->object);
  1594. $this->updateDownloadProduct($this->object);
  1595. if (Configuration::get('PS_FORCE_ASM_NEW_PRODUCT') && Configuration::get('PS_ADVANCED_STOCK_MANAGEMENT'))
  1596. {
  1597. $this->object->advanced_stock_management = 1;
  1598. StockAvailable::setProductDependsOnStock($this->object->id, true, (int)$this->context->shop->id, 0);
  1599. $this->object->save();
  1600. }
  1601. if (empty($this->errors))
  1602. {
  1603. $languages = Language::getLanguages(false);
  1604. if ($this->isProductFieldUpdated('category_box') && !$this->object->updateCategories(Tools::getValue('categoryBox')))
  1605. $this->errors[] = Tools::displayError('An error occurred while linking the object.').' <b>'.$this->table.'</b> '.Tools::displayError('To categories');
  1606. elseif (!$this->updateTags($languages, $this->object))
  1607. $this->errors[] = Tools::displayError('An error occurred while adding tags.');
  1608. else
  1609. {
  1610. Hook::exec('actionProductAdd', array('product' => $this->object));
  1611. if (in_array($this->object->visibility, array('both', 'search')) && Configuration::get('PS_SEARCH_INDEXATION'))
  1612. Search::indexation(false, $this->object->id);
  1613. }
  1614. if (Configuration::get('PS_DEFAULT_WAREHOUSE_NEW_PRODUCT') != 0 && Configuration::get('PS_ADVANCED_STOCK_MANAGEMENT'))
  1615. {
  1616. $warehouse_location_entity = new WarehouseProductLocation();
  1617. $warehouse_location_entity->id_product = $this->object->id;
  1618. $warehouse_location_entity->id_product_attribute = 0;
  1619. $warehouse_location_entity->id_warehouse = Configuration::get('PS_DEFAULT_WAREHOUSE_NEW_PRODUCT');
  1620. $warehouse_location_entity->location = pSQL('');
  1621. $warehouse_location_entity->save();
  1622. }
  1623. // Save and preview
  1624. if (Tools::isSubmit('submitAddProductAndPreview'))
  1625. $this->redirect_after = $this->getPreviewUrl($this->object);
  1626. // Save and stay on same form
  1627. if ($this->display == 'edit')
  1628. $this->redirect_after = self::$currentIndex.'&id_product='.(int)$this->object->id
  1629. .(Tools::getIsset('id_category') ? '&id_category='.(int)Tools::getValue('id_category') : '')
  1630. .'&updateproduct&conf=3&key_tab='.Tools::safeOutput(Tools::getValue('key_tab')).'&token='.$this->token;
  1631. else
  1632. // Default behavior (save and back)
  1633. $this->redirect_after = self::$currentIndex
  1634. .(Tools::getIsset('id_category') ? '&id_category='.(int)Tools::getValue('id_category') : '')
  1635. .'&conf=3&token='.$this->token;
  1636. }
  1637. else
  1638. {
  1639. $this->object->delete();
  1640. // if errors : stay on edit page
  1641. $this->display = 'edit';
  1642. }
  1643. }
  1644. else
  1645. $this->errors[] = Tools::displayError('An error occurred while creating an object.').' <b>'.$this->table.'</b>';
  1646. return $this->object;
  1647. }
  1648. protected function isTabSubmitted($tab_name)
  1649. {
  1650. if (!is_array($this->submitted_tabs))
  1651. $this->submitted_tabs = Tools::getValue('submitted_tabs');
  1652. if (is_array($this->submitted_tabs) && in_array($tab_name, $this->submitted_tabs))
  1653. return true;
  1654. return false;
  1655. }
  1656. public function processStatus()
  1657. {
  1658. $this->loadObject(true);
  1659. if (!Validate::isLoadedObject($this->object))
  1660. return false;
  1661. if (($error = $this->object->validateFields(false, true)) !== true)
  1662. $this->errors[] = $error;
  1663. if (($error = $this->object->validateFieldsLang(false, true)) !== true)
  1664. $this->errors[] = $error;
  1665. if (count($this->errors))
  1666. return false;
  1667. $res = parent::processStatus();
  1668. return $res;
  1669. }
  1670. public function processUpdate()
  1671. {
  1672. $existing_product = $this->object;
  1673. $this->checkProduct();
  1674. if (!empty($this->errors))
  1675. {
  1676. $this->display = 'edit';
  1677. return false;
  1678. }
  1679. $id = (int)Tools::getValue('id_'.$this->table);
  1680. /* Update an existing product */
  1681. if (isset($id) && !empty($id))
  1682. {
  1683. $object = new $this->className((int)$id);
  1684. $this->object = $object;
  1685. if (Validate::isLoadedObject($object))
  1686. {
  1687. $this->_removeTaxFromEcotax();
  1688. $product_type_before = $object->getType();
  1689. $this->copyFromPost($object, $this->table);
  1690. $object->indexed = 0;
  1691. if (Shop::isFeatureActive() && Shop::getContext() != Shop::CONTEXT_SHOP)
  1692. $object->setFieldsToUpdate((array)Tools::getValue('multishop_check'));
  1693. // Duplicate combinations if not associated to shop
  1694. if ($this->context->shop->getContext() == Shop::CONTEXT_SHOP && !$object->isAssociatedToShop())
  1695. {
  1696. $is_associated_to_shop = false;
  1697. $combinations = Product::getProductAttributesIds($object->id);
  1698. if ($combinations)
  1699. {
  1700. foreach ($combinations as $id_combination)
  1701. {
  1702. $combination = new Combination((int)$id_combination['id_product_attribute']);
  1703. $default_combination = new Combination((int)$id_combination['id_product_attribute'], null, (int)$this->object->id_shop_default);
  1704. $def = ObjectModel::getDefinition($default_combination);
  1705. foreach ($def['fields'] as $field_name => $row)
  1706. $combination->$field_name = ObjectModel::formatValue($default_combination->$field_name, $def['fields'][$field_name]['type']);
  1707. $combination->save();
  1708. }
  1709. }
  1710. }
  1711. else
  1712. $is_associated_to_shop = true;
  1713. if ($object->update())
  1714. {
  1715. // If the product doesn't exist in the current shop but exists in another shop
  1716. if (Shop::getContext() == Shop::CONTEXT_SHOP && !$existing_product->isAssociatedToShop($this->context->shop->id))
  1717. {
  1718. $out_of_stock = StockAvailable::outOfStock($existing_product->id, $existing_product->id_shop_default);
  1719. $depends_on_stock = StockAvailable::dependsOnStock($existing_product->id, $existing_product->id_shop_default);
  1720. StockAvailable::setProductOutOfStock((int)$this->object->id, $out_of_stock, $this->context->shop->id);
  1721. StockAvailable::setProductDependsOnStock((int)$this->object->id, $depends_on_stock, $this->context->shop->id);
  1722. }
  1723. PrestaShopLogger::addLog(sprintf($this->l('%s edition', 'AdminTab', false, false), $this->className), 1, null, $this->className, (int)$this->object->id, true, (int)$this->context->employee->id);
  1724. if (in_array($this->context->shop->getContext(), array(Shop::CONTEXT_SHOP, Shop::CONTEXT_ALL)))
  1725. {
  1726. if ($this->isTabSubmitted('Shipping'))
  1727. $this->addCarriers();
  1728. if ($this->isTabSubmitted('Associations'))
  1729. $this->updateAccessories($object);
  1730. if ($this->isTabSubmitted('Suppliers'))
  1731. $this->processSuppliers();
  1732. if ($this->isTabSubmitted('Features'))
  1733. $this->processFeatures();
  1734. if ($this->isTabSubmitted('Combinations'))
  1735. $this->processProductAttribute();
  1736. if ($this->isTabSubmitted('Prices'))
  1737. {
  1738. $this->processPriceAddition();
  1739. $this->processSpecificPricePriorities();
  1740. }
  1741. if ($this->isTabSubmitted('Customization'))
  1742. $this->processCustomizationConfiguration();
  1743. if ($this->isTabSubmitted('Attachments'))
  1744. $this->processAttachments();
  1745. $this->updatePackItems($object);
  1746. // Disallow avanced stock management if the product become a pack
  1747. if ($product_type_before == Product::PTYPE_SIMPLE && $object->getType() == Product::PTYPE_PACK)
  1748. StockAvailable::setProductDependsOnStock((int)$object->id, false);
  1749. $this->updateDownloadProduct($object, 1);
  1750. $this->updateTags(Language::getLanguages(false), $object);
  1751. if ($this->isProductFieldUpdated('category_box') && !$object->updateCategories(Tools::getValue('categoryBox')))
  1752. $this->errors[] = Tools::displayError('An error occurred while linking the object.').' <b>'.$this->table.'</b> '.Tools::displayError('To categories');
  1753. }
  1754. if ($this->isTabSubmitted('Warehouses'))
  1755. $this->processWarehouses();
  1756. if (empty($this->errors))
  1757. {
  1758. if (in_array($object->visibility, array('both', 'search')) && Configuration::get('PS_SEARCH_INDEXATION'))
  1759. Search::indexation(false, $object->id);
  1760. // Save and preview
  1761. if (Tools::isSubmit('submitAddProductAndPreview'))
  1762. $this->redirect_after = $this->getPreviewUrl($object);
  1763. else
  1764. {
  1765. // Save and stay on same form
  1766. if ($this->display == 'edit')
  1767. {
  1768. $this->confirmations[] = $this->l('Update successful');
  1769. $this->redirect_after = self::$currentIndex.'&id_product='.(int)$this->object->id
  1770. .(Tools::getIsset('id_category') ? '&id_category='.(int)Tools::getValue('id_category') : '')
  1771. .'&updateproduct&conf=4&key_tab='.Tools::safeOutput(Tools::getValue('key_tab')).'&token='.$this->token;
  1772. }
  1773. else
  1774. // Default behavior (save and back)
  1775. $this->redirect_after = self::$currentIndex.(Tools::getIsset('id_category') ? '&id_category='.(int)Tools::getValue('id_category') : '').'&conf=4&token='.$this->token;
  1776. }
  1777. }
  1778. // if errors : stay on edit page
  1779. else
  1780. $this->display = 'edit';
  1781. }
  1782. else
  1783. {
  1784. if (!$is_associated_to_shop && $combinations)
  1785. foreach ($combinations as $id_combination)
  1786. {
  1787. $combination = new Combination((int)$id_combination['id_product_attribute']);
  1788. $combination->delete();
  1789. }
  1790. $this->errors[] = Tools::displayError('An error occurred while updating an object.').' <b>'.$this->table.'</b> ('.Db::getInstance()->getMsgError().')';
  1791. }
  1792. }
  1793. else
  1794. $this->errors[] = Tools::displayError('An error occurred while updating an object.').' <b>'.$this->table.'</b> ('.Tools::displayError('The object cannot be loaded. ').')';
  1795. return $object;
  1796. }
  1797. }
  1798. /**
  1799. * Check that a saved product is valid
  1800. */
  1801. public function checkProduct()
  1802. {
  1803. $className = 'Product';
  1804. // @todo : the call_user_func seems to contains only statics values (className = 'Product')
  1805. $rules = call_user_func(array($this->className, 'getValidationRules'), $this->className);
  1806. $default_language = new Language((int)Configuration::get('PS_LANG_DEFAULT'));
  1807. $languages = Language::getLanguages(false);
  1808. // Check required fields
  1809. foreach ($rules['required'] as $field)
  1810. {
  1811. if (!$this->isProductFieldUpdated($field))
  1812. continue;
  1813. if (($value = Tools::getValue($field)) == false && $value != '0')
  1814. {
  1815. if (Tools::getValue('id_'.$this->table) && $field == 'passwd')
  1816. continue;
  1817. $this->errors[] = sprintf(
  1818. Tools::displayError('The %s field is required.'),
  1819. call_user_func(array($className, 'displayFieldName'), $field, $className)
  1820. );
  1821. }
  1822. }
  1823. // Check multilingual required fields
  1824. foreach ($rules['requiredLang'] as $fieldLang)
  1825. if ($this->isProductFieldUpdated($fieldLang, $default_language->id) && !Tools::getValue($fieldLang.'_'.$default_language->id))
  1826. $this->errors[] = sprintf(
  1827. Tools::displayError('This %1$s field is required at least in %2$s'),
  1828. call_user_func(array($className, 'displayFieldName'), $fieldLang, $className),
  1829. $default_language->name
  1830. );
  1831. // Check fields sizes
  1832. foreach ($rules['size'] as $field => $maxLength)
  1833. if ($this->isProductFieldUpdated($field) && ($value = Tools::getValue($field)) && Tools::strlen($value) > $maxLength)
  1834. $this->errors[] = sprintf(
  1835. Tools::displayError('The %1$s field is too long (%2$d chars max).'),
  1836. call_user_func(array($className, 'displayFieldName'), $field, $className),
  1837. $maxLength
  1838. );
  1839. if (Tools::getIsset('description_short') && $this->isProductFieldUpdated('description_short'))
  1840. {
  1841. $saveShort = Tools::getValue('description_short');
  1842. $_POST['description_short'] = strip_tags(Tools::getValue('description_short'));
  1843. }
  1844. // Check description short size without html
  1845. $limit = (int)Configuration::get('PS_PRODUCT_SHORT_DESC_LIMIT');
  1846. if ($limit <= 0) $limit = 400;
  1847. foreach ($languages as $language)
  1848. if ($this->isProductFieldUpdated('description_short', $language['id_lang']) && ($value = Tools::getValue('description_short_'.$language['id_lang'])))
  1849. if (Tools::strlen(strip_tags($value)) > $limit)
  1850. $this->errors[] = sprintf(
  1851. Tools::displayError('This %1$s field (%2$s) is too long: %3$d chars max (current count %4$d).'),
  1852. call_user_func(array($className, 'displayFieldName'), 'description_short'),
  1853. $language['name'],
  1854. $limit,
  1855. Tools::strlen(strip_tags($value))
  1856. );
  1857. // Check multilingual fields sizes
  1858. foreach ($rules['sizeLang'] as $fieldLang => $maxLength)
  1859. foreach ($languages as $language)
  1860. {
  1861. $value = Tools::getValue($fieldLang.'_'.$language['id_lang']);
  1862. if ($value && Tools::strlen($value) > $maxLength)
  1863. $this->errors[] = sprintf(
  1864. Tools::displayError('The %1$s field is too long (%2$d chars max).'),
  1865. call_user_func(array($className, 'displayFieldName'), $fieldLang, $className),
  1866. $maxLength
  1867. );
  1868. }
  1869. if ($this->isProductFieldUpdated('description_short') && isset($_POST['description_short']))
  1870. $_POST['description_short'] = $saveShort;
  1871. // Check fields validity
  1872. foreach ($rules['validate'] as $field => $function)
  1873. if ($this->isProductFieldUpdated($field) && ($value = Tools::getValue($field)))
  1874. {
  1875. $res = true;
  1876. if (Tools::strtolower($function) == 'iscleanhtml')
  1877. {
  1878. if (!Validate::$function($value, (int)Configuration::get('PS_ALLOW_HTML_IFRAME')))
  1879. $res = false;
  1880. }
  1881. else
  1882. if (!Validate::$function($value))
  1883. $res = false;
  1884. if (!$res)
  1885. $this->errors[] = sprintf(
  1886. Tools::displayError('The %s field is invalid.'),
  1887. call_user_func(array($className, 'displayFieldName'), $field, $className)
  1888. );
  1889. }
  1890. // Check multilingual fields validity
  1891. foreach ($rules['validateLang'] as $fieldLang => $function)
  1892. foreach ($languages as $language)
  1893. if ($this->isProductFieldUpdated($fieldLang, $language['id_lang']) && ($value = Tools::getValue($fieldLang.'_'.$language['id_lang'])))
  1894. if (!Validate::$function($value, (int)Configuration::get('PS_ALLOW_HTML_IFRAME')))
  1895. $this->errors[] = sprintf(
  1896. Tools::displayError('The %1$s field (%2$s) is invalid.'),
  1897. call_user_func(array($className, 'displayFieldName'), $fieldLang, $className),
  1898. $language['name']
  1899. );
  1900. // Categories
  1901. if ($this->isProductFieldUpdated('id_category_default') && (!Tools::isSubmit('categoryBox') || !count(Tools::getValue('categoryBox'))))
  1902. $this->errors[] = $this->l('Products must be in at least one category.');
  1903. if ($this->isProductFieldUpdated('id_category_default') && (!is_array(Tools::getValue('categoryBox')) || !in_array(Tools::getValue('id_category_default'), Tools::getValue('categoryBox'))))
  1904. $this->errors[] = $this->l('This product must be in the default category.');
  1905. // Tags
  1906. foreach ($languages as $language)
  1907. if ($value = Tools::getValue('tags_'.$language['id_lang']))
  1908. if (!Validate::isTagsList($value))
  1909. $this->errors[] = sprintf(
  1910. Tools::displayError('The tags list (%s) is invalid.'),
  1911. $language['name']
  1912. );
  1913. }
  1914. /**
  1915. * Check if a field is edited (if the checkbox is checked)
  1916. * This method will do something only for multishop with a context all / group
  1917. *
  1918. * @param string $field Name of field
  1919. * @param int $id_lang
  1920. * @return bool
  1921. */
  1922. protected function isProductFieldUpdated($field, $id_lang = null)
  1923. {
  1924. // Cache this condition to improve performances
  1925. static $is_activated = null;
  1926. if (is_null($is_activated))
  1927. $is_activated = Shop::isFeatureActive() && Shop::getContext() != Shop::CONTEXT_SHOP && $this->id_object;
  1928. if (!$is_activated)
  1929. return true;
  1930. if (is_null($id_lang))
  1931. return !empty($_POST['multishop_check'][$field]);
  1932. else
  1933. return !empty($_POST['multishop_check'][$field][$id_lang]);
  1934. }
  1935. protected function _removeTaxFromEcotax()
  1936. {
  1937. $ecotaxTaxRate = Tax::getProductEcotaxRate();
  1938. if ($ecotax = Tools::getValue('ecotax'))
  1939. $_POST['ecotax'] = Tools::ps_round(Tools::getValue('ecotax') / (1 + $ecotaxTaxRate / 100), 6);
  1940. }
  1941. protected function _applyTaxToEcotax($product)
  1942. {
  1943. $ecotaxTaxRate = Tax::getProductEcotaxRate();
  1944. if ($product->ecotax)
  1945. $product->ecotax = Tools::ps_round($product->ecotax * (1 + $ecotaxTaxRate / 100), 2);
  1946. }
  1947. /**
  1948. * Update product download
  1949. *
  1950. * @param object $product Product
  1951. * @return bool
  1952. */
  1953. public function updateDownloadProduct($product, $edit = 0)
  1954. {
  1955. if ((int)Tools::getValue('is_virtual_file') == 1)
  1956. {
  1957. if (isset($_FILES['virtual_product_file_uploader']) && $_FILES['virtual_product_file_uploader']['size'] > 0)
  1958. {
  1959. $virtual_product_filename = ProductDownload::getNewFilename();
  1960. $helper = new HelperUploader('virtual_product_file_uploader');
  1961. $files = $helper->setPostMaxSize(Tools::getOctets(ini_get('upload_max_filesize')))
  1962. ->setSavePath(_PS_DOWNLOAD_DIR_)->upload($_FILES['virtual_product_file_uploader'], $virtual_product_filename);
  1963. }
  1964. else
  1965. $virtual_product_filename = Tools::getValue('virtual_product_filename', ProductDownload::getNewFilename());
  1966. $product->setDefaultAttribute(0);//reset cache_default_attribute
  1967. if (Tools::getValue('virtual_product_expiration_date') && !Validate::isDate(Tools::getValue('virtual_product_expiration_date')))
  1968. if (!Tools::getValue('virtual_product_expiration_date'))
  1969. {
  1970. $this->errors[] = Tools::displayError('The expiration-date attribute is required.');
  1971. return false;
  1972. }
  1973. // Trick's
  1974. if ($edit == 1)
  1975. {
  1976. $id_product_download = (int)ProductDownload::getIdFromIdProduct((int)$product->id);
  1977. if (!$id_product_download)
  1978. $id_product_download = (int)Tools::getValue('virtual_product_id');
  1979. }
  1980. else
  1981. $id_product_download = Tools::getValue('virtual_product_id');
  1982. $is_shareable = Tools::getValue('virtual_product_is_shareable');
  1983. $virtual_product_name = Tools::getValue('virtual_product_name');
  1984. $virtual_product_nb_days = Tools::getValue('virtual_product_nb_days');
  1985. $virtual_product_nb_downloable = Tools::getValue('virtual_product_nb_downloable');
  1986. $virtual_product_expiration_date = Tools::getValue('virtual_product_expiration_date');
  1987. $download = new ProductDownload((int)$id_product_download);
  1988. $download->id_product = (int)$product->id;
  1989. $download->display_filename = $virtual_product_name;
  1990. $download->filename = $virtual_product_filename;
  1991. $download->date_add = date('Y-m-d H:i:s');
  1992. $download->date_expiration = $virtual_product_expiration_date ? $virtual_product_expiration_date.' 23:59:59' : '';
  1993. $download->nb_days_accessible = (int)$virtual_product_nb_days;
  1994. $download->nb_downloadable = (int)$virtual_product_nb_downloable;
  1995. $download->active = 1;
  1996. $download->is_shareable = (int)$is_shareable;
  1997. if ($download->save())
  1998. return true;
  1999. }
  2000. else
  2001. {
  2002. /* unactive download product if checkbox not checked */
  2003. if ($edit == 1)
  2004. {
  2005. $id_product_download = (int)ProductDownload::getIdFromIdProduct((int)$product->id);
  2006. if (!$id_product_download)
  2007. $id_product_download = (int)Tools::getValue('virtual_product_id');
  2008. }
  2009. else
  2010. $id_product_download = ProductDownload::getIdFromIdProduct($product->id);
  2011. if (!empty($id_product_download))
  2012. {
  2013. $product_download = new ProductDownload((int)$id_product_download);
  2014. $product_download->date_expiration = date('Y-m-d H:i:s', time() - 1);
  2015. $product_download->active = 0;
  2016. return $product_download->save();
  2017. }
  2018. }
  2019. return false;
  2020. }
  2021. /**
  2022. * Update product accessories
  2023. *
  2024. * @param object $product Product
  2025. */
  2026. public function updateAccessories($product)
  2027. {
  2028. $product->deleteAccessories();
  2029. if ($accessories = Tools::getValue('inputAccessories'))
  2030. {
  2031. $accessories_id = array_unique(explode('-', $accessories));
  2032. if (count($accessories_id))
  2033. {
  2034. array_pop($accessories_id);
  2035. $product->changeAccessories($accessories_id);
  2036. }
  2037. }
  2038. }
  2039. /**
  2040. * Update product tags
  2041. *
  2042. * @param array Languages
  2043. * @param object $product Product
  2044. * @return boolean Update result
  2045. */
  2046. public function updateTags($languages, $product)
  2047. {
  2048. $tag_success = true;
  2049. /* Reset all tags for THIS product */
  2050. if (!Tag::deleteTagsForProduct((int)$product->id))
  2051. $this->errors[] = Tools::displayError('An error occurred while attempting to delete previous tags.');
  2052. /* Assign tags to this product */
  2053. foreach ($languages as $language)
  2054. if ($value = Tools::getValue('tags_'.$language['id_lang']))
  2055. $tag_success &= Tag::addTags($language['id_lang'], (int)$product->id, $value);
  2056. if (!$tag_success)
  2057. $this->errors[] = Tools::displayError('An error occurred while adding tags.');
  2058. return $tag_success;
  2059. }
  2060. public function initContent($token = null)
  2061. {
  2062. if ($this->display == 'edit' || $this->display == 'add')
  2063. {
  2064. $this->fields_form = array();
  2065. // Check if Module
  2066. if (substr($this->tab_display, 0, 6) == 'Module')
  2067. {
  2068. $this->tab_display_module = strtolower(substr($this->tab_display, 6, Tools::strlen($this->tab_display) - 6));
  2069. $this->tab_display = 'Modules';
  2070. }
  2071. if (method_exists($this, 'initForm'.$this->tab_display))
  2072. $this->tpl_form = strtolower($this->tab_display).'.tpl';
  2073. if ($this->ajax)
  2074. $this->content_only = true;
  2075. else
  2076. {
  2077. $product_tabs = array();
  2078. // tab_display defines which tab to display first
  2079. if (!method_exists($this, 'initForm'.$this->tab_display))
  2080. $this->tab_display = $this->default_tab;
  2081. $advanced_stock_management_active = Configuration::get('PS_ADVANCED_STOCK_MANAGEMENT');
  2082. $stock_management_active = Configuration::get('PS_STOCK_MANAGEMENT');
  2083. foreach ($this->available_tabs as $product_tab => $value)
  2084. {
  2085. // if it's the warehouses tab and advanced stock management is disabled, continue
  2086. if ($advanced_stock_management_active == 0 && $product_tab == 'Warehouses')
  2087. continue;
  2088. $product_tabs[$product_tab] = array(
  2089. 'id' => $product_tab,
  2090. 'selected' => (strtolower($product_tab) == strtolower($this->tab_display) || (isset($this->tab_display_module) && 'module'.$this->tab_display_module == Tools::strtolower($product_tab))),
  2091. 'name' => $this->available_tabs_lang[$product_tab],
  2092. 'href' => $this->context->link->getAdminLink('AdminProducts').'&amp;id_product='.(int)Tools::getValue('id_product').'&amp;action='.$product_tab,
  2093. );
  2094. }
  2095. $this->tpl_form_vars['product_tabs'] = $product_tabs;
  2096. }
  2097. }
  2098. else
  2099. {
  2100. if ($id_category = (int)$this->id_current_category)
  2101. self::$currentIndex .= '&id_category='.(int)$this->id_current_category;
  2102. // If products from all categories are displayed, we don't want to use sorting by position
  2103. if (!$id_category)
  2104. {
  2105. $this->_defaultOrderBy = $this->identifier;
  2106. if ($this->context->cookie->{$this->table.'Orderby'} == 'position')
  2107. {
  2108. unset($this->context->cookie->{$this->table.'Orderby'});
  2109. unset($this->context->cookie->{$this->table.'Orderway'});
  2110. }
  2111. }
  2112. if (!$id_category)
  2113. $id_category = 1;
  2114. $this->tpl_list_vars['is_category_filter'] = (bool)$this->id_current_category;
  2115. // Generate category selection tree
  2116. $tree = new HelperTreeCategories('categories-tree', $this->l('Filter by category'));
  2117. $tree->setAttribute('is_category_filter', (bool)$this->id_current_category)
  2118. ->setAttribute('base_url', preg_replace('#&id_category=[0-9]*#', '', self::$currentIndex).'&token='.$this->token)
  2119. ->setInputName('id-category')
  2120. ->setSelectedCategories(array((int)$id_category));
  2121. $this->tpl_list_vars['category_tree'] = $tree->render();
  2122. // used to build the new url when changing category
  2123. $this->tpl_list_vars['base_url'] = preg_replace('#&id_category=[0-9]*#', '', self::$currentIndex).'&token='.$this->token;
  2124. }
  2125. // @todo module free
  2126. $this->tpl_form_vars['vat_number'] = file_exists(_PS_MODULE_DIR_.'vatnumber/ajax.php');
  2127. parent::initContent();
  2128. }
  2129. public function renderKpis()
  2130. {
  2131. $time = time();
  2132. $kpis = array();
  2133. /* The data generation is located in AdminStatsControllerCore */
  2134. if (Configuration::get('PS_STOCK_MANAGEMENT'))
  2135. {
  2136. $helper = new HelperKpi();
  2137. $helper->id = 'box-products-stock';
  2138. $helper->icon = 'icon-archive';
  2139. $helper->color = 'color1';
  2140. $helper->title = $this->l('Items in Stock', null, null, false);
  2141. if (ConfigurationKPI::get('PERCENT_PRODUCT_STOCK') !== false)
  2142. $helper->value = ConfigurationKPI::get('PERCENT_PRODUCT_STOCK');
  2143. if (ConfigurationKPI::get('PERCENT_PRODUCT_STOCK_EXPIRE') < $time)
  2144. $helper->source = $this->context->link->getAdminLink('AdminStats').'&ajax=1&action=getKpi&kpi=percent_product_stock';
  2145. $kpis[] = $helper->generate();
  2146. }
  2147. $helper = new HelperKpi();
  2148. $helper->id = 'box-avg-gross-margin';
  2149. $helper->icon = 'icon-tags';
  2150. $helper->color = 'color2';
  2151. $helper->title = $this->l('Average Gross Margin', null, null, false);
  2152. if (ConfigurationKPI::get('PRODUCT_AVG_GROSS_MARGIN') !== false)
  2153. $helper->value = ConfigurationKPI::get('PRODUCT_AVG_GROSS_MARGIN');
  2154. if (ConfigurationKPI::get('PRODUCT_AVG_GROSS_MARGIN_EXPIRE') < $time)
  2155. $helper->source = $this->context->link->getAdminLink('AdminStats').'&ajax=1&action=getKpi&kpi=product_avg_gross_margin';
  2156. $kpis[] = $helper->generate();
  2157. $helper = new HelperKpi();
  2158. $helper->id = 'box-8020-sales-catalog';
  2159. $helper->icon = 'icon-beaker';
  2160. $helper->color = 'color3';
  2161. $helper->title = $this->l('80% of your sales', null, null, false);
  2162. $helper->subtitle = $this->l('30 days', null, null, false);
  2163. if (ConfigurationKPI::get('8020_SALES_CATALOG') !== false)
  2164. $helper->value = ConfigurationKPI::get('8020_SALES_CATALOG');
  2165. if (ConfigurationKPI::get('8020_SALES_CATALOG_EXPIRE') < $time)
  2166. $helper->source = $this->context->link->getAdminLink('AdminStats').'&ajax=1&action=getKpi&kpi=8020_sales_catalog';
  2167. $kpis[] = $helper->generate();
  2168. $helper = new HelperKpi();
  2169. $helper->id = 'box-disabled-products';
  2170. $helper->icon = 'icon-off';
  2171. $helper->color = 'color4';
  2172. $helper->href = $this->context->link->getAdminLink('AdminProducts');
  2173. $helper->title = $this->l('Disabled Products', null, null, false);
  2174. if (ConfigurationKPI::get('DISABLED_PRODUCTS') !== false)
  2175. $helper->value = ConfigurationKPI::get('DISABLED_PRODUCTS');
  2176. if (ConfigurationKPI::get('DISABLED_PRODUCTS_EXPIRE') < $time)
  2177. $helper->source = $this->context->link->getAdminLink('AdminStats').'&ajax=1&action=getKpi&kpi=disabled_products';
  2178. $kpis[] = $helper->generate();
  2179. $helper = new HelperKpiRow();
  2180. $helper->kpis = $kpis;
  2181. return $helper->generate();
  2182. }
  2183. public function renderList()
  2184. {
  2185. $this->addRowAction('edit');
  2186. $this->addRowAction('duplicate');
  2187. $this->addRowAction('delete');
  2188. return parent::renderList();
  2189. }
  2190. public function ajaxProcessProductManufacturers()
  2191. {
  2192. $manufacturers = Manufacturer::getManufacturers(false, 0, true, false, false, false, true);
  2193. $jsonArray = array();
  2194. if ($manufacturers)
  2195. foreach ($manufacturers as $manufacturer)
  2196. $jsonArray[] = '{"optionValue": "'.(int)$manufacturer['id_manufacturer'].'", "optionDisplay": "'.htmlspecialchars(trim($manufacturer['name'])).'"}';
  2197. die('['.implode(',', $jsonArray).']');
  2198. }
  2199. /**
  2200. * Build a categories tree
  2201. *
  2202. * @param array $indexedCategories Array with categories where product is indexed (in order to check checkbox)
  2203. * @param array $categories Categories to list
  2204. * @param array $current Current category
  2205. * @param integer $id_category Current category id
  2206. */
  2207. public static function recurseCategoryForInclude($id_obj, $indexedCategories, $categories, $current, $id_category = 1, $id_category_default = null, $has_suite = array())
  2208. {
  2209. global $done;
  2210. static $irow;
  2211. $content = '';
  2212. if (!isset($done[$current['infos']['id_parent']]))
  2213. $done[$current['infos']['id_parent']] = 0;
  2214. $done[$current['infos']['id_parent']] += 1;
  2215. $todo = count($categories[$current['infos']['id_parent']]);
  2216. $doneC = $done[$current['infos']['id_parent']];
  2217. $level = $current['infos']['level_depth'] + 1;
  2218. $content .= '
  2219. <tr class="'.($irow++ % 2 ? 'alt_row' : '').'">
  2220. <td>
  2221. <input type="checkbox" name="categoryBox[]" class="categoryBox'.($id_category_default == $id_category ? ' id_category_default' : '').'" id="categoryBox_'.$id_category.'" value="'.$id_category.'"'.((in_array($id_category, $indexedCategories) || ((int)(Tools::getValue('id_category')) == $id_category && !(int)($id_obj))) ? ' checked="checked"' : '').' />
  2222. </td>
  2223. <td>
  2224. '.$id_category.'
  2225. </td>
  2226. <td>';
  2227. for ($i = 2; $i < $level; $i++)
  2228. $content .= '<img src="../img/admin/lvl_'.$has_suite[$i - 2].'.gif" alt="" />';
  2229. $content .= '<img src="../img/admin/'.($level == 1 ? 'lv1.gif' : 'lv2_'.($todo == $doneC ? 'f' : 'b').'.gif').'" alt="" /> &nbsp;
  2230. <label for="categoryBox_'.$id_category.'" class="t">'.stripslashes($current['infos']['name']).'</label></td>
  2231. </tr>';
  2232. if ($level > 1)
  2233. $has_suite[] = ($todo == $doneC ? 0 : 1);
  2234. if (isset($categories[$id_category]))
  2235. foreach ($categories[$id_category] as $key => $row)
  2236. if ($key != 'infos')
  2237. $content .= AdminProductsController::recurseCategoryForInclude($id_obj, $indexedCategories, $categories, $categories[$id_category][$key], $key, $id_category_default, $has_suite);
  2238. return $content;
  2239. }
  2240. protected function _displayDraftWarning($active)
  2241. {
  2242. $content = '<div class="warn draft" style="'.($active ? 'display:none' : '').'">
  2243. <span>'.$this->l('Your product will be saved as a draft.').'</span>
  2244. <a href="#" class="btn btn-default pull-right" onclick="submitAddProductAndPreview()" ><i class="icon-external-link-sign"></i> '.$this->l('Save and preview').'</a>
  2245. <input type="hidden" name="fakeSubmitAddProductAndPreview" id="fakeSubmitAddProductAndPreview" />
  2246. </div>';
  2247. $this->tpl_form_vars['draft_warning'] = $content;
  2248. }
  2249. public function initPageHeaderToolbar()
  2250. {
  2251. if (empty($this->display))
  2252. $this->page_header_toolbar_btn['new_product'] = array(
  2253. 'href' => self::$currentIndex.'&addproduct&token='.$this->token,
  2254. 'desc' => $this->l('Add new product', null, null, false),
  2255. 'icon' => 'process-icon-new'
  2256. );
  2257. if ($this->display == 'edit')
  2258. {
  2259. if (($product = $this->loadObject(true)) && $product->isAssociatedToShop())
  2260. {
  2261. // adding button for preview this product
  2262. if ($url_preview = $this->getPreviewUrl($product))
  2263. $this->page_header_toolbar_btn['preview'] = array(
  2264. 'short' => $this->l('Preview', null, null, false),
  2265. 'href' => $url_preview,
  2266. 'desc' => $this->l('Preview', null, null, false),
  2267. 'target' => true,
  2268. 'class' => 'previewUrl'
  2269. );
  2270. // adding button for duplicate this product
  2271. if ($this->tabAccess['add'])
  2272. $this->page_header_toolbar_btn['duplicate'] = array(
  2273. 'short' => $this->l('Duplicate', null, null, false),
  2274. 'href' => $this->context->link->getAdminLink('AdminProducts', true).'&id_product='.(int)$product->id.'&duplicateproduct',
  2275. 'desc' => $this->l('Duplicate', null, null, false),
  2276. 'confirm' => 1,
  2277. 'js' => 'if (confirm(\''.$this->l('Also copy images', null, true, false).' ?\')){document.location.href = \''.$this->context->link->getAdminLink('AdminProducts', true).'&id_product='.(int)$product->id.'&duplicateproduct\'; return false;} else{document.location.href = \''.$this->context->link->getAdminLink('AdminProducts', true).'&id_product='.(int)$product->id.'&duplicateproduct&noimage=1\'; return false;}'
  2278. );
  2279. // adding button for preview this product statistics
  2280. if (file_exists(_PS_MODULE_DIR_.'statsproduct/statsproduct.php'))
  2281. $this->page_header_toolbar_btn['stats'] = array(
  2282. 'short' => $this->l('Statistics', null, null, false),
  2283. 'href' => $this->context->link->getAdminLink('AdminStats').'&module=statsproduct&id_product='.(int)$product->id,
  2284. 'desc' => $this->l('Product sales', null, null, false),
  2285. );
  2286. // adding button for delete this product
  2287. if ($this->tabAccess['delete'])
  2288. $this->page_header_toolbar_btn['delete'] = array(
  2289. 'short' => $this->l('Delete', null, null, false),
  2290. 'href' => $this->context->link->getAdminLink('AdminProducts').'&id_product='.(int)$product->id.'&deleteproduct',
  2291. 'desc' => $this->l('Delete this product', null, null, false),
  2292. 'confirm' => 1,
  2293. 'js' => 'if (confirm(\''.$this->l('Delete product?', null, true, false).'\')){return true;}else{event.preventDefault();}'
  2294. );
  2295. }
  2296. }
  2297. parent::initPageHeaderToolbar();
  2298. }
  2299. public function initToolbar()
  2300. {
  2301. parent::initToolbar();
  2302. if ($this->display == 'edit' || $this->display == 'add')
  2303. {
  2304. $this->toolbar_btn['save'] = array(
  2305. 'short' => 'Save',
  2306. 'href' => '#',
  2307. 'desc' => $this->l('Save'),
  2308. );
  2309. $this->toolbar_btn['save-and-stay'] = array(
  2310. 'short' => 'SaveAndStay',
  2311. 'href' => '#',
  2312. 'desc' => $this->l('Save and stay'),
  2313. );
  2314. // adding button for adding a new combination in Combination tab
  2315. $this->toolbar_btn['newCombination'] = array(
  2316. 'short' => 'New combination',
  2317. 'desc' => $this->l('New combination'),
  2318. 'class' => 'toolbar-new'
  2319. );
  2320. }
  2321. else
  2322. $this->toolbar_btn['import'] = array(
  2323. 'href' => $this->context->link->getAdminLink('AdminImport', true).'&import_type=products',
  2324. 'desc' => $this->l('Import')
  2325. );
  2326. $this->context->smarty->assign('toolbar_scroll', 1);
  2327. $this->context->smarty->assign('show_toolbar', 1);
  2328. $this->context->smarty->assign('toolbar_btn', $this->toolbar_btn);
  2329. }
  2330. /**
  2331. * renderForm contains all necessary initialization needed for all tabs
  2332. *
  2333. * @return string|void
  2334. */
  2335. public function renderForm()
  2336. {
  2337. // This nice code (irony) is here to store the product name, because the row after will erase product name in multishop context
  2338. $this->product_name = $this->object->name[$this->context->language->id];
  2339. if (!method_exists($this, 'initForm'.$this->tab_display))
  2340. return;
  2341. $product = $this->object;
  2342. // Product for multishop
  2343. $this->context->smarty->assign('bullet_common_field', '');
  2344. if (Shop::isFeatureActive() && $this->display == 'edit')
  2345. {
  2346. if (Shop::getContext() != Shop::CONTEXT_SHOP)
  2347. {
  2348. $this->context->smarty->assign(array(
  2349. 'display_multishop_checkboxes' => true,
  2350. 'multishop_check' => Tools::getValue('multishop_check'),
  2351. ));
  2352. }
  2353. if (Shop::getContext() != Shop::CONTEXT_ALL)
  2354. {
  2355. $this->context->smarty->assign('bullet_common_field', '<i class="icon-circle text-orange"></i>');
  2356. $this->context->smarty->assign('display_common_field', true);
  2357. }
  2358. }
  2359. $this->tpl_form_vars['tabs_preloaded'] = $this->available_tabs;
  2360. $this->tpl_form_vars['product_type'] = (int)Tools::getValue('type_product', $product->getType());
  2361. $this->getLanguages();
  2362. $this->tpl_form_vars['id_lang_default'] = Configuration::get('PS_LANG_DEFAULT');
  2363. $this->tpl_form_vars['currentIndex'] = self::$currentIndex;
  2364. $this->tpl_form_vars['display_multishop_checkboxes'] = (Shop::isFeatureActive() && Shop::getContext() != Shop::CONTEXT_SHOP && $this->display == 'edit');
  2365. $this->fields_form = array('');
  2366. $this->tpl_form_vars['token'] = $this->token;
  2367. $this->tpl_form_vars['combinationImagesJs'] = $this->getCombinationImagesJs();
  2368. $this->tpl_form_vars['PS_ALLOW_ACCENTED_CHARS_URL'] = (int)Configuration::get('PS_ALLOW_ACCENTED_CHARS_URL');
  2369. $this->tpl_form_vars['post_data'] = Tools::jsonEncode($_POST);
  2370. $this->tpl_form_vars['save_error'] = !empty($this->errors);
  2371. $this->tpl_form_vars['ps_force_friendly_product'] = Configuration::get('PS_FORCE_FRIENDLY_PRODUCT');
  2372. // autoload rich text editor (tiny mce)
  2373. $this->tpl_form_vars['tinymce'] = true;
  2374. $iso = $this->context->language->iso_code;
  2375. $this->tpl_form_vars['iso'] = file_exists(_PS_CORE_DIR_.'/js/tiny_mce/langs/'.$iso.'.js') ? $iso : 'en';
  2376. $this->tpl_form_vars['path_css'] = _THEME_CSS_DIR_;
  2377. $this->tpl_form_vars['ad'] = __PS_BASE_URI__.basename(_PS_ADMIN_DIR_);
  2378. if (Validate::isLoadedObject(($this->object)))
  2379. $id_product = (int)$this->object->id;
  2380. else
  2381. $id_product = (int)Tools::getvalue('id_product');
  2382. $this->tpl_form_vars['form_action'] = $this->context->link->getAdminLink('AdminProducts').'&amp;'.($id_product ? 'id_product='.(int)$id_product : 'addproduct');
  2383. $this->tpl_form_vars['id_product'] = $id_product;
  2384. // Transform configuration option 'upload_max_filesize' in octets
  2385. $upload_max_filesize = Tools::getOctets(ini_get('upload_max_filesize'));
  2386. // Transform configuration option 'upload_max_filesize' in MegaOctets
  2387. $upload_max_filesize = ($upload_max_filesize / 1024) / 1024;
  2388. $this->tpl_form_vars['upload_max_filesize'] = $upload_max_filesize;
  2389. $this->tpl_form_vars['country_display_tax_label'] = $this->context->country->display_tax_label;
  2390. $this->tpl_form_vars['has_combinations'] = $this->object->hasAttributes();
  2391. $this->product_exists_in_shop = true;
  2392. if ($this->display == 'edit' && Validate::isLoadedObject($product) && Shop::isFeatureActive() && Shop::getContext() == Shop::CONTEXT_SHOP && !$product->isAssociatedToShop($this->context->shop->id))
  2393. {
  2394. $this->product_exists_in_shop = false;
  2395. if ($this->tab_display == 'Informations')
  2396. $this->displayWarning($this->l('Warning: The product does not exist in this shop'));
  2397. $default_product = new Product();
  2398. $definition = ObjectModel::getDefinition($product);
  2399. foreach ($definition['fields'] as $field_name => $field)
  2400. if (isset($field['shop']) && $field['shop'])
  2401. $product->$field_name = ObjectModel::formatValue($default_product->$field_name, $field['type']);
  2402. }
  2403. // let's calculate this once for all
  2404. if (!Validate::isLoadedObject($this->object) && Tools::getValue('id_product'))
  2405. $this->errors[] = 'Unable to load object';
  2406. else
  2407. {
  2408. $this->_displayDraftWarning($this->object->active);
  2409. // if there was an error while saving, we don't want to lose posted data
  2410. if (!empty($this->errors))
  2411. $this->copyFromPost($this->object, $this->table);
  2412. $this->initPack($this->object);
  2413. $this->{'initForm'.$this->tab_display}($this->object);
  2414. $this->tpl_form_vars['product'] = $this->object;
  2415. if ($this->ajax)
  2416. if (!isset($this->tpl_form_vars['custom_form']))
  2417. throw new PrestaShopException('custom_form empty for action '.$this->tab_display);
  2418. else
  2419. return $this->tpl_form_vars['custom_form'];
  2420. }
  2421. $parent = parent::renderForm();
  2422. $this->addJqueryPlugin(array('autocomplete', 'fancybox', 'typewatch'));
  2423. return $parent;
  2424. }
  2425. public function getPreviewUrl(Product $product)
  2426. {
  2427. $id_lang = Configuration::get('PS_LANG_DEFAULT', null, null, Context::getContext()->shop->id);
  2428. if (!ShopUrl::getMainShopDomain())
  2429. return false;
  2430. $is_rewrite_active = (bool)Configuration::get('PS_REWRITING_SETTINGS');
  2431. $preview_url = $this->context->link->getProductLink(
  2432. $product,
  2433. $this->getFieldValue($product, 'link_rewrite', $this->context->language->id),
  2434. Category::getLinkRewrite($this->getFieldValue($product, 'id_category_default'), $this->context->language->id),
  2435. null,
  2436. $id_lang,
  2437. (int)Context::getContext()->shop->id,
  2438. 0,
  2439. $is_rewrite_active
  2440. );
  2441. if (!$product->active)
  2442. {
  2443. $admin_dir = dirname($_SERVER['PHP_SELF']);
  2444. $admin_dir = substr($admin_dir, strrpos($admin_dir, '/') + 1);
  2445. $preview_url .= ((strpos($preview_url, '?') === false) ? '?' : '&').'adtoken='.$this->token.'&ad='.$admin_dir.'&id_employee='.(int)$this->context->employee->id;
  2446. }
  2447. return $preview_url;
  2448. }
  2449. /**
  2450. * Post treatment for suppliers
  2451. */
  2452. public function processSuppliers()
  2453. {
  2454. if ((int)Tools::getValue('supplier_loaded') === 1 && Validate::isLoadedObject($product = new Product((int)Tools::getValue('id_product'))))
  2455. {
  2456. // Get all id_product_attribute
  2457. $attributes = $product->getAttributesResume($this->context->language->id);
  2458. if (empty($attributes))
  2459. $attributes[] = array(
  2460. 'id_product_attribute' => 0,
  2461. 'attribute_designation' => ''
  2462. );
  2463. // Get all available suppliers
  2464. $suppliers = Supplier::getSuppliers();
  2465. // Get already associated suppliers
  2466. $associated_suppliers = ProductSupplier::getSupplierCollection($product->id);
  2467. $suppliers_to_associate = array();
  2468. $new_default_supplier = 0;
  2469. if (Tools::isSubmit('default_supplier'))
  2470. $new_default_supplier = (int)Tools::getValue('default_supplier');
  2471. // Get new associations
  2472. foreach ($suppliers as $supplier)
  2473. if (Tools::isSubmit('check_supplier_'.$supplier['id_supplier']))
  2474. $suppliers_to_associate[] = $supplier['id_supplier'];
  2475. // Delete already associated suppliers if needed
  2476. foreach ($associated_suppliers as $key => $associated_supplier)
  2477. if (!in_array($associated_supplier->id_supplier, $suppliers_to_associate))
  2478. {
  2479. $associated_supplier->delete();
  2480. unset($associated_suppliers[$key]);
  2481. }
  2482. // Associate suppliers
  2483. foreach ($suppliers_to_associate as $id)
  2484. {
  2485. $to_add = true;
  2486. foreach ($associated_suppliers as $as)
  2487. if ($id == $as->id_supplier)
  2488. $to_add = false;
  2489. if ($to_add)
  2490. {
  2491. $product_supplier = new ProductSupplier();
  2492. $product_supplier->id_product = $product->id;
  2493. $product_supplier->id_product_attribute = 0;
  2494. $product_supplier->id_supplier = $id;
  2495. if ($this->context->currency->id)
  2496. $product_supplier->id_currency = (int)$this->context->currency->id;
  2497. else
  2498. $product_supplier->id_currency = (int)Configuration::get('PS_CURRENCY_DEFAULT');
  2499. $product_supplier->save();
  2500. $associated_suppliers[] = $product_supplier;
  2501. }
  2502. }
  2503. // Manage references and prices
  2504. foreach ($attributes as $attribute)
  2505. foreach ($associated_suppliers as $supplier)
  2506. {
  2507. if (Tools::isSubmit('supplier_reference_'.$product->id.'_'.$attribute['id_product_attribute'].'_'.$supplier->id_supplier) ||
  2508. (Tools::isSubmit('product_price_'.$product->id.'_'.$attribute['id_product_attribute'].'_'.$supplier->id_supplier) &&
  2509. Tools::isSubmit('product_price_currency_'.$product->id.'_'.$attribute['id_product_attribute'].'_'.$supplier->id_supplier)))
  2510. {
  2511. $reference = pSQL(
  2512. Tools::getValue(
  2513. 'supplier_reference_'.$product->id.'_'.$attribute['id_product_attribute'].'_'.$supplier->id_supplier,
  2514. ''
  2515. )
  2516. );
  2517. $price = (float)str_replace(
  2518. array(' ', ','),
  2519. array('', '.'),
  2520. Tools::getValue(
  2521. 'product_price_'.$product->id.'_'.$attribute['id_product_attribute'].'_'.$supplier->id_supplier,
  2522. 0
  2523. )
  2524. );
  2525. $price = Tools::ps_round($price, 6);
  2526. $id_currency = (int)Tools::getValue(
  2527. 'product_price_currency_'.$product->id.'_'.$attribute['id_product_attribute'].'_'.$supplier->id_supplier,
  2528. 0
  2529. );
  2530. if ($id_currency <= 0 || ( !($result = Currency::getCurrency($id_currency)) || empty($result) ))
  2531. $this->errors[] = Tools::displayError('The selected currency is not valid');
  2532. // Save product-supplier data
  2533. $product_supplier_id = (int)ProductSupplier::getIdByProductAndSupplier($product->id, $attribute['id_product_attribute'], $supplier->id_supplier);
  2534. if (!$product_supplier_id)
  2535. {
  2536. $product->addSupplierReference($supplier->id_supplier, (int)$attribute['id_product_attribute'], $reference, (float)$price, (int)$id_currency);
  2537. if ($product->id_supplier == $supplier->id_supplier)
  2538. {
  2539. if ((int)$attribute['id_product_attribute'] > 0)
  2540. {
  2541. $data = array(
  2542. 'supplier_reference' => pSQL($reference),
  2543. 'wholesale_price' => (float)Tools::convertPrice($price, $id_currency)
  2544. );
  2545. $where = '
  2546. a.id_product = '.(int)$product->id.'
  2547. AND a.id_product_attribute = '.(int)$attribute['id_product_attribute'];
  2548. ObjectModel::updateMultishopTable('Combination', $data, $where);
  2549. }
  2550. else
  2551. {
  2552. $product->wholesale_price = (float)Tools::convertPrice($price, $id_currency); //converted in the default currency
  2553. $product->supplier_reference = pSQL($reference);
  2554. $product->update();
  2555. }
  2556. }
  2557. }
  2558. else
  2559. {
  2560. $product_supplier = new ProductSupplier($product_supplier_id);
  2561. $product_supplier->id_currency = (int)$id_currency;
  2562. $product_supplier->product_supplier_price_te = (float)$price;
  2563. $product_supplier->product_supplier_reference = pSQL($reference);
  2564. $product_supplier->update();
  2565. }
  2566. }
  2567. elseif (Tools::isSubmit('supplier_reference_'.$product->id.'_'.$attribute['id_product_attribute'].'_'.$supplier->id_supplier))
  2568. {
  2569. //int attribute with default values if possible
  2570. if ((int)$attribute['id_product_attribute'] > 0)
  2571. {
  2572. $product_supplier = new ProductSupplier();
  2573. $product_supplier->id_product = $product->id;
  2574. $product_supplier->id_product_attribute = (int)$attribute['id_product_attribute'];
  2575. $product_supplier->id_supplier = $supplier->id_supplier;
  2576. $product_supplier->save();
  2577. }
  2578. }
  2579. }
  2580. // Manage defaut supplier for product
  2581. if ($new_default_supplier != $product->id_supplier)
  2582. {
  2583. $this->object->id_supplier = $new_default_supplier;
  2584. $this->object->update();
  2585. }
  2586. }
  2587. }
  2588. /**
  2589. * Post treatment for warehouses
  2590. */
  2591. public function processWarehouses()
  2592. {
  2593. if ((int)Tools::getValue('warehouse_loaded') === 1 && Validate::isLoadedObject($product = new Product((int)$id_product = Tools::getValue('id_product'))))
  2594. {
  2595. // Get all id_product_attribute
  2596. $attributes = $product->getAttributesResume($this->context->language->id);
  2597. if (empty($attributes))
  2598. $attributes[] = array(
  2599. 'id_product_attribute' => 0,
  2600. 'attribute_designation' => ''
  2601. );
  2602. // Get all available warehouses
  2603. $warehouses = Warehouse::getWarehouses(true);
  2604. // Get already associated warehouses
  2605. $associated_warehouses_collection = WarehouseProductLocation::getCollection($product->id);
  2606. $elements_to_manage = array();
  2607. // get form inforamtion
  2608. foreach ($attributes as $attribute)
  2609. {
  2610. foreach ($warehouses as $warehouse)
  2611. {
  2612. $key = $warehouse['id_warehouse'].'_'.$product->id.'_'.$attribute['id_product_attribute'];
  2613. // get elements to manage
  2614. if (Tools::isSubmit('check_warehouse_'.$key))
  2615. {
  2616. $location = Tools::getValue('location_warehouse_'.$key, '');
  2617. $elements_to_manage[$key] = $location;
  2618. }
  2619. }
  2620. }
  2621. // Delete entry if necessary
  2622. foreach ($associated_warehouses_collection as $awc)
  2623. {
  2624. if (!array_key_exists($awc->id_warehouse.'_'.$awc->id_product.'_'.$awc->id_product_attribute, $elements_to_manage))
  2625. $awc->delete();
  2626. }
  2627. // Manage locations
  2628. foreach ($elements_to_manage as $key => $location)
  2629. {
  2630. $params = explode('_', $key);
  2631. $wpl_id = (int)WarehouseProductLocation::getIdByProductAndWarehouse((int)$params[1], (int)$params[2], (int)$params[0]);
  2632. if (empty($wpl_id))
  2633. {
  2634. //create new record
  2635. $warehouse_location_entity = new WarehouseProductLocation();
  2636. $warehouse_location_entity->id_product = (int)$params[1];
  2637. $warehouse_location_entity->id_product_attribute = (int)$params[2];
  2638. $warehouse_location_entity->id_warehouse = (int)$params[0];
  2639. $warehouse_location_entity->location = pSQL($location);
  2640. $warehouse_location_entity->save();
  2641. }
  2642. else
  2643. {
  2644. $warehouse_location_entity = new WarehouseProductLocation((int)$wpl_id);
  2645. $location = pSQL($location);
  2646. if ($location != $warehouse_location_entity->location)
  2647. {
  2648. $warehouse_location_entity->location = pSQL($location);
  2649. $warehouse_location_entity->update();
  2650. }
  2651. }
  2652. }
  2653. StockAvailable::synchronize((int)$id_product);
  2654. }
  2655. }
  2656. public function initFormAssociations($obj)
  2657. {
  2658. $product = $obj;
  2659. $data = $this->createTemplate($this->tpl_form);
  2660. // Prepare Categories tree for display in Associations tab
  2661. $root = Category::getRootCategory();
  2662. $default_category = $this->context->cookie->id_category_products_filter ? $this->context->cookie->id_category_products_filter : Context::getContext()->shop->id_category;
  2663. if (!$product->id || !$product->isAssociatedToShop())
  2664. $selected_cat = Category::getCategoryInformations(Tools::getValue('categoryBox', array($default_category)), $this->default_form_language);
  2665. else
  2666. {
  2667. if (Tools::isSubmit('categoryBox'))
  2668. $selected_cat = Category::getCategoryInformations(Tools::getValue('categoryBox', array($default_category)), $this->default_form_language);
  2669. else
  2670. $selected_cat = Product::getProductCategoriesFull($product->id, $this->default_form_language);
  2671. }
  2672. // Multishop block
  2673. $data->assign('feature_shop_active', Shop::isFeatureActive());
  2674. $helper = new HelperForm();
  2675. if ($this->object && $this->object->id)
  2676. $helper->id = $this->object->id;
  2677. else
  2678. $helper->id = null;
  2679. $helper->table = $this->table;
  2680. $helper->identifier = $this->identifier;
  2681. // Accessories block
  2682. $accessories = Product::getAccessoriesLight($this->context->language->id, $product->id);
  2683. if ($post_accessories = Tools::getValue('inputAccessories'))
  2684. {
  2685. $post_accessories_tab = explode('-', Tools::getValue('inputAccessories'));
  2686. foreach ($post_accessories_tab as $accessory_id)
  2687. if (!$this->haveThisAccessory($accessory_id, $accessories) && $accessory = Product::getAccessoryById($accessory_id))
  2688. $accessories[] = $accessory;
  2689. }
  2690. $data->assign('accessories', $accessories);
  2691. $product->manufacturer_name = Manufacturer::getNameById($product->id_manufacturer);
  2692. $categories = array();
  2693. foreach ($selected_cat as $key => $category)
  2694. $categories[] = $key;
  2695. $tree = new HelperTreeCategories('associated-categories-tree', 'Associated categories');
  2696. $tree->setTemplate('tree_associated_categories.tpl')
  2697. ->setHeaderTemplate('tree_associated_header.tpl')
  2698. ->setRootCategory($root->id)
  2699. ->setUseCheckBox(true)
  2700. ->setUseSearch(true)
  2701. ->setSelectedCategories($categories);
  2702. $data->assign(array('default_category' => $default_category,
  2703. 'selected_cat_ids' => implode(',', array_keys($selected_cat)),
  2704. 'selected_cat' => $selected_cat,
  2705. 'id_category_default' => $product->getDefaultCategory(),
  2706. 'category_tree' => $tree->render(),
  2707. 'product' => $product,
  2708. 'link' => $this->context->link,
  2709. 'is_shop_context' => Shop::getContext() == Shop::CONTEXT_SHOP
  2710. ));
  2711. $this->tpl_form_vars['custom_form'] = $data->fetch();
  2712. }
  2713. public function initFormPrices($obj)
  2714. {
  2715. $data = $this->createTemplate($this->tpl_form);
  2716. $product = $obj;
  2717. if ($obj->id)
  2718. {
  2719. $shops = Shop::getShops();
  2720. $countries = Country::getCountries($this->context->language->id);
  2721. $groups = Group::getGroups($this->context->language->id);
  2722. $currencies = Currency::getCurrencies();
  2723. $attributes = $obj->getAttributesGroups((int)$this->context->language->id);
  2724. $combinations = array();
  2725. foreach ($attributes as $attribute)
  2726. {
  2727. $combinations[$attribute['id_product_attribute']]['id_product_attribute'] = $attribute['id_product_attribute'];
  2728. if (!isset($combinations[$attribute['id_product_attribute']]['attributes']))
  2729. $combinations[$attribute['id_product_attribute']]['attributes'] = '';
  2730. $combinations[$attribute['id_product_attribute']]['attributes'] .= $attribute['attribute_name'].' - ';
  2731. $combinations[$attribute['id_product_attribute']]['price'] = Tools::displayPrice(
  2732. Tools::convertPrice(
  2733. Product::getPriceStatic((int)$obj->id, false, $attribute['id_product_attribute']),
  2734. $this->context->currency
  2735. ), $this->context->currency
  2736. );
  2737. }
  2738. foreach ($combinations as &$combination)
  2739. $combination['attributes'] = rtrim($combination['attributes'], ' - ');
  2740. $data->assign('specificPriceModificationForm', $this->_displaySpecificPriceModificationForm(
  2741. $this->context->currency, $shops, $currencies, $countries, $groups)
  2742. );
  2743. $data->assign('ecotax_tax_excl', $obj->ecotax);
  2744. $this->_applyTaxToEcotax($obj);
  2745. $data->assign(array(
  2746. 'shops' => $shops,
  2747. 'admin_one_shop' => count($this->context->employee->getAssociatedShops()) == 1,
  2748. 'currencies' => $currencies,
  2749. 'countries' => $countries,
  2750. 'groups' => $groups,
  2751. 'combinations' => $combinations,
  2752. 'multi_shop' => Shop::isFeatureActive(),
  2753. 'link' => new Link()
  2754. ));
  2755. }
  2756. else
  2757. {
  2758. $this->displayWarning($this->l('You must save this product before adding specific pricing'));
  2759. $product->id_tax_rules_group = (int)Product::getIdTaxRulesGroupMostUsed();
  2760. $data->assign('ecotax_tax_excl', 0);
  2761. }
  2762. // prices part
  2763. $data->assign(array(
  2764. 'link' => $this->context->link,
  2765. 'currency' => $currency = $this->context->currency,
  2766. 'tax_rules_groups' => TaxRulesGroup::getTaxRulesGroups(true),
  2767. 'taxesRatesByGroup' => TaxRulesGroup::getAssociatedTaxRatesByIdCountry($this->context->country->id),
  2768. 'ecotaxTaxRate' => Tax::getProductEcotaxRate(),
  2769. 'tax_exclude_taxe_option' => Tax::excludeTaxeOption(),
  2770. 'ps_use_ecotax' => Configuration::get('PS_USE_ECOTAX'),
  2771. ));
  2772. $product->price = Tools::convertPrice($product->price, $this->context->currency, true, $this->context);
  2773. if ($product->unit_price_ratio != 0)
  2774. $data->assign('unit_price', Tools::ps_round($product->price / $product->unit_price_ratio, 2));
  2775. else
  2776. $data->assign('unit_price', 0);
  2777. $data->assign('ps_tax', Configuration::get('PS_TAX'));
  2778. $data->assign('country_display_tax_label', $this->context->country->display_tax_label);
  2779. $data->assign(array(
  2780. 'currency', $this->context->currency,
  2781. 'product' => $product,
  2782. 'token' => $this->token
  2783. ));
  2784. $this->tpl_form_vars['custom_form'] = $data->fetch();
  2785. }
  2786. public function initFormSeo($product)
  2787. {
  2788. if (!$this->default_form_language)
  2789. $this->getLanguages();
  2790. $data = $this->createTemplate($this->tpl_form);
  2791. $data->assign(array(
  2792. 'product' => $product,
  2793. 'languages' => $this->_languages,
  2794. 'id_lang' => $this->context->language->id,
  2795. 'ps_ssl_enabled' => Configuration::get('PS_SSL_ENABLED'),
  2796. 'curent_shop_url' => $this->context->shop->getBaseURL(),
  2797. 'default_form_language' => $this->default_form_language
  2798. ));
  2799. $this->tpl_form_vars['custom_form'] = $data->fetch();
  2800. }
  2801. /**
  2802. * Get an array of pack items for display from the product object if specified, else from POST/GET values
  2803. *
  2804. * @param Product $product
  2805. * @return array of pack items
  2806. */
  2807. public function getPackItems($product = null)
  2808. {
  2809. $pack_items = array();
  2810. if (!$product)
  2811. {
  2812. $names_input = Tools::getValue('namePackItems');
  2813. $ids_input = Tools::getValue('inputPackItems');
  2814. if (!$names_input || !$ids_input)
  2815. return array();
  2816. // ids is an array of string with format : QTYxID
  2817. $ids = array_unique(explode('-', $ids_input));
  2818. $names = array_unique(explode('造', $names_input));
  2819. if (!empty($ids))
  2820. {
  2821. $length = count($ids);
  2822. for ($i = 0; $i < $length; $i++)
  2823. {
  2824. if (!empty($ids[$i]) && !empty($names[$i]))
  2825. {
  2826. list($pack_items[$i]['pack_quantity'], $pack_items[$i]['id']) = explode('x', $ids[$i]);
  2827. $exploded_name = explode('x', $names[$i]);
  2828. $pack_items[$i]['name'] = $exploded_name[1];
  2829. }
  2830. }
  2831. }
  2832. }
  2833. else
  2834. {
  2835. $i = 0;
  2836. foreach ($product->packItems as $pack_item)
  2837. {
  2838. $pack_items[$i]['id'] = $pack_item->id;
  2839. $pack_items[$i]['pack_quantity'] = $pack_item->pack_quantity;
  2840. $pack_items[$i]['name'] = $pack_item->name;
  2841. $pack_items[$i]['reference'] = $pack_item->reference;
  2842. $cover = Product::getCover($pack_item->id);
  2843. $pack_items[$i]['image'] = Context::getContext()->link->getImageLink($pack_item->link_rewrite, $cover['id_image'] , 'home_default');
  2844. // @todo: don't rely on 'home_default'
  2845. //$path_to_image = _PS_IMG_DIR_.'p/'.Image::getImgFolderStatic($cover['id_image']).(int)$cover['id_image'].'.jpg';
  2846. //$pack_items[$i]['image'] = ImageManager::thumbnail($path_to_image, 'pack_mini_'.$pack_item->id.'_'.$this->context->shop->id.'.jpg', 120);
  2847. $i++;
  2848. }
  2849. }
  2850. return $pack_items;
  2851. }
  2852. public function initFormPack($product)
  2853. {
  2854. $data = $this->createTemplate($this->tpl_form);
  2855. // If pack items have been submitted, we want to display them instead of the actuel content of the pack
  2856. // in database. In case of a submit error, the posted data is not lost and can be sent again.
  2857. if (Tools::getValue('namePackItems'))
  2858. {
  2859. $input_pack_items = Tools::getValue('inputPackItems');
  2860. $input_namepack_items = Tools::getValue('namePackItems');
  2861. $pack_items = $this->getPackItems();
  2862. }
  2863. else
  2864. {
  2865. $product->packItems = Pack::getItems($product->id, $this->context->language->id);
  2866. $pack_items = $this->getPackItems($product);
  2867. $input_namepack_items = '';
  2868. $input_pack_items = '';
  2869. foreach ($pack_items as $pack_item)
  2870. {
  2871. $input_pack_items .= $pack_item['pack_quantity'].'x'.$pack_item['id'].'-';
  2872. $input_namepack_items .= $pack_item['pack_quantity'].' x '.$pack_item['name'].'造';
  2873. }
  2874. }
  2875. $data->assign(array(
  2876. 'input_pack_items' => $input_pack_items,
  2877. 'input_namepack_items' => $input_namepack_items,
  2878. 'pack_items' => $pack_items,
  2879. 'product_type' => (int)Tools::getValue('type_product', $product->getType())
  2880. ));
  2881. $this->tpl_form_vars['custom_form'] = $data->fetch();
  2882. }
  2883. public function initFormVirtualProduct($product)
  2884. {
  2885. $data = $this->createTemplate($this->tpl_form);
  2886. $currency = $this->context->currency;
  2887. /*
  2888. * Form for adding a virtual product like software, mp3, etc...
  2889. */
  2890. $product_download = new ProductDownload();
  2891. if ($id_product_download = $product_download->getIdFromIdProduct($this->getFieldValue($product, 'id')))
  2892. $product_download = new ProductDownload($id_product_download);
  2893. $product->{'productDownload'} = $product_download;
  2894. if ($product->productDownload->id && empty($product->productDownload->display_filename))
  2895. {
  2896. $this->errors[] = Tools::displayError('A file name is required in order to associate a file');
  2897. $this->tab_display = 'VirtualProduct';
  2898. }
  2899. // @todo handle is_virtual with the value of the product
  2900. $exists_file = realpath(_PS_DOWNLOAD_DIR_).'/'.$product->productDownload->filename;
  2901. $data->assign('product_downloaded', $product->productDownload->id);
  2902. if (!file_exists($exists_file)
  2903. && !empty($product->productDownload->display_filename)
  2904. && empty($product->cache_default_attribute))
  2905. $msg = sprintf(Tools::displayError('File "%s" is missing'),
  2906. $product->productDownload->display_filename);
  2907. else
  2908. $msg = '';
  2909. $virtual_product_file_uploader = new HelperUploader('virtual_product_file_uploader');
  2910. $virtual_product_file_uploader->setMultiple(false)->setUrl(
  2911. Context::getContext()->link->getAdminLink('AdminProducts').'&ajax=1&id_product='.(int)$product->id
  2912. .'&action=AddVirtualProductFile')->setPostMaxSize(Tools::getOctets(ini_get('upload_max_filesize')))
  2913. ->setTemplate('virtual_product.tpl');
  2914. $data->assign(array(
  2915. 'download_product_file_missing' => $msg,
  2916. 'download_dir_writable' => ProductDownload::checkWritableDir(),
  2917. 'up_filename' => strval(Tools::getValue('virtual_product_filename'))
  2918. ));
  2919. $product->productDownload->nb_downloadable = ($product->productDownload->id > 0) ? $product->productDownload->nb_downloadable : htmlentities(Tools::getValue('virtual_product_nb_downloable'), ENT_COMPAT, 'UTF-8');
  2920. $product->productDownload->date_expiration = ($product->productDownload->id > 0) ? ((!empty($product->productDownload->date_expiration) && $product->productDownload->date_expiration != '0000-00-00 00:00:00') ? date('Y-m-d', strtotime($product->productDownload->date_expiration)) : '' ) : htmlentities(Tools::getValue('virtual_product_expiration_date'), ENT_COMPAT, 'UTF-8');
  2921. $product->productDownload->nb_days_accessible = ($product->productDownload->id > 0) ? $product->productDownload->nb_days_accessible : htmlentities(Tools::getValue('virtual_product_nb_days'), ENT_COMPAT, 'UTF-8');
  2922. $product->productDownload->is_shareable = $product->productDownload->id > 0 && $product->productDownload->is_shareable;
  2923. $iso_tiny_mce = $this->context->language->iso_code;
  2924. $iso_tiny_mce = (file_exists(_PS_JS_DIR_.'tiny_mce/langs/'.$iso_tiny_mce.'.js') ? $iso_tiny_mce : 'en');
  2925. $data->assign(array(
  2926. 'ad' => __PS_BASE_URI__.basename(_PS_ADMIN_DIR_),
  2927. 'iso_tiny_mce' => $iso_tiny_mce,
  2928. 'product' => $product,
  2929. 'token' => $this->token,
  2930. 'currency' => $currency,
  2931. 'link' => $this->context->link,
  2932. 'is_file' => $product->productDownload->checkFile(),
  2933. 'virtual_product_file_uploader' => $virtual_product_file_uploader->render()
  2934. ));
  2935. $data->assign($this->tpl_form_vars);
  2936. $this->tpl_form_vars['product'] = $product;
  2937. $this->tpl_form_vars['custom_form'] = $data->fetch();
  2938. }
  2939. protected function _getFinalPrice($specific_price, $product_price, $tax_rate)
  2940. {
  2941. return $this->object->getPrice(false, $specific_price['id_product_attribute'], 2);
  2942. }
  2943. protected function _displaySpecificPriceModificationForm($defaultCurrency, $shops, $currencies, $countries, $groups)
  2944. {
  2945. $content = '';
  2946. if (!($obj = $this->loadObject()))
  2947. return;
  2948. $specific_prices = SpecificPrice::getByProductId((int)$obj->id);
  2949. $specific_price_priorities = SpecificPrice::getPriority((int)$obj->id);
  2950. $tax_rate = $obj->getTaxesRate(Address::initialize());
  2951. $tmp = array();
  2952. foreach ($shops as $shop)
  2953. $tmp[$shop['id_shop']] = $shop;
  2954. $shops = $tmp;
  2955. $tmp = array();
  2956. foreach ($currencies as $currency)
  2957. $tmp[$currency['id_currency']] = $currency;
  2958. $currencies = $tmp;
  2959. $tmp = array();
  2960. foreach ($countries as $country)
  2961. $tmp[$country['id_country']] = $country;
  2962. $countries = $tmp;
  2963. $tmp = array();
  2964. foreach ($groups as $group)
  2965. $tmp[$group['id_group']] = $group;
  2966. $groups = $tmp;
  2967. if (!is_array($specific_prices) || !count($specific_prices))
  2968. $content .= '
  2969. <tr>
  2970. <td class="text-center" colspan="13"><i class="icon-warning-sign"></i>&nbsp;'.$this->l('No specific prices.').'</td>
  2971. </tr>';
  2972. else
  2973. {
  2974. $i = 0;
  2975. foreach ($specific_prices as $specific_price)
  2976. {
  2977. $current_specific_currency = $currencies[($specific_price['id_currency'] ? $specific_price['id_currency'] : $defaultCurrency->id)];
  2978. if ($specific_price['reduction_type'] == 'percentage')
  2979. $impact = '- '.($specific_price['reduction'] * 100).' %';
  2980. elseif ($specific_price['reduction'] > 0)
  2981. $impact = '- '.Tools::displayPrice(Tools::ps_round($specific_price['reduction'], 2), $current_specific_currency);
  2982. else
  2983. $impact = '--';
  2984. if ($specific_price['from'] == '0000-00-00 00:00:00' && $specific_price['to'] == '0000-00-00 00:00:00')
  2985. $period = $this->l('Unlimited');
  2986. else
  2987. $period = $this->l('From').' '.($specific_price['from'] != '0000-00-00 00:00:00' ? $specific_price['from'] : '0000-00-00 00:00:00').'<br />'.$this->l('To').' '.($specific_price['to'] != '0000-00-00 00:00:00' ? $specific_price['to'] : '0000-00-00 00:00:00');
  2988. if ($specific_price['id_product_attribute'])
  2989. {
  2990. $combination = new Combination((int)$specific_price['id_product_attribute']);
  2991. $attributes = $combination->getAttributesName((int)$this->context->language->id);
  2992. $attributes_name = '';
  2993. foreach ($attributes as $attribute)
  2994. $attributes_name .= $attribute['name'].' - ';
  2995. $attributes_name = rtrim($attributes_name, ' - ');
  2996. }
  2997. else
  2998. $attributes_name = $this->l('All combinations');
  2999. $rule = new SpecificPriceRule((int)$specific_price['id_specific_price_rule']);
  3000. $rule_name = ($rule->id ? $rule->name : '--');
  3001. if ($specific_price['id_customer'])
  3002. {
  3003. $customer = new Customer((int)$specific_price['id_customer']);
  3004. if (Validate::isLoadedObject($customer))
  3005. $customer_full_name = $customer->firstname.' '.$customer->lastname;
  3006. unset($customer);
  3007. }
  3008. if (!$specific_price['id_shop'] || in_array($specific_price['id_shop'], Shop::getContextListShopID()))
  3009. {
  3010. $content .= '
  3011. <tr '.($i % 2 ? 'class="alt_row"' : '').'>
  3012. <td>'.$rule_name.'</td>
  3013. <td>'.$attributes_name.'</td>';
  3014. $can_delete_specific_prices = true;
  3015. if (Shop::isFeatureActive())
  3016. {
  3017. $id_shop_sp = $specific_price['id_shop'];
  3018. $can_delete_specific_prices = (count($this->context->employee->getAssociatedShops()) > 1 && !$id_shop_sp) || $id_shop_sp;
  3019. $content .= '
  3020. <td>'.($id_shop_sp ? $shops[$id_shop_sp]['name'] : $this->l('All shops')).'</td>';
  3021. }
  3022. $price = Tools::ps_round($specific_price['price'], 2);
  3023. $fixed_price = ($price == Tools::ps_round($obj->price, 2) || $specific_price['price'] == -1) ? '--' : Tools::displayPrice($price, $current_specific_currency);
  3024. $content .= '
  3025. <td>'.($specific_price['id_currency'] ? $currencies[$specific_price['id_currency']]['name'] : $this->l('All currencies')).'</td>
  3026. <td>'.($specific_price['id_country'] ? $countries[$specific_price['id_country']]['name'] : $this->l('All countries')).'</td>
  3027. <td>'.($specific_price['id_group'] ? $groups[$specific_price['id_group']]['name'] : $this->l('All groups')).'</td>
  3028. <td title="'.$this->l('ID:').' '.$specific_price['id_customer'].'">'.(isset($customer_full_name) ? $customer_full_name : $this->l('All customers')).'</td>
  3029. <td>'.$fixed_price.'</td>
  3030. <td>'.$impact.'</td>
  3031. <td>'.$period.'</td>
  3032. <td>'.$specific_price['from_quantity'].'</th>
  3033. <td>'.((!$rule->id && $can_delete_specific_prices) ? '<a class="btn btn-default" name="delete_link" href="'.self::$currentIndex.'&id_product='.(int)Tools::getValue('id_product').'&action=deleteSpecificPrice&id_specific_price='.(int)($specific_price['id_specific_price']).'&token='.Tools::getValue('token').'"><i class="icon-trash"></i></a>': '').'</td>
  3034. </tr>';
  3035. $i++;
  3036. unset($customer_full_name);
  3037. }
  3038. }
  3039. }
  3040. $content .= '
  3041. </tbody>
  3042. </table>
  3043. </div>
  3044. <div class="panel-footer">
  3045. <a href="'.$this->context->link->getAdminLink('AdminProducts').'" class="btn btn-default"><i class="process-icon-cancel"></i> '.$this->l('Cancel').'</a>
  3046. <button id="product_form_submit_btn" type="submit" name="submitAddproduct" class="btn btn-default pull-right"><i class="process-icon-save"></i> '.$this->l('Save') .'</button>
  3047. <button id="product_form_submit_btn" type="submit" name="submitAddproductAndStay" class="btn btn-default pull-right"><i class="process-icon-save"></i> '.$this->l('Save and stay') .'</button>
  3048. </div>
  3049. </div>';
  3050. $content .= '
  3051. <script type="text/javascript">
  3052. var currencies = new Array();
  3053. currencies[0] = new Array();
  3054. currencies[0]["sign"] = "'.$defaultCurrency->sign.'";
  3055. currencies[0]["format"] = '.$defaultCurrency->format.';
  3056. ';
  3057. foreach ($currencies as $currency)
  3058. {
  3059. $content .= '
  3060. currencies['.$currency['id_currency'].'] = new Array();
  3061. currencies['.$currency['id_currency'].']["sign"] = "'.$currency['sign'].'";
  3062. currencies['.$currency['id_currency'].']["format"] = '.$currency['format'].';
  3063. ';
  3064. }
  3065. $content .= '
  3066. </script>
  3067. ';
  3068. // Not use id_customer
  3069. if ($specific_price_priorities[0] == 'id_customer')
  3070. unset($specific_price_priorities[0]);
  3071. // Reindex array starting from 0
  3072. $specific_price_priorities = array_values($specific_price_priorities);
  3073. $content .= '<div class="panel">
  3074. <h3>'.$this->l('Priority management').'</h3>
  3075. <div class="alert alert-info">
  3076. '.$this->l('Sometimes one customer can fit into multiple price rules. Priorities allow you to define which rule applies to the customer.').'
  3077. </div>';
  3078. $content .= '
  3079. <div class="form-group">
  3080. <label class="control-label col-lg-3" for="specificPricePriority1">'.$this->l('Priorities').'</label>
  3081. <div class="input-group col-lg-9">
  3082. <select id="specificPricePriority1" name="specificPricePriority[]">
  3083. <option value="id_shop"'.($specific_price_priorities[0] == 'id_shop' ? ' selected="selected"' : '').'>'.$this->l('Shop').'</option>
  3084. <option value="id_currency"'.($specific_price_priorities[0] == 'id_currency' ? ' selected="selected"' : '').'>'.$this->l('Currency').'</option>
  3085. <option value="id_country"'.($specific_price_priorities[0] == 'id_country' ? ' selected="selected"' : '').'>'.$this->l('Country').'</option>
  3086. <option value="id_group"'.($specific_price_priorities[0] == 'id_group' ? ' selected="selected"' : '').'>'.$this->l('Group').'</option>
  3087. </select>
  3088. <span class="input-group-addon"><i class="icon-chevron-right"></i></span>
  3089. <select name="specificPricePriority[]">
  3090. <option value="id_shop"'.($specific_price_priorities[1] == 'id_shop' ? ' selected="selected"' : '').'>'.$this->l('Shop').'</option>
  3091. <option value="id_currency"'.($specific_price_priorities[1] == 'id_currency' ? ' selected="selected"' : '').'>'.$this->l('Currency').'</option>
  3092. <option value="id_country"'.($specific_price_priorities[1] == 'id_country' ? ' selected="selected"' : '').'>'.$this->l('Country').'</option>
  3093. <option value="id_group"'.($specific_price_priorities[1] == 'id_group' ? ' selected="selected"' : '').'>'.$this->l('Group').'</option>
  3094. </select>
  3095. <span class="input-group-addon"><i class="icon-chevron-right"></i></span>
  3096. <select name="specificPricePriority[]">
  3097. <option value="id_shop"'.($specific_price_priorities[2] == 'id_shop' ? ' selected="selected"' : '').'>'.$this->l('Shop').'</option>
  3098. <option value="id_currency"'.($specific_price_priorities[2] == 'id_currency' ? ' selected="selected"' : '').'>'.$this->l('Currency').'</option>
  3099. <option value="id_country"'.($specific_price_priorities[2] == 'id_country' ? ' selected="selected"' : '').'>'.$this->l('Country').'</option>
  3100. <option value="id_group"'.($specific_price_priorities[2] == 'id_group' ? ' selected="selected"' : '').'>'.$this->l('Group').'</option>
  3101. </select>
  3102. <span class="input-group-addon"><i class="icon-chevron-right"></i></span>
  3103. <select name="specificPricePriority[]">
  3104. <option value="id_shop"'.($specific_price_priorities[3] == 'id_shop' ? ' selected="selected"' : '').'>'.$this->l('Shop').'</option>
  3105. <option value="id_currency"'.($specific_price_priorities[3] == 'id_currency' ? ' selected="selected"' : '').'>'.$this->l('Currency').'</option>
  3106. <option value="id_country"'.($specific_price_priorities[3] == 'id_country' ? ' selected="selected"' : '').'>'.$this->l('Country').'</option>
  3107. <option value="id_group"'.($specific_price_priorities[3] == 'id_group' ? ' selected="selected"' : '').'>'.$this->l('Group').'</option>
  3108. </select>
  3109. </div>
  3110. </div>
  3111. <div class="form-group">
  3112. <div class="col-lg-9 col-lg-offset-3">
  3113. <p class="checkbox">
  3114. <label for="specificPricePriorityToAll"><input type="checkbox" name="specificPricePriorityToAll" id="specificPricePriorityToAll" />'.$this->l('Apply to all products').'</label>
  3115. </p>
  3116. </div>
  3117. </div>
  3118. <div class="panel-footer">
  3119. <a href="'.$this->context->link->getAdminLink('AdminProducts').'" class="btn btn-default"><i class="process-icon-cancel"></i> '.$this->l('Cancel').'</a>
  3120. <button id="product_form_submit_btn" type="submit" name="submitAddproduct" class="btn btn-default pull-right"><i class="process-icon-save"></i> '.$this->l('Save') .'</button>
  3121. <button id="product_form_submit_btn" type="submit" name="submitAddproductAndStay" class="btn btn-default pull-right"><i class="process-icon-save"></i> '.$this->l('Save and stay') .'</button>
  3122. </div>
  3123. </div>
  3124. ';
  3125. return $content;
  3126. }
  3127. protected function _getCustomizationFieldIds($labels, $alreadyGenerated, $obj)
  3128. {
  3129. $customizableFieldIds = array();
  3130. if (isset($labels[Product::CUSTOMIZE_FILE]))
  3131. foreach ($labels[Product::CUSTOMIZE_FILE] as $id_customization_field => $label)
  3132. $customizableFieldIds[] = 'label_'.Product::CUSTOMIZE_FILE.'_'.(int)($id_customization_field);
  3133. if (isset($labels[Product::CUSTOMIZE_TEXTFIELD]))
  3134. foreach ($labels[Product::CUSTOMIZE_TEXTFIELD] as $id_customization_field => $label)
  3135. $customizableFieldIds[] = 'label_'.Product::CUSTOMIZE_TEXTFIELD.'_'.(int)($id_customization_field);
  3136. $j = 0;
  3137. for ($i = $alreadyGenerated[Product::CUSTOMIZE_FILE]; $i < (int)($this->getFieldValue($obj, 'uploadable_files')); $i++)
  3138. $customizableFieldIds[] = 'newLabel_'.Product::CUSTOMIZE_FILE.'_'.$j++;
  3139. $j = 0;
  3140. for ($i = $alreadyGenerated[Product::CUSTOMIZE_TEXTFIELD]; $i < (int)($this->getFieldValue($obj, 'text_fields')); $i++)
  3141. $customizableFieldIds[] = 'newLabel_'.Product::CUSTOMIZE_TEXTFIELD.'_'.$j++;
  3142. return implode('造', $customizableFieldIds);
  3143. }
  3144. protected function _displayLabelField(&$label, $languages, $default_language, $type, $fieldIds, $id_customization_field)
  3145. {
  3146. foreach ($languages as $language)
  3147. $input_value[$language['id_lang']] = (isset($label[(int)($language['id_lang'])])) ? $label[(int)($language['id_lang'])]['name'] : '';
  3148. $required = (isset($label[(int)($language['id_lang'])])) ? $label[(int)($language['id_lang'])]['required'] : false;
  3149. $template = $this->context->smarty->createTemplate('controllers/products/input_text_lang.tpl',
  3150. $this->context->smarty);
  3151. return '<div class="form-group">'
  3152. .'<div class="col-lg-6">'
  3153. .$template->assign(array(
  3154. 'languages' => $languages,
  3155. 'input_name' => 'label_'.$type.'_'.(int)($id_customization_field),
  3156. 'input_value' => $input_value
  3157. ))->fetch()
  3158. .'</div>'
  3159. .'<div class="col-lg-6">'
  3160. .'<div class="checkbox">'
  3161. .'<label for="require_'.$type.'_'.(int)($id_customization_field).'">'
  3162. .'<input type="checkbox" name="require_'.$type.'_'.(int)($id_customization_field).'" id="require_'.$type.'_'.(int)($id_customization_field).'" value="1" '.($required ? 'checked="checked"' : '').'/>'
  3163. .$this->l('Required')
  3164. .'</label>'
  3165. .'</div>'
  3166. .'</div>'
  3167. .'</div>';
  3168. }
  3169. protected function _displayLabelFields(&$obj, &$labels, $languages, $default_language, $type)
  3170. {
  3171. $content = '';
  3172. $type = (int)($type);
  3173. $labelGenerated = array(Product::CUSTOMIZE_FILE => (isset($labels[Product::CUSTOMIZE_FILE]) ? count($labels[Product::CUSTOMIZE_FILE]) : 0), Product::CUSTOMIZE_TEXTFIELD => (isset($labels[Product::CUSTOMIZE_TEXTFIELD]) ? count($labels[Product::CUSTOMIZE_TEXTFIELD]) : 0));
  3174. $fieldIds = $this->_getCustomizationFieldIds($labels, $labelGenerated, $obj);
  3175. if (isset($labels[$type]))
  3176. foreach ($labels[$type] as $id_customization_field => $label)
  3177. $content .= $this->_displayLabelField($label, $languages, $default_language, $type, $fieldIds, (int)($id_customization_field));
  3178. return $content;
  3179. }
  3180. public function initFormCustomization($obj)
  3181. {
  3182. $data = $this->createTemplate($this->tpl_form);
  3183. if ((bool)$obj->id)
  3184. {
  3185. if ($this->product_exists_in_shop)
  3186. {
  3187. $labels = $obj->getCustomizationFields();
  3188. $has_file_labels = (int)$this->getFieldValue($obj, 'uploadable_files');
  3189. $has_text_labels = (int)$this->getFieldValue($obj, 'text_fields');
  3190. $data->assign(array(
  3191. 'obj' => $obj,
  3192. 'table' => $this->table,
  3193. 'languages' => $this->_languages,
  3194. 'has_file_labels' => $has_file_labels,
  3195. 'display_file_labels' => $this->_displayLabelFields($obj, $labels, $this->_languages, Configuration::get('PS_LANG_DEFAULT'), Product::CUSTOMIZE_FILE),
  3196. 'has_text_labels' => $has_text_labels,
  3197. 'display_text_labels' => $this->_displayLabelFields($obj, $labels, $this->_languages, Configuration::get('PS_LANG_DEFAULT'), Product::CUSTOMIZE_TEXTFIELD),
  3198. 'uploadable_files' => (int)($this->getFieldValue($obj, 'uploadable_files') ? (int)$this->getFieldValue($obj, 'uploadable_files') : '0'),
  3199. 'text_fields' => (int)($this->getFieldValue($obj, 'text_fields') ? (int)$this->getFieldValue($obj, 'text_fields') : '0'),
  3200. ));
  3201. }
  3202. else
  3203. $this->displayWarning($this->l('You must save the product in this shop before adding customization.'));
  3204. }
  3205. else
  3206. $this->displayWarning($this->l('You must save this product before adding customization.'));
  3207. $this->tpl_form_vars['custom_form'] = $data->fetch();
  3208. }
  3209. public function initFormAttachments($obj)
  3210. {
  3211. if (!$this->default_form_language)
  3212. $this->getLanguages();
  3213. $data = $this->createTemplate($this->tpl_form);
  3214. $data->assign('default_form_language', $this->default_form_language);
  3215. if ((bool)$obj->id)
  3216. {
  3217. if ($this->product_exists_in_shop)
  3218. {
  3219. $attachment_name = array();
  3220. $attachment_description = array();
  3221. foreach ($this->_languages as $language)
  3222. {
  3223. $attachment_name[$language['id_lang']] = '';
  3224. $attachment_description[$language['id_lang']] = '';
  3225. }
  3226. $iso_tiny_mce = $this->context->language->iso_code;
  3227. $iso_tiny_mce = (file_exists(_PS_JS_DIR_.'tiny_mce/langs/'.$iso_tiny_mce.'.js') ? $iso_tiny_mce : 'en');
  3228. $attachment_uploader = new HelperUploader('attachment_file');
  3229. $attachment_uploader->setMultiple(false)->setUseAjax(true)->setUrl(
  3230. Context::getContext()->link->getAdminLink('AdminProducts').'&ajax=1&id_product='.(int)$obj->id
  3231. .'&action=AddAttachment')->setPostMaxSize((Configuration::get('PS_ATTACHMENT_MAXIMUM_SIZE') * 1024 * 1024))
  3232. ->setTemplate('attachment_ajax.tpl');
  3233. $data->assign(array(
  3234. 'obj' => $obj,
  3235. 'table' => $this->table,
  3236. 'ad' => __PS_BASE_URI__.basename(_PS_ADMIN_DIR_),
  3237. 'iso_tiny_mce' => $iso_tiny_mce,
  3238. 'languages' => $this->_languages,
  3239. 'id_lang' => $this->context->language->id,
  3240. 'attach1' => Attachment::getAttachments($this->context->language->id, $obj->id, true),
  3241. 'attach2' => Attachment::getAttachments($this->context->language->id, $obj->id, false),
  3242. 'default_form_language' => (int)Configuration::get('PS_LANG_DEFAULT'),
  3243. 'attachment_name' => $attachment_name,
  3244. 'attachment_description' => $attachment_description,
  3245. 'PS_ATTACHMENT_MAXIMUM_SIZE' => Configuration::get('PS_ATTACHMENT_MAXIMUM_SIZE'),
  3246. 'attachment_uploader' => $attachment_uploader->render()
  3247. ));
  3248. }
  3249. else
  3250. $this->displayWarning($this->l('You must save the product in this shop before adding attachements.'));
  3251. }
  3252. else
  3253. $this->displayWarning($this->l('You must save this product before adding attachements.'));
  3254. $this->tpl_form_vars['custom_form'] = $data->fetch();
  3255. }
  3256. public function initFormInformations($product)
  3257. {
  3258. if (!$this->default_form_language)
  3259. $this->getLanguages();
  3260. $data = $this->createTemplate($this->tpl_form);
  3261. $currency = $this->context->currency;
  3262. $data->assign('languages', $this->_languages);
  3263. $data->assign('default_form_language', $this->default_form_language);
  3264. $data->assign('currency', $currency);
  3265. $this->object = $product;
  3266. //$this->display = 'edit';
  3267. $data->assign('product_name_redirected', Product::getProductName((int)$product->id_product_redirected, null, (int)$this->context->language->id));
  3268. /*
  3269. * Form for adding a virtual product like software, mp3, etc...
  3270. */
  3271. $product_download = new ProductDownload();
  3272. if ($id_product_download = $product_download->getIdFromIdProduct($this->getFieldValue($product, 'id')))
  3273. $product_download = new ProductDownload($id_product_download);
  3274. $product->{'productDownload'} = $product_download;
  3275. $cache_default_attribute = (int)$this->getFieldValue($product, 'cache_default_attribute');
  3276. $product_props = array();
  3277. // global informations
  3278. array_push($product_props, 'reference', 'ean13', 'upc',
  3279. 'available_for_order', 'show_price', 'online_only',
  3280. 'id_manufacturer'
  3281. );
  3282. // specific / detailled information
  3283. array_push($product_props,
  3284. // physical product
  3285. 'width', 'height', 'weight', 'active',
  3286. // virtual product
  3287. 'is_virtual', 'cache_default_attribute',
  3288. // customization
  3289. 'uploadable_files', 'text_fields'
  3290. );
  3291. // prices
  3292. array_push($product_props,
  3293. 'price', 'wholesale_price', 'id_tax_rules_group', 'unit_price_ratio', 'on_sale',
  3294. 'unity', 'minimum_quantity', 'additional_shipping_cost',
  3295. 'available_now', 'available_later', 'available_date'
  3296. );
  3297. if (Configuration::get('PS_USE_ECOTAX'))
  3298. array_push($product_props, 'ecotax');
  3299. foreach ($product_props as $prop)
  3300. $product->$prop = $this->getFieldValue($product, $prop);
  3301. $product->name['class'] = 'updateCurrentText';
  3302. if (!$product->id || Configuration::get('PS_FORCE_FRIENDLY_PRODUCT'))
  3303. $product->name['class'] .= ' copy2friendlyUrl';
  3304. $images = Image::getImages($this->context->language->id, $product->id);
  3305. if (is_array($images))
  3306. {
  3307. foreach ($images as $k => $image)
  3308. $images[$k]['src'] = $this->context->link->getImageLink($product->link_rewrite[$this->context->language->id], $product->id.'-'.$image['id_image'], 'small_default');
  3309. $data->assign('images', $images);
  3310. }
  3311. $data->assign('imagesTypes', ImageType::getImagesTypes('products'));
  3312. $product->tags = Tag::getProductTags($product->id);
  3313. $data->assign('product_type', (int)Tools::getValue('type_product', $product->getType()));
  3314. $data->assign('is_in_pack', (int)Pack::isPacked($product->id));
  3315. $check_product_association_ajax = false;
  3316. if (Shop::isFeatureActive() && Shop::getContext() != Shop::CONTEXT_ALL)
  3317. $check_product_association_ajax = true;
  3318. // TinyMCE
  3319. $iso_tiny_mce = $this->context->language->iso_code;
  3320. $iso_tiny_mce = (file_exists(_PS_ROOT_DIR_.'/js/tiny_mce/langs/'.$iso_tiny_mce.'.js') ? $iso_tiny_mce : 'en');
  3321. $data->assign('ad', dirname($_SERVER['PHP_SELF']));
  3322. $data->assign('iso_tiny_mce', $iso_tiny_mce);
  3323. $data->assign('check_product_association_ajax', $check_product_association_ajax);
  3324. $data->assign('id_lang', $this->context->language->id);
  3325. $data->assign('product', $product);
  3326. $data->assign('token', $this->token);
  3327. $data->assign('currency', $currency);
  3328. $data->assign($this->tpl_form_vars);
  3329. $data->assign('link', $this->context->link);
  3330. $data->assign('PS_PRODUCT_SHORT_DESC_LIMIT', Configuration::get('PS_PRODUCT_SHORT_DESC_LIMIT') ? Configuration::get('PS_PRODUCT_SHORT_DESC_LIMIT') : 400);
  3331. $this->tpl_form_vars['product'] = $product;
  3332. $this->tpl_form_vars['custom_form'] = $data->fetch();
  3333. }
  3334. public function initFormShipping($obj)
  3335. {
  3336. $data = $this->createTemplate($this->tpl_form);
  3337. $data->assign(array(
  3338. 'product' => $obj,
  3339. 'ps_dimension_unit' => Configuration::get('PS_DIMENSION_UNIT'),
  3340. 'ps_weight_unit' => Configuration::get('PS_WEIGHT_UNIT'),
  3341. 'carrier_list' => $this->getCarrierList(),
  3342. 'currency' => $this->context->currency,
  3343. 'country_display_tax_label' => $this->context->country->display_tax_label
  3344. ));
  3345. $this->tpl_form_vars['custom_form'] = $data->fetch();
  3346. }
  3347. protected function getCarrierList()
  3348. {
  3349. $carrier_list = Carrier::getCarriers($this->context->language->id, false, false, false, null, Carrier::ALL_CARRIERS);
  3350. if ($product = $this->loadObject(true))
  3351. {
  3352. $carrier_selected_list = $product->getCarriers();
  3353. foreach ($carrier_list as &$carrier)
  3354. foreach ($carrier_selected_list as $carrier_selected)
  3355. if ($carrier_selected['id_reference'] == $carrier['id_reference'])
  3356. {
  3357. $carrier['selected'] = true;
  3358. continue;
  3359. }
  3360. }
  3361. return $carrier_list;
  3362. }
  3363. protected function addCarriers($product = null)
  3364. {
  3365. if (!isset($product))
  3366. $product = new Product((int)Tools::getValue('id_product'));
  3367. if (Validate::isLoadedObject($product))
  3368. {
  3369. $carriers = array();
  3370. if (Tools::getValue('selectedCarriers'))
  3371. $carriers = Tools::getValue('selectedCarriers');
  3372. $product->setCarriers($carriers);
  3373. }
  3374. }
  3375. public function ajaxProcessaddProductImage()
  3376. {
  3377. self::$currentIndex = 'index.php?tab=AdminProducts';
  3378. $product = new Product((int)Tools::getValue('id_product'));
  3379. $legends = Tools::getValue('legend');
  3380. if (!is_array($legends))
  3381. $legends = (array)$legends;
  3382. if (!Validate::isLoadedObject($product))
  3383. {
  3384. $files = array();
  3385. $files[0]['error'] = Tools::displayError('Cannot add image because product creation failed.');
  3386. }
  3387. $image_uploader = new HelperImageUploader('file');
  3388. $image_uploader->setAcceptTypes(array('jpeg', 'gif', 'png', 'jpg'))->setMaxSize($this->max_image_size);
  3389. $files = $image_uploader->process();
  3390. foreach ($files as &$file)
  3391. {
  3392. $image = new Image();
  3393. $image->id_product = (int)($product->id);
  3394. $image->position = Image::getHighestPosition($product->id) + 1;
  3395. foreach ($legends as $key => $legend)
  3396. if (!empty($legend))
  3397. $image->legend[(int)$key] = $legend;
  3398. if (!Image::getCover($image->id_product))
  3399. $image->cover = 1;
  3400. else
  3401. $image->cover = 0;
  3402. if (($validate = $image->validateFieldsLang(false, true)) !== true)
  3403. $file['error'] = Tools::displayError($validate);
  3404. if (isset($file['error']) && (!is_numeric($file['error']) || $file['error'] != 0))
  3405. continue;
  3406. if (!$image->add())
  3407. $file['error'] = Tools::displayError('Error while creating additional image');
  3408. else
  3409. {
  3410. if (!$new_path = $image->getPathForCreation())
  3411. {
  3412. $file['error'] = Tools::displayError('An error occurred during new folder creation');
  3413. continue;
  3414. }
  3415. $error = 0;
  3416. if (!ImageManager::resize($file['save_path'], $new_path.'.'.$image->image_format, null, null, 'jpg', false, $error))
  3417. {
  3418. switch ($error)
  3419. {
  3420. case ImageManager::ERROR_FILE_NOT_EXIST :
  3421. $file['error'] = Tools::displayError('An error occurred while copying image, the file does not exist anymore.');
  3422. break;
  3423. case ImageManager::ERROR_FILE_WIDTH :
  3424. $file['error'] = Tools::displayError('An error occurred while copying image, the file width is 0px.');
  3425. break;
  3426. case ImageManager::ERROR_MEMORY_LIMIT :
  3427. $file['error'] = Tools::displayError('An error occurred while copying image, check your memory limit.');
  3428. break;
  3429. default:
  3430. $file['error'] = Tools::displayError('An error occurred while copying image.');
  3431. break;
  3432. }
  3433. continue;
  3434. }
  3435. else
  3436. {
  3437. $imagesTypes = ImageType::getImagesTypes('products');
  3438. foreach ($imagesTypes as $imageType)
  3439. {
  3440. if (!ImageManager::resize($file['save_path'], $new_path.'-'.stripslashes($imageType['name']).'.'.$image->image_format, $imageType['width'], $imageType['height'], $image->image_format))
  3441. {
  3442. $file['error'] = Tools::displayError('An error occurred while copying image:').' '.stripslashes($imageType['name']);
  3443. continue;
  3444. }
  3445. }
  3446. }
  3447. unlink($file['save_path']);
  3448. //Necesary to prevent hacking
  3449. unset($file['save_path']);
  3450. Hook::exec('actionWatermark', array('id_image' => $image->id, 'id_product' => $product->id));
  3451. if (!$image->update())
  3452. {
  3453. $file['error'] = Tools::displayError('Error while updating status');
  3454. continue;
  3455. }
  3456. // Associate image to shop from context
  3457. $shops = Shop::getContextListShopID();
  3458. $image->associateTo($shops);
  3459. $json_shops = array();
  3460. foreach ($shops as $id_shop)
  3461. $json_shops[$id_shop] = true;
  3462. $file['status'] = 'ok';
  3463. $file['id'] = $image->id;
  3464. $file['position'] = $image->position;
  3465. $file['cover'] = $image->cover;
  3466. $file['legend'] = $image->legend;
  3467. $file['path'] = $image->getExistingImgPath();
  3468. $file['shops'] = $json_shops;
  3469. @unlink(_PS_TMP_IMG_DIR_.'product_'.(int)$product->id.'.jpg');
  3470. @unlink(_PS_TMP_IMG_DIR_.'product_mini_'.(int)$product->id.'_'.$this->context->shop->id.'.jpg');
  3471. }
  3472. }
  3473. die(Tools::jsonEncode(array($image_uploader->getName() => $files)));
  3474. }
  3475. public function initFormImages($obj)
  3476. {
  3477. $data = $this->createTemplate($this->tpl_form);
  3478. if ((bool)$obj->id)
  3479. {
  3480. if ($this->product_exists_in_shop)
  3481. {
  3482. $data->assign('product', $this->loadObject());
  3483. $shops = false;
  3484. if (Shop::isFeatureActive())
  3485. $shops = Shop::getShops();
  3486. if ($shops)
  3487. foreach ($shops as $key => $shop)
  3488. if (!$obj->isAssociatedToShop($shop['id_shop']))
  3489. unset($shops[$key]);
  3490. $data->assign('shops', $shops);
  3491. $count_images = Db::getInstance()->getValue('
  3492. SELECT COUNT(id_product)
  3493. FROM '._DB_PREFIX_.'image
  3494. WHERE id_product = '.(int)$obj->id
  3495. );
  3496. $images = Image::getImages($this->context->language->id, $obj->id);
  3497. foreach ($images as $k => $image)
  3498. $images[$k] = new Image($image['id_image']);
  3499. if ($this->context->shop->getContext() == Shop::CONTEXT_SHOP)
  3500. $current_shop_id = (int)$this->context->shop->id;
  3501. else
  3502. $current_shop_id = 0;
  3503. $languages = Language::getLanguages(true);
  3504. $image_uploader = new HelperImageUploader('file');
  3505. $image_uploader->setMultiple(!(Tools::getUserBrowser() == 'Apple Safari' && Tools::getUserPlatform() == 'Windows'))
  3506. ->setUseAjax(true)->setUrl(
  3507. Context::getContext()->link->getAdminLink('AdminProducts').'&ajax=1&id_product='.(int)$obj->id
  3508. .'&action=addProductImage');
  3509. $data->assign(array(
  3510. 'countImages' => $count_images,
  3511. 'id_product' => (int)Tools::getValue('id_product'),
  3512. 'id_category_default' => (int)$this->_category->id,
  3513. 'images' => $images,
  3514. 'iso_lang' => $languages[0]['iso_code'],
  3515. 'token' => $this->token,
  3516. 'table' => $this->table,
  3517. 'max_image_size' => $this->max_image_size / 1024 / 1024,
  3518. 'up_filename' => (string)Tools::getValue('virtual_product_filename_attribute'),
  3519. 'currency' => $this->context->currency,
  3520. 'current_shop_id' => $current_shop_id,
  3521. 'languages' => $this->_languages,
  3522. 'default_language' => (int)Configuration::get('PS_LANG_DEFAULT'),
  3523. 'image_uploader' => $image_uploader->render()
  3524. ));
  3525. $type = ImageType::getByNameNType('%', 'products', 'height');
  3526. if (isset($type['name']))
  3527. $data->assign('imageType', $type['name']);
  3528. else
  3529. $data->assign('imageType', 'small_default');
  3530. }
  3531. else
  3532. $this->displayWarning($this->l('You must save the product in this shop before adding images.'));
  3533. }
  3534. else
  3535. $this->displayWarning($this->l('You must save this product before adding images.'));
  3536. $this->tpl_form_vars['custom_form'] = $data->fetch();
  3537. }
  3538. public function initFormCombinations($obj)
  3539. {
  3540. return $this->initFormAttributes($obj);
  3541. }
  3542. public function initFormAttributes($product)
  3543. {
  3544. $data = $this->createTemplate($this->tpl_form);
  3545. if (!Combination::isFeatureActive())
  3546. $this->displayWarning($this->l('This feature has been disabled. ').
  3547. ' <a href="index.php?tab=AdminPerformance&token='.Tools::getAdminTokenLite('AdminPerformance').'#featuresDetachables">'.$this->l('Performances').'</a>');
  3548. else if (Validate::isLoadedObject($product))
  3549. {
  3550. if ($this->product_exists_in_shop)
  3551. {
  3552. if ($product->is_virtual)
  3553. {
  3554. $data->assign('product', $product);
  3555. $this->displayWarning($this->l('A virtual product cannot have combinations.'));
  3556. }
  3557. else
  3558. {
  3559. $attribute_js = array();
  3560. $attributes = Attribute::getAttributes($this->context->language->id, true);
  3561. foreach ($attributes as $k => $attribute)
  3562. $attribute_js[$attribute['id_attribute_group']][$attribute['id_attribute']] = $attribute['name'];
  3563. $currency = $this->context->currency;
  3564. $data->assign('attributeJs', $attribute_js);
  3565. $data->assign('attributes_groups', AttributeGroup::getAttributesGroups($this->context->language->id));
  3566. $data->assign('currency', $currency);
  3567. $images = Image::getImages($this->context->language->id, $product->id);
  3568. $data->assign('tax_exclude_option', Tax::excludeTaxeOption());
  3569. $data->assign('ps_weight_unit', Configuration::get('PS_WEIGHT_UNIT'));
  3570. $data->assign('ps_use_ecotax', Configuration::get('PS_USE_ECOTAX'));
  3571. $data->assign('field_value_unity', $this->getFieldValue($product, 'unity'));
  3572. $data->assign('reasons', $reasons = StockMvtReason::getStockMvtReasons($this->context->language->id));
  3573. $data->assign('ps_stock_mvt_reason_default', $ps_stock_mvt_reason_default = Configuration::get('PS_STOCK_MVT_REASON_DEFAULT'));
  3574. $data->assign('minimal_quantity', $this->getFieldValue($product, 'minimal_quantity') ? $this->getFieldValue($product, 'minimal_quantity') : 1);
  3575. $data->assign('available_date', ($this->getFieldValue($product, 'available_date') != 0) ? stripslashes(htmlentities($this->getFieldValue($product, 'available_date'), $this->context->language->id)) : '0000-00-00');
  3576. $i = 0;
  3577. $type = ImageType::getByNameNType('%', 'products', 'height');
  3578. if (isset($type['name']))
  3579. $data->assign('imageType', $type['name']);
  3580. else
  3581. $data->assign('imageType', 'small_default');
  3582. $data->assign('imageWidth', (isset($image_type['width']) ? (int)($image_type['width']) : 64) + 25);
  3583. foreach ($images as $k => $image)
  3584. {
  3585. $images[$k]['obj'] = new Image($image['id_image']);
  3586. ++$i;
  3587. }
  3588. $data->assign('images', $images);
  3589. $data->assign($this->tpl_form_vars);
  3590. $data->assign(array(
  3591. 'list' => $this->renderListAttributes($product, $currency),
  3592. 'product' => $product,
  3593. 'id_category' => $product->getDefaultCategory(),
  3594. 'token_generator' => Tools::getAdminTokenLite('AdminAttributeGenerator'),
  3595. 'combination_exists' => (Shop::isFeatureActive() && (Shop::getContextShopGroup()->share_stock) && count(AttributeGroup::getAttributesGroups($this->context->language->id)) > 0 && $product->hasAttributes())
  3596. ));
  3597. }
  3598. }
  3599. else
  3600. $this->displayWarning($this->l('You must save the product in this shop before adding combinations.'));
  3601. }
  3602. else
  3603. {
  3604. $data->assign('product', $product);
  3605. $this->displayWarning($this->l('You must save this product before adding combinations.'));
  3606. }
  3607. $this->tpl_form_vars['custom_form'] = $data->fetch();
  3608. }
  3609. public function renderListAttributes($product, $currency)
  3610. {
  3611. $this->bulk_actions = array('delete' => array('text' => $this->l('Delete selected'), 'confirm' => $this->l('Delete selected items?')));
  3612. $this->addRowAction('edit');
  3613. $this->addRowAction('default');
  3614. $this->addRowAction('delete');
  3615. $default_class = 'highlighted';
  3616. $this->fields_list = array(
  3617. 'attributes' => array('title' => $this->l('Attribute - value pair'), 'align' => 'left'),
  3618. 'price' => array('title' => $this->l('Impact on price'), 'type' => 'price', 'align' => 'left'),
  3619. 'weight' => array('title' => $this->l('Impact on weight'), 'align' => 'left'),
  3620. 'reference' => array('title' => $this->l('Reference'), 'align' => 'left'),
  3621. 'ean13' => array('title' => $this->l('EAN-13'), 'align' => 'left'),
  3622. 'upc' => array('title' => $this->l('UPC'), 'align' => 'left')
  3623. );
  3624. if ($product->id)
  3625. {
  3626. /* Build attributes combinations */
  3627. $combinations = $product->getAttributeCombinations($this->context->language->id);
  3628. $groups = array();
  3629. $comb_array = array();
  3630. if (is_array($combinations))
  3631. {
  3632. $combination_images = $product->getCombinationImages($this->context->language->id);
  3633. foreach ($combinations as $k => $combination)
  3634. {
  3635. $price_to_convert = Tools::convertPrice($combination['price'], $currency);
  3636. $price = Tools::displayPrice($price_to_convert, $currency);
  3637. $comb_array[$combination['id_product_attribute']]['id_product_attribute'] = $combination['id_product_attribute'];
  3638. $comb_array[$combination['id_product_attribute']]['attributes'][] = array($combination['group_name'], $combination['attribute_name'], $combination['id_attribute']);
  3639. $comb_array[$combination['id_product_attribute']]['wholesale_price'] = $combination['wholesale_price'];
  3640. $comb_array[$combination['id_product_attribute']]['price'] = $price;
  3641. $comb_array[$combination['id_product_attribute']]['weight'] = $combination['weight'].Configuration::get('PS_WEIGHT_UNIT');
  3642. $comb_array[$combination['id_product_attribute']]['unit_impact'] = $combination['unit_price_impact'];
  3643. $comb_array[$combination['id_product_attribute']]['reference'] = $combination['reference'];
  3644. $comb_array[$combination['id_product_attribute']]['ean13'] = $combination['ean13'];
  3645. $comb_array[$combination['id_product_attribute']]['upc'] = $combination['upc'];
  3646. $comb_array[$combination['id_product_attribute']]['id_image'] = isset($combination_images[$combination['id_product_attribute']][0]['id_image']) ? $combination_images[$combination['id_product_attribute']][0]['id_image'] : 0;
  3647. $comb_array[$combination['id_product_attribute']]['available_date'] = strftime($combination['available_date']);
  3648. $comb_array[$combination['id_product_attribute']]['default_on'] = $combination['default_on'];
  3649. if ($combination['is_color_group'])
  3650. $groups[$combination['id_attribute_group']] = $combination['group_name'];
  3651. }
  3652. }
  3653. $irow = 0;
  3654. if (isset($comb_array))
  3655. {
  3656. foreach ($comb_array as $id_product_attribute => $product_attribute)
  3657. {
  3658. $list = '';
  3659. /* In order to keep the same attributes order */
  3660. asort($product_attribute['attributes']);
  3661. foreach ($product_attribute['attributes'] as $attribute)
  3662. $list .= $attribute[0].' - '.$attribute[1].', ';
  3663. $list = rtrim($list, ', ');
  3664. $comb_array[$id_product_attribute]['image'] = $product_attribute['id_image'] ? new Image($product_attribute['id_image']) : false;
  3665. $comb_array[$id_product_attribute]['available_date'] = $product_attribute['available_date'] != 0 ? date('Y-m-d', strtotime($product_attribute['available_date'])) : '0000-00-00';
  3666. $comb_array[$id_product_attribute]['attributes'] = $list;
  3667. $comb_array[$id_product_attribute]['name'] = $list;
  3668. if ($product_attribute['default_on'])
  3669. $comb_array[$id_product_attribute]['class'] = $default_class;
  3670. }
  3671. }
  3672. }
  3673. foreach ($this->actions_available as $action)
  3674. {
  3675. if (!in_array($action, $this->actions) && isset($this->$action) && $this->$action)
  3676. $this->actions[] = $action;
  3677. }
  3678. $helper = new HelperList();
  3679. $helper->identifier = 'id_product_attribute';
  3680. $helper->table_id = 'combinations-list';
  3681. $helper->token = $this->token;
  3682. $helper->currentIndex = self::$currentIndex;
  3683. $helper->no_link = true;
  3684. $helper->simple_header = true;
  3685. $helper->show_toolbar = false;
  3686. $helper->shopLinkType = $this->shopLinkType;
  3687. $helper->actions = $this->actions;
  3688. $helper->list_skip_actions = $this->list_skip_actions;
  3689. $helper->colorOnBackground = true;
  3690. $helper->override_folder = $this->tpl_folder.'combination/';
  3691. return $helper->generateList($comb_array, $this->fields_list);
  3692. }
  3693. public function initFormQuantities($obj)
  3694. {
  3695. if (!$this->default_form_language)
  3696. $this->getLanguages();
  3697. $data = $this->createTemplate($this->tpl_form);
  3698. $data->assign('default_form_language', $this->default_form_language);
  3699. if ($obj->id)
  3700. {
  3701. if ($this->product_exists_in_shop)
  3702. {
  3703. // Get all id_product_attribute
  3704. $attributes = $obj->getAttributesResume($this->context->language->id);
  3705. if (empty($attributes))
  3706. $attributes[] = array(
  3707. 'id_product_attribute' => 0,
  3708. 'attribute_designation' => ''
  3709. );
  3710. // Get available quantities
  3711. $available_quantity = array();
  3712. $product_designation = array();
  3713. foreach ($attributes as $attribute)
  3714. {
  3715. // Get available quantity for the current product attribute in the current shop
  3716. $available_quantity[$attribute['id_product_attribute']] = StockAvailable::getQuantityAvailableByProduct((int)$obj->id,
  3717. $attribute['id_product_attribute']);
  3718. // Get all product designation
  3719. $product_designation[$attribute['id_product_attribute']] = rtrim(
  3720. $obj->name[$this->context->language->id].' - '.$attribute['attribute_designation'],
  3721. ' - '
  3722. );
  3723. }
  3724. $show_quantities = true;
  3725. $shop_context = Shop::getContext();
  3726. $shop_group = new ShopGroup((int)Shop::getContextShopGroupID());
  3727. // if we are in all shops context, it's not possible to manage quantities at this level
  3728. if (Shop::isFeatureActive() && $shop_context == Shop::CONTEXT_ALL)
  3729. $show_quantities = false;
  3730. // if we are in group shop context
  3731. elseif (Shop::isFeatureActive() && $shop_context == Shop::CONTEXT_GROUP)
  3732. {
  3733. // if quantities are not shared between shops of the group, it's not possible to manage them at group level
  3734. if (!$shop_group->share_stock)
  3735. $show_quantities = false;
  3736. }
  3737. // if we are in shop context
  3738. else
  3739. {
  3740. // if quantities are shared between shops of the group, it's not possible to manage them for a given shop
  3741. if ($shop_group->share_stock)
  3742. $show_quantities = false;
  3743. }
  3744. $data->assign('ps_stock_management', Configuration::get('PS_STOCK_MANAGEMENT'));
  3745. $data->assign('has_attribute', $obj->hasAttributes());
  3746. // Check if product has combination, to display the available date only for the product or for each combination
  3747. if (Combination::isFeatureActive())
  3748. $data->assign('countAttributes', (int)Db::getInstance()->getValue('SELECT COUNT(id_product) FROM '._DB_PREFIX_.'product_attribute WHERE id_product = '.(int)$obj->id));
  3749. else
  3750. $data->assign('countAttributes', false);
  3751. // if advanced stock management is active, checks associations
  3752. $advanced_stock_management_warning = false;
  3753. if (Configuration::get('PS_ADVANCED_STOCK_MANAGEMENT') && $obj->advanced_stock_management)
  3754. {
  3755. $p_attributes = Product::getProductAttributesIds($obj->id);
  3756. $warehouses = array();
  3757. if (!$p_attributes)
  3758. $warehouses[] = Warehouse::getProductWarehouseList($obj->id, 0);
  3759. foreach ($p_attributes as $p_attribute)
  3760. {
  3761. $ws = Warehouse::getProductWarehouseList($obj->id, $p_attribute['id_product_attribute']);
  3762. if ($ws)
  3763. $warehouses[] = $ws;
  3764. }
  3765. $warehouses = Tools::arrayUnique($warehouses);
  3766. if (empty($warehouses))
  3767. $advanced_stock_management_warning = true;
  3768. }
  3769. if ($advanced_stock_management_warning)
  3770. {
  3771. $this->displayWarning($this->l('If you wish to use the advanced stock management, you must:'));
  3772. $this->displayWarning('- '.$this->l('associate your products with warehouses.'));
  3773. $this->displayWarning('- '.$this->l('associate your warehouses with carriers.'));
  3774. $this->displayWarning('- '.$this->l('associate your warehouses with the appropriate shops.'));
  3775. }
  3776. $pack_quantity = null;
  3777. // if product is a pack
  3778. if (Pack::isPack($obj->id))
  3779. {
  3780. $items = Pack::getItems((int)$obj->id, Configuration::get('PS_LANG_DEFAULT'));
  3781. // gets an array of quantities (quantity for the product / quantity in pack)
  3782. $pack_quantities = array();
  3783. foreach ($items as $item)
  3784. {
  3785. if (!$item->isAvailableWhenOutOfStock((int)$item->out_of_stock))
  3786. {
  3787. $pack_id_product_attribute = Product::getDefaultAttribute($item->id, 1);
  3788. $pack_quantities[] = Product::getQuantity($item->id, $pack_id_product_attribute) / ($item->pack_quantity !== 0 ? $item->pack_quantity : 1);
  3789. }
  3790. }
  3791. // gets the minimum
  3792. if (count($pack_quantities))
  3793. {
  3794. $pack_quantity = $pack_quantities[0];
  3795. foreach ($pack_quantities as $value)
  3796. {
  3797. if ($pack_quantity > $value)
  3798. $pack_quantity = $value;
  3799. }
  3800. }
  3801. if (!Warehouse::getPackWarehouses((int)$obj->id))
  3802. $this->displayWarning($this->l('You must have a common warehouse between this pack and its product.'));
  3803. }
  3804. $data->assign(array(
  3805. 'attributes' => $attributes,
  3806. 'available_quantity' => $available_quantity,
  3807. 'pack_quantity' => $pack_quantity,
  3808. 'stock_management_active' => Configuration::get('PS_ADVANCED_STOCK_MANAGEMENT'),
  3809. 'product_designation' => $product_designation,
  3810. 'product' => $obj,
  3811. 'show_quantities' => $show_quantities,
  3812. 'order_out_of_stock' => Configuration::get('PS_ORDER_OUT_OF_STOCK'),
  3813. 'token_preferences' => Tools::getAdminTokenLite('AdminPPreferences'),
  3814. 'token' => $this->token,
  3815. 'languages' => $this->_languages,
  3816. 'id_lang' => $this->context->language->id
  3817. ));
  3818. }
  3819. else
  3820. $this->displayWarning($this->l('You must save the product in this shop before managing quantities.'));
  3821. }
  3822. else
  3823. $this->displayWarning($this->l('You must save this product before managing quantities.'));
  3824. $this->tpl_form_vars['custom_form'] = $data->fetch();
  3825. }
  3826. public function initFormSuppliers($obj)
  3827. {
  3828. $data = $this->createTemplate($this->tpl_form);
  3829. if ($obj->id)
  3830. {
  3831. if ($this->product_exists_in_shop)
  3832. {
  3833. // Get all id_product_attribute
  3834. $attributes = $obj->getAttributesResume($this->context->language->id);
  3835. if (empty($attributes))
  3836. $attributes[] = array(
  3837. 'id_product' => $obj->id,
  3838. 'id_product_attribute' => 0,
  3839. 'attribute_designation' => ''
  3840. );
  3841. $product_designation = array();
  3842. foreach ($attributes as $attribute)
  3843. $product_designation[$attribute['id_product_attribute']] = rtrim(
  3844. $obj->name[$this->context->language->id].' - '.$attribute['attribute_designation'],
  3845. ' - '
  3846. );
  3847. // Get all available suppliers
  3848. $suppliers = Supplier::getSuppliers();
  3849. // Get already associated suppliers
  3850. $associated_suppliers = ProductSupplier::getSupplierCollection($obj->id);
  3851. // Get already associated suppliers and force to retreive product declinaisons
  3852. $product_supplier_collection = ProductSupplier::getSupplierCollection($obj->id, false);
  3853. $default_supplier = 0;
  3854. foreach ($suppliers as &$supplier)
  3855. {
  3856. $supplier['is_selected'] = false;
  3857. $supplier['is_default'] = false;
  3858. foreach ($associated_suppliers as $associated_supplier)
  3859. if ($associated_supplier->id_supplier == $supplier['id_supplier'])
  3860. {
  3861. $associated_supplier->name = $supplier['name'];
  3862. $supplier['is_selected'] = true;
  3863. if ($obj->id_supplier == $supplier['id_supplier'])
  3864. {
  3865. $supplier['is_default'] = true;
  3866. $default_supplier = $supplier['id_supplier'];
  3867. }
  3868. }
  3869. }
  3870. $data->assign(array(
  3871. 'attributes' => $attributes,
  3872. 'suppliers' => $suppliers,
  3873. 'default_supplier' => $default_supplier,
  3874. 'associated_suppliers' => $associated_suppliers,
  3875. 'associated_suppliers_collection' => $product_supplier_collection,
  3876. 'product_designation' => $product_designation,
  3877. 'currencies' => Currency::getCurrencies(),
  3878. 'product' => $obj,
  3879. 'link' => $this->context->link,
  3880. 'token' => $this->token,
  3881. 'id_default_currency' => Configuration::get('PS_CURRENCY_DEFAULT'),
  3882. ));
  3883. }
  3884. else
  3885. $this->displayWarning($this->l('You must save the product in this shop before managing suppliers.'));
  3886. }
  3887. else
  3888. $this->displayWarning($this->l('You must save this product before managing suppliers.'));
  3889. $this->tpl_form_vars['custom_form'] = $data->fetch();
  3890. }
  3891. public function initFormWarehouses($obj)
  3892. {
  3893. $data = $this->createTemplate($this->tpl_form);
  3894. if ($obj->id)
  3895. {
  3896. if ($this->product_exists_in_shop)
  3897. {
  3898. // Get all id_product_attribute
  3899. $attributes = $obj->getAttributesResume($this->context->language->id);
  3900. if (empty($attributes))
  3901. $attributes[] = array(
  3902. 'id_product' => $obj->id,
  3903. 'id_product_attribute' => 0,
  3904. 'attribute_designation' => ''
  3905. );
  3906. $product_designation = array();
  3907. foreach ($attributes as $attribute)
  3908. $product_designation[$attribute['id_product_attribute']] = rtrim(
  3909. $obj->name[$this->context->language->id].' - '.$attribute['attribute_designation'],
  3910. ' - '
  3911. );
  3912. // Get all available warehouses
  3913. $warehouses = Warehouse::getWarehouses(true);
  3914. // Get already associated warehouses
  3915. $associated_warehouses_collection = WarehouseProductLocation::getCollection($obj->id);
  3916. $data->assign(array(
  3917. 'attributes' => $attributes,
  3918. 'warehouses' => $warehouses,
  3919. 'associated_warehouses' => $associated_warehouses_collection,
  3920. 'product_designation' => $product_designation,
  3921. 'product' => $obj,
  3922. 'link' => $this->context->link,
  3923. 'token' => $this->token
  3924. ));
  3925. }
  3926. else
  3927. $this->displayWarning($this->l('You must save the product in this shop before managing warehouses.'));
  3928. }
  3929. else
  3930. $this->displayWarning($this->l('You must save this product before managing warehouses.'));
  3931. $this->tpl_form_vars['custom_form'] = $data->fetch();
  3932. }
  3933. public function initFormFeatures($obj)
  3934. {
  3935. if (!$this->default_form_language)
  3936. $this->getLanguages();
  3937. $data = $this->createTemplate($this->tpl_form);
  3938. $data->assign('default_form_language', $this->default_form_language);
  3939. if (!Feature::isFeatureActive())
  3940. $this->displayWarning($this->l('This feature has been disabled. ').' <a href="index.php?tab=AdminPerformance&token='.Tools::getAdminTokenLite('AdminPerformance').'#featuresDetachables">'.$this->l('Performances').'</a>');
  3941. else
  3942. {
  3943. if ($obj->id)
  3944. {
  3945. if ($this->product_exists_in_shop)
  3946. {
  3947. $features = Feature::getFeatures($this->context->language->id, (Shop::isFeatureActive() && Shop::getContext() == Shop::CONTEXT_SHOP));
  3948. foreach ($features as $k => $tab_features)
  3949. {
  3950. $features[$k]['current_item'] = false;
  3951. $features[$k]['val'] = array();
  3952. $custom = true;
  3953. foreach ($obj->getFeatures() as $tab_products)
  3954. if ($tab_products['id_feature'] == $tab_features['id_feature'])
  3955. $features[$k]['current_item'] = $tab_products['id_feature_value'];
  3956. $features[$k]['featureValues'] = FeatureValue::getFeatureValuesWithLang($this->context->language->id, (int)$tab_features['id_feature']);
  3957. if (count($features[$k]['featureValues']))
  3958. foreach ($features[$k]['featureValues'] as $value)
  3959. if ($features[$k]['current_item'] == $value['id_feature_value'])
  3960. $custom = false;
  3961. if ($custom)
  3962. $features[$k]['val'] = FeatureValue::getFeatureValueLang($features[$k]['current_item']);
  3963. }
  3964. $data->assign('available_features', $features);
  3965. $data->assign('product', $obj);
  3966. $data->assign('link', $this->context->link);
  3967. $data->assign('languages', $this->_languages);
  3968. $data->assign('default_form_language', $this->default_form_language);
  3969. }
  3970. else
  3971. $this->displayWarning($this->l('You must save the product in this shop before adding features.'));
  3972. }
  3973. else
  3974. $this->displayWarning($this->l('You must save this product before adding features.'));
  3975. }
  3976. $this->tpl_form_vars['custom_form'] = $data->fetch();
  3977. }
  3978. public function ajaxProcessProductQuantity()
  3979. {
  3980. if (!Tools::getValue('actionQty'))
  3981. return Tools::jsonEncode(array('error' => $this->l('Undefined action')));
  3982. $product = new Product((int)Tools::getValue('id_product'), true);
  3983. switch (Tools::getValue('actionQty'))
  3984. {
  3985. case 'depends_on_stock':
  3986. if (Tools::getValue('value') === false)
  3987. die (Tools::jsonEncode(array('error' => $this->l('Undefined value'))));
  3988. if ((int)Tools::getValue('value') != 0 && (int)Tools::getValue('value') != 1)
  3989. die (Tools::jsonEncode(array('error' => $this->l('Incorrect value'))));
  3990. if (!$product->advanced_stock_management && (int)Tools::getValue('value') == 1)
  3991. die (Tools::jsonEncode(array('error' => $this->l('Not possible if advanced stock management is disabled. '))));
  3992. if ($product->advanced_stock_management && Pack::isPack($product->id))
  3993. die (Tools::jsonEncode(array('error' => $this->l('Not possible if the product is a pack.'))));
  3994. StockAvailable::setProductDependsOnStock($product->id, (int)Tools::getValue('value'));
  3995. break;
  3996. case 'out_of_stock':
  3997. if (Tools::getValue('value') === false)
  3998. die (Tools::jsonEncode(array('error' => $this->l('Undefined value'))));
  3999. if (!in_array((int)Tools::getValue('value'), array(0, 1, 2)))
  4000. die (Tools::jsonEncode(array('error' => $this->l('Incorrect value'))));
  4001. StockAvailable::setProductOutOfStock($product->id, (int)Tools::getValue('value'));
  4002. break;
  4003. case 'set_qty':
  4004. if (Tools::getValue('value') === false || (!is_numeric(trim(Tools::getValue('value')))))
  4005. die (Tools::jsonEncode(array('error' => $this->l('Undefined value'))));
  4006. if (Tools::getValue('id_product_attribute') === false)
  4007. die (Tools::jsonEncode(array('error' => $this->l('Undefined id product attribute'))));
  4008. StockAvailable::setQuantity($product->id, (int)Tools::getValue('id_product_attribute'), (int)Tools::getValue('value'));
  4009. Hook::exec('actionProductUpdate', array('product' => $this->object));
  4010. // Catch potential echo from modules
  4011. $error = ob_get_contents();
  4012. if (!empty($error))
  4013. {
  4014. ob_end_clean();
  4015. die (Tools::jsonEncode(array('error' => $error)));
  4016. }
  4017. break;
  4018. case 'advanced_stock_management' :
  4019. if (Tools::getValue('value') === false)
  4020. die (Tools::jsonEncode(array('error' => $this->l('Undefined value'))));
  4021. if ((int)Tools::getValue('value') != 1 && (int)Tools::getValue('value') != 0)
  4022. die (Tools::jsonEncode(array('error' => $this->l('Incorrect value'))));
  4023. if (!Configuration::get('PS_ADVANCED_STOCK_MANAGEMENT') && (int)Tools::getValue('value') == 1)
  4024. die (Tools::jsonEncode(array('error' => $this->l('Not possible if advanced stock management is disabled. '))));
  4025. if (Configuration::get('PS_ADVANCED_STOCK_MANAGEMENT') && Pack::isPack($product->id))
  4026. die (Tools::jsonEncode(array('error' => $this->l('Not possible if the product is a pack.'))));
  4027. $product->setAdvancedStockManagement((int)Tools::getValue('value'));
  4028. if (StockAvailable::dependsOnStock($product->id) == 1 && (int)Tools::getValue('value') == 0)
  4029. StockAvailable::setProductDependsOnStock($product->id, 0);
  4030. break;
  4031. }
  4032. die(Tools::jsonEncode(array('error' => false)));
  4033. }
  4034. public function getCombinationImagesJS()
  4035. {
  4036. if (!($obj = $this->loadObject(true)))
  4037. return;
  4038. $content = 'var combination_images = new Array();';
  4039. if (!$allCombinationImages = $obj->getCombinationImages($this->context->language->id))
  4040. return $content;
  4041. foreach ($allCombinationImages as $id_product_attribute => $combination_images)
  4042. {
  4043. $i = 0;
  4044. $content .= 'combination_images['.(int)$id_product_attribute.'] = new Array();';
  4045. foreach ($combination_images as $combination_image)
  4046. $content .= 'combination_images['.(int)$id_product_attribute.']['.$i++.'] = '.(int)$combination_image['id_image'].';';
  4047. }
  4048. return $content;
  4049. }
  4050. public function haveThisAccessory($accessory_id, $accessories)
  4051. {
  4052. foreach ($accessories as $accessory)
  4053. if ((int)$accessory['id_product'] == (int)$accessory_id)
  4054. return true;
  4055. return false;
  4056. }
  4057. protected function initPack(Product $product)
  4058. {
  4059. $this->tpl_form_vars['is_pack'] = ($product->id && Pack::isPack($product->id)) || Tools::getValue('type_product') == Product::PTYPE_PACK;
  4060. $product->packItems = Pack::getItems($product->id, $this->context->language->id);
  4061. $input_pack_items = '';
  4062. if (Tools::getValue('inputPackItems'))
  4063. $input_pack_items = Tools::getValue('inputPackItems');
  4064. else
  4065. foreach ($product->packItems as $pack_item)
  4066. $input_pack_items .= $pack_item->pack_quantity.'x'.$pack_item->id.'-';
  4067. $this->tpl_form_vars['input_pack_items'] = $input_pack_items;
  4068. $input_namepack_items = '';
  4069. if (Tools::getValue('namePackItems'))
  4070. $input_namepack_items = Tools::getValue('namePackItems');
  4071. else
  4072. foreach ($product->packItems as $pack_item)
  4073. $input_namepack_items .= $pack_item->pack_quantity.' x '.$pack_item->name.'造';
  4074. $this->tpl_form_vars['input_namepack_items'] = $input_namepack_items;
  4075. }
  4076. /**
  4077. * AdminProducts display hook
  4078. */
  4079. public function initFormModules($obj)
  4080. {
  4081. $id_module = Db::getInstance()->getValue('SELECT `id_module` FROM `'._DB_PREFIX_.'module` WHERE `name` = \''.pSQL($this->tab_display_module).'\'');
  4082. $this->tpl_form_vars['custom_form'] = Hook::exec('displayAdminProductsExtra', array(), (int)$id_module);
  4083. }
  4084. /**
  4085. * delete all items in pack, then check if type_product value is 2.
  4086. * if yes, add the pack items from input "inputPackItems"
  4087. *
  4088. * @param Product $product
  4089. * @return boolean
  4090. */
  4091. public function updatePackItems($product)
  4092. {
  4093. Pack::deleteItems($product->id);
  4094. // lines format: QTY x ID-QTY x ID
  4095. if (Tools::getValue('type_product') == Product::PTYPE_PACK)
  4096. {
  4097. $product->setDefaultAttribute(0);//reset cache_default_attribute
  4098. $items = Tools::getValue('inputPackItems');
  4099. $lines = array_unique(explode('-', $items));
  4100. // lines is an array of string with format : QTYxID
  4101. if (count($lines))
  4102. foreach ($lines as $line)
  4103. if (!empty($line))
  4104. {
  4105. list($qty, $item_id) = explode('x', $line);
  4106. if ($qty > 0 && isset($item_id))
  4107. {
  4108. if (Pack::isPack((int)$item_id))
  4109. $this->errors[] = Tools::displayError('You can\'t add product packs into a pack');
  4110. elseif (!Pack::addItem((int)$product->id, (int)$item_id, (int)$qty))
  4111. $this->errors[] = Tools::displayError('An error occurred while attempting to add products to the pack.');
  4112. }
  4113. }
  4114. }
  4115. }
  4116. public function getL($key)
  4117. {
  4118. $trad = array(
  4119. 'Default category:' => $this->l('Default category'),
  4120. 'Catalog:' => $this->l('Catalog'),
  4121. 'Consider changing the default category.' => $this->l('Consider changing the default category.'),
  4122. 'ID' => $this->l('ID'),
  4123. 'Name' => $this->l('Name'),
  4124. 'Mark all checkbox(es) of categories in which product is to appear' => $this->l('Mark the checkbox of each categories in which this product will appear.')
  4125. );
  4126. return $trad[$key];
  4127. }
  4128. protected function _displayUnavailableProductWarning()
  4129. {
  4130. $content = '<div class="alert">
  4131. <span>'.$this->l('Your product will be saved as a draft.').'</span>
  4132. <a href="#" class="btn btn-default pull-right" onclick="submitAddProductAndPreview()" ><i class="icon-external-link-sign"></i> '.$this->l('Save and preview').'</a>
  4133. <input type="hidden" name="fakeSubmitAddProductAndPreview" id="fakeSubmitAddProductAndPreview" />
  4134. </div>';
  4135. $this->tpl_form_vars['warning_unavailable_product'] = $content;
  4136. }
  4137. public function ajaxProcessCheckProductName()
  4138. {
  4139. if ($this->tabAccess['view'] === '1')
  4140. {
  4141. $search = Tools::getValue('q');
  4142. $id_lang = Tools::getValue('id_lang');
  4143. $limit = Tools::getValue('limit');
  4144. if (Context::getContext()->shop->getContext() != Shop::CONTEXT_SHOP)
  4145. $result = false;
  4146. else
  4147. $result = Db::getInstance()->executeS('
  4148. SELECT DISTINCT pl.`name`, p.`id_product`, pl.`id_shop`
  4149. FROM `'._DB_PREFIX_.'product` p
  4150. LEFT JOIN `'._DB_PREFIX_.'product_shop` ps ON (ps.id_product = p.id_product AND ps.id_shop ='.(int)Context::getContext()->shop->id.')
  4151. LEFT JOIN `'._DB_PREFIX_.'product_lang` pl
  4152. ON (pl.`id_product` = p.`id_product` AND pl.`id_lang` = '.(int)$id_lang.')
  4153. WHERE pl.`name` LIKE "%'.pSQL($search).'%" AND ps.id_product IS NULL
  4154. GROUP BY pl.`id_product`
  4155. LIMIT '.(int)$limit);
  4156. die(Tools::jsonEncode($result));
  4157. }
  4158. }
  4159. public function ajaxProcessUpdatePositions()
  4160. {
  4161. if ($this->tabAccess['edit'] === '1')
  4162. {
  4163. $way = (int)(Tools::getValue('way'));
  4164. $id_product = (int)(Tools::getValue('id_product'));
  4165. $id_category = (int)(Tools::getValue('id_category'));
  4166. $positions = Tools::getValue('product');
  4167. if (is_array($positions))
  4168. foreach ($positions as $position => $value)
  4169. {
  4170. $pos = explode('_', $value);
  4171. if ((isset($pos[1]) && isset($pos[2])) && ($pos[1] == $id_category && (int)$pos[2] === $id_product))
  4172. {
  4173. if ($product = new Product((int)$pos[2]))
  4174. if (isset($position) && $product->updatePosition($way, $position))
  4175. {
  4176. $category = new Category((int)$id_category);
  4177. if (Validate::isLoadedObject($category))
  4178. hook::Exec('categoryUpdate', array('category' => $category));
  4179. echo 'ok position '.(int)$position.' for product '.(int)$pos[2]."\r\n";
  4180. }
  4181. else
  4182. echo '{"hasError" : true, "errors" : "Can not update product '.(int)$id_product.' to position '.(int)$position.' "}';
  4183. else
  4184. echo '{"hasError" : true, "errors" : "This product ('.(int)$id_product.') can t be loaded"}';
  4185. break;
  4186. }
  4187. }
  4188. }
  4189. }
  4190. public function ajaxProcessPublishProduct()
  4191. {
  4192. if ($this->tabAccess['edit'] === '1')
  4193. {
  4194. if ($id_product = (int)Tools::getValue('id_product'))
  4195. {
  4196. $id_tab_catalog = (int)(Tab::getIdFromClassName('AdminProducts'));
  4197. $bo_product_url = dirname($_SERVER['PHP_SELF']).'/index.php?tab=AdminProducts&id_product='.$id_product.'&updateproduct&token='.$this->token;
  4198. if (Tools::getValue('redirect'))
  4199. die($bo_product_url);
  4200. $product = new Product((int)$id_product);
  4201. if (!Validate::isLoadedObject($product))
  4202. die('error: invalid id');
  4203. $product->active = 1;
  4204. if ($product->save())
  4205. die($bo_product_url);
  4206. else
  4207. die('error: saving');
  4208. }
  4209. }
  4210. }
  4211. }