1<?php 2/** 3 * 2007-2016 PrestaShop 4 * 5 * thirty bees is an extension to the PrestaShop e-commerce software developed by PrestaShop SA 6 * Copyright (C) 2017-2018 thirty bees 7 * 8 * NOTICE OF LICENSE 9 * 10 * This source file is subject to the Open Software License (OSL 3.0) 11 * that is bundled with this package in the file LICENSE.txt. 12 * It is also available through the world-wide-web at this URL: 13 * http://opensource.org/licenses/osl-3.0.php 14 * If you did not receive a copy of the license and are unable to 15 * obtain it through the world-wide-web, please send an email 16 * to license@thirtybees.com so we can send you a copy immediately. 17 * 18 * DISCLAIMER 19 * 20 * Do not edit or add to this file if you wish to upgrade PrestaShop to newer 21 * versions in the future. If you wish to customize PrestaShop for your 22 * needs please refer to https://www.thirtybees.com for more information. 23 * 24 * @author thirty bees <contact@thirtybees.com> 25 * @author PrestaShop SA <contact@prestashop.com> 26 * @copyright 2017-2018 thirty bees 27 * @copyright 2007-2016 PrestaShop SA 28 * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) 29 * PrestaShop is an internationally registered trademark & property of PrestaShop SA 30 */ 31 32/** 33 * Class AdminProductsControllerCore 34 * 35 * @since 1.0.0 36 */ 37class AdminProductsControllerCore extends AdminController 38{ 39 // @codingStandardsIgnoreStart 40 /** @var int Max image size for upload 41 * As of 1.5 it is recommended to not set a limit to max image size 42 */ 43 protected $max_file_size = null; 44 protected $max_image_size = null; 45 46 protected $_category; 47 /** 48 * @var string name of the tab to display 49 */ 50 protected $tab_display; 51 protected $tab_display_module; 52 53 /** 54 * 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. 55 * The tabs are preloaded from the smallest to the highest number. 56 * 57 * @var array Product tabs. 58 */ 59 protected $available_tabs = []; 60 61 /** @var string $default_tab */ 62 protected $default_tab = 'Informations'; 63 64 /** @var array $available_tabs_lang */ 65 protected $available_tabs_lang = []; 66 67 /** @var string $position_identifier */ 68 protected $position_identifier = 'id_product'; 69 70 /** @var array $submitted_tabs */ 71 protected $submitted_tabs; 72 73 /** @var int $id_current_category */ 74 protected $id_current_category; 75 76 /** @var Product $object */ 77 public $object; 78 // @codingStandardsIgnoreEnd 79 80 /** 81 * AdminProductsControllerCore constructor. 82 * 83 * @since 1.0.0 84 */ 85 public function __construct() 86 { 87 $this->bootstrap = true; 88 $this->table = 'product'; 89 $this->className = 'Product'; 90 $this->lang = true; 91 $this->explicitSelect = true; 92 $this->bulk_actions = [ 93 'delete' => [ 94 'text' => $this->l('Delete selected'), 95 'icon' => 'icon-trash', 96 'confirm' => $this->l('Delete selected items?'), 97 ], 98 ]; 99 if (!Tools::getValue('id_product')) { 100 $this->multishop_context_group = false; 101 } 102 103 parent::__construct(); 104 105 $this->imageType = 'jpg'; 106 $this->_defaultOrderBy = 'position'; 107 $this->max_file_size = (int) (Configuration::get('PS_LIMIT_UPLOAD_FILE_VALUE') * 1000000); 108 $this->max_image_size = (int) Configuration::get('PS_PRODUCT_PICTURE_MAX_SIZE'); 109 $this->allow_export = true; 110 111 // @since 1.5 : translations for tabs 112 $this->available_tabs_lang = [ 113 'Informations' => $this->l('Information'), 114 'Pack' => $this->l('Pack'), 115 'VirtualProduct' => $this->l('Virtual Product'), 116 'Prices' => $this->l('Prices'), 117 'Seo' => $this->l('SEO'), 118 'Images' => $this->l('Images'), 119 'Associations' => $this->l('Associations'), 120 'Shipping' => $this->l('Shipping'), 121 'Combinations' => $this->l('Combinations'), 122 'Features' => $this->l('Features'), 123 'Customization' => $this->l('Customization'), 124 'Attachments' => $this->l('Attachments'), 125 'Quantities' => $this->l('Quantities'), 126 'Suppliers' => $this->l('Suppliers'), 127 'Warehouses' => $this->l('Warehouses'), 128 ]; 129 130 $this->available_tabs = ['Quantities' => 6, 'Warehouses' => 14]; 131 if ($this->context->shop->getContext() != Shop::CONTEXT_GROUP) { 132 $this->available_tabs = array_merge( 133 $this->available_tabs, 134 [ 135 'Informations' => 0, 136 'Pack' => 7, 137 'VirtualProduct' => 8, 138 'Prices' => 1, 139 'Seo' => 2, 140 'Associations' => 3, 141 'Images' => 9, 142 'Shipping' => 4, 143 'Combinations' => 5, 144 'Features' => 10, 145 'Customization' => 11, 146 'Attachments' => 12, 147 'Suppliers' => 13, 148 ] 149 ); 150 } 151 152 // Sort the tabs that need to be preloaded by their priority number 153 asort($this->available_tabs, SORT_NUMERIC); 154 155 /* Adding tab if modules are hooked */ 156 $modulesList = Hook::getHookModuleExecList('displayAdminProductsExtra'); 157 if (is_array($modulesList) && count($modulesList) > 0) { 158 foreach ($modulesList as $m) { 159 $this->available_tabs['Module'.ucfirst($m['module'])] = 23; 160 $this->available_tabs_lang['Module'.ucfirst($m['module'])] = Module::getModuleName($m['module']); 161 } 162 } 163 164 if (Tools::getValue('reset_filter_category')) { 165 $this->context->cookie->id_category_products_filter = false; 166 } 167 if (Shop::isFeatureActive() && $this->context->cookie->id_category_products_filter) { 168 $category = new Category((int) $this->context->cookie->id_category_products_filter); 169 if (!$category->inShop()) { 170 $this->context->cookie->id_category_products_filter = false; 171 Tools::redirectAdmin($this->context->link->getAdminLink('AdminProducts')); 172 } 173 } 174 /* Join categories table */ 175 if ($idCategory = (int) Tools::getValue('productFilter_cl!name')) { 176 $this->_category = new Category((int) $idCategory); 177 $_POST['productFilter_cl!name'] = $this->_category->name[$this->context->language->id]; 178 } else { 179 if ($idCategory = (int) Tools::getValue('id_category')) { 180 $this->id_current_category = $idCategory; 181 $this->context->cookie->id_category_products_filter = $idCategory; 182 } elseif ($idCategory = $this->context->cookie->id_category_products_filter) { 183 $this->id_current_category = $idCategory; 184 } 185 if ($this->id_current_category) { 186 $this->_category = new Category((int) $this->id_current_category); 187 } else { 188 $this->_category = new Category(); 189 } 190 } 191 192 $joinCategory = false; 193 if (Validate::isLoadedObject($this->_category) && empty($this->_filter)) { 194 $joinCategory = true; 195 } 196 197 $this->_join .= ' 198 LEFT JOIN `'._DB_PREFIX_.'stock_available` sav ON (sav.`id_product` = a.`id_product` AND sav.`id_product_attribute` = 0 199 '.StockAvailable::addSqlShopRestriction(null, null, 'sav').') '; 200 201 $alias = 'sa'; 202 $aliasImage = 'image_shop'; 203 204 $idShop = Shop::isFeatureActive() && Shop::getContext() == Shop::CONTEXT_SHOP ? (int) $this->context->shop->id : 'a.id_shop_default'; 205 $this->_join .= ' JOIN `'._DB_PREFIX_.'product_shop` sa ON (a.`id_product` = sa.`id_product` AND sa.id_shop = '.$idShop.') 206 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 = '.$idShop.') 207 LEFT JOIN `'._DB_PREFIX_.'shop` shop ON (shop.id_shop = '.$idShop.') 208 LEFT JOIN `'._DB_PREFIX_.'image_shop` image_shop ON (image_shop.`id_product` = a.`id_product` AND image_shop.`cover` = 1 AND image_shop.id_shop = '.$idShop.') 209 LEFT JOIN `'._DB_PREFIX_.'image` i ON (i.`id_image` = image_shop.`id_image`) 210 LEFT JOIN `'._DB_PREFIX_.'product_download` pd ON (pd.`id_product` = a.`id_product` AND pd.`active` = 1)'; 211 212 $this->_select .= 'shop.`name` AS `shopname`, a.`id_shop_default`, '; 213 $this->_select .= $aliasImage.'.`id_image` AS `id_image`, cl.`name` AS `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) AS `badge_danger`'; 214 215 if ($joinCategory) { 216 $this->_join .= ' INNER JOIN `'._DB_PREFIX_.'category_product` cp ON (cp.`id_product` = a.`id_product` AND cp.`id_category` = '.(int) $this->_category->id.') '; 217 $this->_select .= ' , cp.`position`, '; 218 } 219 $this->_use_found_rows = false; 220 $this->_group = ''; 221 222 $this->fields_list = []; 223 $this->fields_list['id_product'] = [ 224 'title' => $this->l('ID'), 225 'align' => 'center', 226 'class' => 'fixed-width-xs', 227 'type' => 'int', 228 ]; 229 $this->fields_list['image'] = [ 230 'title' => $this->l('Image'), 231 'align' => 'center', 232 'image' => 'p', 233 'orderby' => false, 234 'filter' => false, 235 'search' => false, 236 ]; 237 $this->fields_list['name'] = [ 238 'title' => $this->l('Name'), 239 'filter_key' => 'b!name', 240 ]; 241 $this->fields_list['reference'] = [ 242 'title' => $this->l('Reference'), 243 'align' => 'left', 244 ]; 245 246 if (Shop::isFeatureActive() && Shop::getContext() != Shop::CONTEXT_SHOP) { 247 $this->fields_list['shopname'] = [ 248 'title' => $this->l('Default shop'), 249 'filter_key' => 'shop!name', 250 ]; 251 } else { 252 $this->fields_list['name_category'] = [ 253 'title' => $this->l('Category'), 254 'filter_key' => 'cl!name', 255 ]; 256 } 257 $this->fields_list['price'] = [ 258 'title' => $this->l('Base price'), 259 'type' => 'price', 260 'align' => 'text-right', 261 'filter_key' => 'a!price', 262 ]; 263 $this->fields_list['price_final'] = [ 264 'title' => $this->l('Final price'), 265 'type' => 'price', 266 'align' => 'text-right', 267 'havingFilter' => true, 268 'orderby' => false, 269 'search' => false, 270 ]; 271 272 if (Configuration::get('PS_STOCK_MANAGEMENT')) { 273 $this->fields_list['sav_quantity'] = [ 274 'title' => $this->l('Quantity'), 275 'type' => 'int', 276 'align' => 'text-right', 277 'filter_key' => 'sav!quantity', 278 'orderby' => true, 279 'badge_danger' => true, 280 //'hint' => $this->l('This is the quantity available in the current shop/group.'), 281 ]; 282 } 283 284 $this->fields_list['active'] = [ 285 'title' => $this->l('Status'), 286 'active' => 'status', 287 'filter_key' => $alias.'!active', 288 'align' => 'text-center', 289 'type' => 'bool', 290 'class' => 'fixed-width-sm', 291 'orderby' => false, 292 ]; 293 294 if ($joinCategory && (int) $this->id_current_category) { 295 $this->fields_list['position'] = [ 296 'title' => $this->l('Position'), 297 'filter_key' => 'cp!position', 298 'align' => 'center', 299 'position' => 'position', 300 ]; 301 } 302 } 303 304 /** 305 * @param string $echo 306 * @param array $tr 307 * 308 * @return string 309 * 310 * @since 1.0.0 311 */ 312 public static function getQuantities($echo, $tr) 313 { 314 if ((int) $tr['is_virtual'] == 1 && $tr['nb_downloadable'] == 0) { 315 return '∞'; 316 } else { 317 return $echo; 318 } 319 } 320 321 /** 322 * Build a categories tree 323 * 324 * @param int $idObj 325 * @param array $indexedCategories Array with categories where product is indexed (in order to check checkbox) 326 * @param array $categories Categories to list 327 * @param $current 328 * @param null $idCategory Current category ID 329 * @param null $idCategoryDefault 330 * @param array $hasSuite 331 * 332 * @return string 333 * 334 * @since 1.0.0 335 */ 336 public static function recurseCategoryForInclude($idObj, $indexedCategories, $categories, $current, $idCategory = null, $idCategoryDefault = null, $hasSuite = []) 337 { 338 global $done; 339 static $irow; 340 $content = ''; 341 342 if (!$idCategory) { 343 $idCategory = (int) Configuration::get('PS_ROOT_CATEGORY'); 344 } 345 346 if (!isset($done[$current['infos']['id_parent']])) { 347 $done[$current['infos']['id_parent']] = 0; 348 } 349 $done[$current['infos']['id_parent']] += 1; 350 351 $todo = count($categories[$current['infos']['id_parent']]); 352 $doneC = $done[$current['infos']['id_parent']]; 353 354 $level = $current['infos']['level_depth'] + 1; 355 356 $content .= ' 357 <tr class="'.($irow++ % 2 ? 'alt_row' : '').'"> 358 <td> 359 <input type="checkbox" name="categoryBox[]" class="categoryBox'.($idCategoryDefault == $idCategory ? ' id_category_default' : '').'" id="categoryBox_'.$idCategory.'" value="'.$idCategory.'"'.((in_array($idCategory, $indexedCategories) || ((int) (Tools::getValue('id_category')) == $idCategory && !(int) ($idObj))) ? ' checked="checked"' : '').' /> 360 </td> 361 <td> 362 '.$idCategory.' 363 </td> 364 <td>'; 365 for ($i = 2; $i < $level; $i++) { 366 $content .= '<img src="../img/admin/lvl_'.$hasSuite[$i - 2].'.gif" alt="" />'; 367 } 368 $content .= '<img src="../img/admin/'.($level == 1 ? 'lv1.gif' : 'lv2_'.($todo == $doneC ? 'f' : 'b').'.gif').'" alt="" /> 369 <label for="categoryBox_'.$idCategory.'" class="t">'.stripslashes($current['infos']['name']).'</label></td> 370 </tr>'; 371 372 if ($level > 1) { 373 $hasSuite[] = ($todo == $doneC ? 0 : 1); 374 } 375 if (isset($categories[$idCategory])) { 376 foreach ($categories[$idCategory] as $key => $row) { 377 if ($key != 'infos') { 378 $content .= AdminProductsController::recurseCategoryForInclude($idObj, $indexedCategories, $categories, $categories[$idCategory][$key], $key, $idCategoryDefault, $hasSuite); 379 } 380 } 381 } 382 383 return $content; 384 } 385 386 /** 387 * @return void 388 * 389 * @since 1.0.0 390 */ 391 public function setMedia() 392 { 393 parent::setMedia(); 394 395 $boTheme = ((Validate::isLoadedObject($this->context->employee) 396 && $this->context->employee->bo_theme) ? $this->context->employee->bo_theme : 'default'); 397 398 if (!file_exists(_PS_BO_ALL_THEMES_DIR_.$boTheme.DIRECTORY_SEPARATOR.'template')) { 399 $boTheme = 'default'; 400 } 401 402 $this->addJs(__PS_BASE_URI__.$this->admin_webpath.'/themes/'.$boTheme.'/js/jquery.iframe-transport.js'); 403 $this->addJs(__PS_BASE_URI__.$this->admin_webpath.'/themes/'.$boTheme.'/js/jquery.fileupload.js'); 404 $this->addJs(__PS_BASE_URI__.$this->admin_webpath.'/themes/'.$boTheme.'/js/jquery.fileupload-process.js'); 405 $this->addJs(__PS_BASE_URI__.$this->admin_webpath.'/themes/'.$boTheme.'/js/jquery.fileupload-validate.js'); 406 $this->addJs(__PS_BASE_URI__.'js/vendor/spin.js'); 407 $this->addJs(__PS_BASE_URI__.'js/vendor/ladda.js'); 408 } 409 410 /** 411 * @param int $idLang 412 * @param string|null $orderBy 413 * @param string|null $orderWay 414 * @param int $start 415 * @param int|null $limit 416 * @param int|null $idLangShop 417 * 418 * @since 1.0.0 419 */ 420 public function getList($idLang, $orderBy = null, $orderWay = null, $start = 0, $limit = null, $idLangShop = null) 421 { 422 $orderByPriceFinal = (empty($orderBy) ? ($this->context->cookie->__get($this->table.'Orderby') ? $this->context->cookie->__get($this->table.'Orderby') : 'id_'.$this->table) : $orderBy); 423 $orderWayPriceFinal = (empty($orderWay) ? ($this->context->cookie->__get($this->table.'Orderway') ? $this->context->cookie->__get($this->table.'Orderby') : 'ASC') : $orderWay); 424 if ($orderByPriceFinal == 'price_final') { 425 $orderBy = 'id_'.$this->table; 426 $orderWay = 'ASC'; 427 } 428 parent::getList($idLang, $orderBy, $orderWay, $start, $limit, $this->context->shop->id); 429 430 /* update product quantity with attributes ...*/ 431 $nb = count($this->_list); 432 if ($this->_list) { 433 $context = $this->context->cloneContext(); 434 $context->shop = clone($context->shop); 435 /* update product final price */ 436 for ($i = 0; $i < $nb; $i++) { 437 if ($this->context->shop->getContext() != Shop::CONTEXT_SHOP) { 438 $context->shop = new Shop((int) $this->_list[$i]['id_shop_default']); 439 } 440 441 $decimals = 0; 442 if ($this->context->currency->decimals) { 443 $decimals = Configuration::get('PS_PRICE_DISPLAY_PRECISION'); 444 } 445 // convert price with the currency from context 446 $this->_list[$i]['price'] = Tools::convertPrice($this->_list[$i]['price'], $this->context->currency, true, $this->context); 447 $this->_list[$i]['price_tmp'] = Product::getPriceStatic( 448 $this->_list[$i]['id_product'], 449 true, 450 null, 451 $decimals, 452 null, 453 false, 454 true, 455 1, 456 true, 457 null, 458 null, 459 null, 460 $nothing, 461 true, 462 true, 463 $context 464 ); 465 } 466 } 467 468 if ($orderByPriceFinal == 'price_final') { 469 if (strtolower($orderWayPriceFinal) == 'desc') { 470 uasort($this->_list, 'cmpPriceDesc'); 471 } else { 472 uasort($this->_list, 'cmpPriceAsc'); 473 } 474 } 475 for ($i = 0; $this->_list && $i < $nb; $i++) { 476 $this->_list[$i]['price_final'] = $this->_list[$i]['price_tmp']; 477 unset($this->_list[$i]['price_tmp']); 478 } 479 } 480 481 /** 482 * Ajax process get category tree 483 * 484 * @since 1.0.0 485 */ 486 public function ajaxProcessGetCategoryTree() 487 { 488 $category = Tools::getValue('category', Category::getRootCategory()->id); 489 $fullTree = Tools::getValue('fullTree', 0); 490 $useCheckBox = Tools::getValue('useCheckBox', 1); 491 $selected = Tools::getValue('selected', []); 492 $idTree = Tools::getValue('type'); 493 $inputName = str_replace(['[', ']'], '', Tools::getValue('inputName', null)); 494 495 $tree = new HelperTreeCategories('subtree_associated_categories'); 496 $tree->setTemplate('subtree_associated_categories.tpl') 497 ->setUseCheckBox($useCheckBox) 498 ->setUseSearch(true) 499 ->setIdTree($idTree) 500 ->setSelectedCategories($selected) 501 ->setFullTree($fullTree) 502 ->setChildrenOnly(true) 503 ->setNoJS(true) 504 ->setRootCategory($category); 505 506 if ($inputName) { 507 $tree->setInputName($inputName); 508 } 509 510 die($tree->render()); 511 } 512 513 /** 514 * Ajax process get countries options 515 * 516 * @since 1.0.0 517 */ 518 public function ajaxProcessGetCountriesOptions() 519 { 520 if (!$res = Country::getCountriesByIdShop((int) Tools::getValue('id_shop'), (int) $this->context->language->id)) { 521 return; 522 } 523 524 $tpl = $this->createTemplate('specific_prices_shop_update.tpl'); 525 $tpl->assign( 526 [ 527 'option_list' => $res, 528 'key_id' => 'id_country', 529 'key_value' => 'name', 530 ] 531 ); 532 533 $this->content = $tpl->fetch(); 534 } 535 536 /** 537 * 538 * Ajax process get currency options 539 * 540 * @since 1.0.0 541 */ 542 public function ajaxProcessGetCurrenciesOptions() 543 { 544 if (!$res = Currency::getCurrenciesByIdShop((int) Tools::getValue('id_shop'))) { 545 return; 546 } 547 548 $tpl = $this->createTemplate('specific_prices_shop_update.tpl'); 549 $tpl->assign( 550 [ 551 'option_list' => $res, 552 'key_id' => 'id_currency', 553 'key_value' => 'name', 554 ] 555 ); 556 557 $this->content = $tpl->fetch(); 558 } 559 560 /** 561 * Ajax process get group options 562 * 563 * @since 1.0.0 564 */ 565 public function ajaxProcessGetGroupsOptions() 566 { 567 if (!$res = Group::getGroups((int) $this->context->language->id, (int) Tools::getValue('id_shop'))) { 568 return; 569 } 570 571 $tpl = $this->createTemplate('specific_prices_shop_update.tpl'); 572 $tpl->assign( 573 [ 574 'option_list' => $res, 575 'key_id' => 'id_group', 576 'key_value' => 'name', 577 ] 578 ); 579 580 $this->content = $tpl->fetch(); 581 } 582 583 /** 584 * Process delete virtual product 585 * 586 * @since 1.0.0 587 */ 588 public function processDeleteVirtualProduct() 589 { 590 $idProduct = (int) Tools::getValue('id_product'); 591 $idProductDownload = ProductDownload::getIdFromIdProduct($idProduct); 592 593 if ($idProductDownload) { 594 $productDownload = new ProductDownload($idProductDownload); 595 596 if (!$productDownload->deleteFile()) { 597 $this->errors[] = Tools::displayError('Cannot delete file.'); 598 } else { 599 $productDownload->active = false; 600 $productDownload->update(); 601 $this->redirect_after = static::$currentIndex.'&id_product='.$idProduct.'&updateproduct&key_tab=VirtualProduct&conf=1&token='.$this->token; 602 } 603 } 604 605 $this->display = 'edit'; 606 $this->tab_display = 'VirtualProduct'; 607 } 608 609 /** 610 * Ajax process add attachment 611 * 612 * @since 1.0.0 613 */ 614 public function ajaxProcessAddAttachment() 615 { 616 if ($this->tabAccess['edit'] === '0') { 617 $this->ajaxDie(json_encode(['error' => $this->l('You do not have the right permission')])); 618 } 619 if (isset($_FILES['attachment_file'])) { 620 if ((int) $_FILES['attachment_file']['error'] === 1) { 621 $_FILES['attachment_file']['error'] = []; 622 623 $maxUpload = (int) ini_get('upload_max_filesize'); 624 $maxPost = (int) ini_get('post_max_size'); 625 $uploadMb = min($maxUpload, $maxPost); 626 $_FILES['attachment_file']['error'][] = sprintf( 627 $this->l('File %1$s exceeds the size allowed by the server. The limit is set to %2$d MB.'), 628 '<b>'.$_FILES['attachment_file']['name'].'</b> ', 629 '<b>'.$uploadMb.'</b>' 630 ); 631 } 632 633 $_FILES['attachment_file']['error'] = []; 634 635 $isAttachmentNameValid = false; 636 $attachmentNames = Tools::getValue('attachment_name'); 637 $attachmentDescriptions = Tools::getValue('attachment_description'); 638 639 if (!isset($attachmentNames) || !$attachmentNames) { 640 $attachmentNames = []; 641 } 642 643 if (!isset($attachmentDescriptions) || !$attachmentDescriptions) { 644 $attachmentDescriptions = []; 645 } 646 647 foreach ($attachmentNames as $lang => $name) { 648 $language = Language::getLanguage((int) $lang); 649 650 if (mb_strlen($name) > 0) { 651 $isAttachmentNameValid = true; 652 } 653 654 if (!Validate::isGenericName($name)) { 655 $_FILES['attachment_file']['error'][] = sprintf(Tools::displayError('Invalid name for %s language'), $language['name']); 656 } elseif (mb_strlen($name) > 32) { 657 $_FILES['attachment_file']['error'][] = sprintf(Tools::displayError('The name for %1s language is too long (%2d chars max).'), $language['name'], 32); 658 } 659 } 660 661 foreach ($attachmentDescriptions as $lang => $description) { 662 $language = Language::getLanguage((int) $lang); 663 664 if (!Validate::isCleanHtml($description)) { 665 $_FILES['attachment_file']['error'][] = sprintf(Tools::displayError('Invalid description for %s language'), $language['name']); 666 } 667 } 668 669 if (!$isAttachmentNameValid) { 670 $_FILES['attachment_file']['error'][] = Tools::displayError('An attachment name is required.'); 671 } 672 673 if (empty($_FILES['attachment_file']['error'])) { 674 if (is_uploaded_file($_FILES['attachment_file']['tmp_name'])) { 675 if ($_FILES['attachment_file']['size'] > (Configuration::get('PS_ATTACHMENT_MAXIMUM_SIZE') * 1024 * 1024)) { 676 $_FILES['attachment_file']['error'][] = sprintf( 677 $this->l('The file is too large. Maximum size allowed is: %1$d kB. The file you are trying to upload is %2$d kB.'), 678 (Configuration::get('PS_ATTACHMENT_MAXIMUM_SIZE') * 1024), 679 number_format(($_FILES['attachment_file']['size'] / 1024), 2, '.', '') 680 ); 681 } else { 682 do { 683 $uniqid = sha1(microtime()); 684 } while (file_exists(_PS_DOWNLOAD_DIR_.$uniqid)); 685 if (!copy($_FILES['attachment_file']['tmp_name'], _PS_DOWNLOAD_DIR_.$uniqid)) { 686 $_FILES['attachment_file']['error'][] = $this->l('File copy failed'); 687 } 688 @unlink($_FILES['attachment_file']['tmp_name']); 689 } 690 } else { 691 $_FILES['attachment_file']['error'][] = Tools::displayError('The file is missing.'); 692 } 693 694 if (empty($_FILES['attachment_file']['error']) && isset($uniqid)) { 695 $attachment = new Attachment(); 696 697 foreach ($attachmentNames as $lang => $name) { 698 $attachment->name[(int) $lang] = $name; 699 } 700 701 foreach ($attachmentDescriptions as $lang => $description) { 702 $attachment->description[(int) $lang] = $description; 703 } 704 705 $attachment->file = $uniqid; 706 $attachment->mime = $_FILES['attachment_file']['type']; 707 $attachment->file_name = $_FILES['attachment_file']['name']; 708 709 if (empty($attachment->mime) || mb_strlen($attachment->mime) > 128) { 710 $_FILES['attachment_file']['error'][] = Tools::displayError('Invalid file extension'); 711 } 712 if (!Validate::isGenericName($attachment->file_name)) { 713 $_FILES['attachment_file']['error'][] = Tools::displayError('Invalid file name'); 714 } 715 if (mb_strlen($attachment->file_name) > 128) { 716 $_FILES['attachment_file']['error'][] = Tools::displayError('The file name is too long.'); 717 } 718 if (empty($this->errors)) { 719 $res = $attachment->add(); 720 if (!$res) { 721 $_FILES['attachment_file']['error'][] = Tools::displayError('This attachment was unable to be loaded into the database.'); 722 } else { 723 $_FILES['attachment_file']['id_attachment'] = $attachment->id; 724 $_FILES['attachment_file']['filename'] = $attachment->name[$this->context->employee->id_lang]; 725 $idProduct = (int) Tools::getValue($this->identifier); 726 $res = $attachment->attachProduct($idProduct); 727 if (!$res) { 728 $_FILES['attachment_file']['error'][] = Tools::displayError('We were unable to associate this attachment to a product.'); 729 } 730 } 731 } else { 732 $_FILES['attachment_file']['error'][] = Tools::displayError('Invalid file'); 733 } 734 } 735 } 736 737 $this->ajaxDie(json_encode($_FILES)); 738 } 739 } 740 741 /** 742 * Process duplicate 743 * 744 * @since 1.0.0 745 */ 746 public function processDuplicate() 747 { 748 if (Validate::isLoadedObject($product = new Product((int) Tools::getValue('id_product')))) { 749 $idProductOld = $product->id; 750 if (empty($product->price) && Shop::getContext() == Shop::CONTEXT_GROUP) { 751 $shops = ShopGroup::getShopsFromGroup(Shop::getContextShopGroupID()); 752 foreach ($shops as $shop) { 753 if ($product->isAssociatedToShop($shop['id_shop'])) { 754 $productPrice = new Product($idProductOld, false, null, $shop['id_shop']); 755 $product->price = $productPrice->price; 756 } 757 } 758 } 759 unset($product->id); 760 unset($product->id_product); 761 $product->indexed = 0; 762 $product->active = 0; 763 if ($product->add() 764 && Category::duplicateProductCategories($idProductOld, $product->id) 765 && Product::duplicateSuppliers($idProductOld, $product->id) 766 && ($combinationImages = Product::duplicateAttributes($idProductOld, $product->id)) !== false 767 && GroupReduction::duplicateReduction($idProductOld, $product->id) 768 && Product::duplicateAccessories($idProductOld, $product->id) 769 && Product::duplicateFeatures($idProductOld, $product->id) 770 && Product::duplicateSpecificPrices($idProductOld, $product->id) 771 && Pack::duplicate($idProductOld, $product->id) 772 && Product::duplicateCustomizationFields($idProductOld, $product->id) 773 && Product::duplicateTags($idProductOld, $product->id) 774 && Product::duplicateDownload($idProductOld, $product->id) 775 && Product::duplicateAttachments($idProductOld, $product->id) 776 ) { 777 if ($product->hasAttributes()) { 778 Product::updateDefaultAttribute($product->id); 779 } else { 780 // Set stock quantity 781 $quantityAttributeOld = Db::getInstance(_PS_USE_SQL_SLAVE_)->getValue( 782 (new DbQuery()) 783 ->select('`quantity`') 784 ->from('stock_available') 785 ->where('`id_product` = '.(int) $idProductOld) 786 ->where('`id_product_attribute` = 0') 787 ); 788 StockAvailable::setQuantity((int) $product->id, 0, (int) $quantityAttributeOld); 789 } 790 791 if (!Tools::getValue('noimage') && !Image::duplicateProductImages($idProductOld, $product->id, $combinationImages)) { 792 $this->errors[] = Tools::displayError('An error occurred while copying images.'); 793 } else { 794 Hook::exec('actionProductAdd', ['id_product' => (int) $product->id, 'product' => $product]); 795 if (in_array($product->visibility, ['both', 'search']) && Configuration::get('PS_SEARCH_INDEXATION')) { 796 Search::indexation(false, $product->id); 797 } 798 $this->redirect_after = static::$currentIndex.(Tools::getIsset('id_category') ? '&id_category='.(int) Tools::getValue('id_category') : '').'&conf=19&token='.$this->token; 799 } 800 } else { 801 $this->errors[] = Tools::displayError('An error occurred while creating an object.'); 802 } 803 } 804 } 805 806 /** 807 * Process delete 808 * 809 * @since 1.0.0 810 */ 811 public function processDelete() 812 { 813 if (Validate::isLoadedObject($object = $this->loadObject()) && isset($this->fieldImageSettings)) { 814 /** @var Product $object */ 815 // check if request at least one object with noZeroObject 816 if (isset($object->noZeroObject) && count($taxes = call_user_func([$this->className, $object->noZeroObject])) <= 1) { 817 $this->errors[] = Tools::displayError('You need at least one object.').' <b>'.$this->table.'</b><br />'.Tools::displayError('You cannot delete all of the items.'); 818 } else { 819 /* 820 * @since 1.5.0 821 * It is NOT possible to delete a product if there is/are currently: 822 * - a physical stock for this product 823 * - supply order(s) for this product 824 */ 825 if (Configuration::get('PS_ADVANCED_STOCK_MANAGEMENT') && $object->advanced_stock_management) { 826 $stockManager = StockManagerFactory::getManager(); 827 $physicalQuantity = $stockManager->getProductPhysicalQuantities($object->id, 0); 828 $realQuantity = $stockManager->getProductRealQuantities($object->id, 0); 829 if ($physicalQuantity > 0 || $realQuantity > $physicalQuantity) { 830 $this->errors[] = Tools::displayError('You cannot delete this product because there is physical stock left.'); 831 } 832 } 833 834 if (!count($this->errors)) { 835 if ($object->delete()) { 836 $idCategory = (int) Tools::getValue('id_category'); 837 $categoryUrl = empty($idCategory) ? '' : '&id_category='.(int) $idCategory; 838 Logger::addLog(sprintf($this->l('%s deletion', 'AdminTab', false, false), $this->className), 1, null, $this->className, (int) $object->id, true, (int) $this->context->employee->id); 839 $this->redirect_after = static::$currentIndex.'&conf=1&token='.$this->token.$categoryUrl; 840 } else { 841 $this->errors[] = Tools::displayError('An error occurred during deletion.'); 842 } 843 } 844 } 845 } else { 846 $this->errors[] = Tools::displayError('An error occurred while deleting the object.').' <b>'.$this->table.'</b> '.Tools::displayError('(cannot load object)'); 847 } 848 } 849 850 /** 851 * Load object 852 * 853 * @param bool $opt 854 * 855 * @return false|ObjectModel 856 * 857 * @since 1.0.0 858 */ 859 protected function loadObject($opt = false) 860 { 861 $result = parent::loadObject($opt); 862 if ($result && Validate::isLoadedObject($this->object)) { 863 if (Shop::getContext() == Shop::CONTEXT_SHOP && Shop::isFeatureActive() && !$this->object->isAssociatedToShop()) { 864 $defaultProduct = new Product((int) $this->object->id, false, null, (int) $this->object->id_shop_default); 865 $def = ObjectModel::getDefinition($this->object); 866 foreach ($def['fields'] as $field_name => $row) { 867 if (is_array($defaultProduct->$field_name)) { 868 foreach ($defaultProduct->$field_name as $key => $value) { 869 $this->object->{$field_name}[$key] = $value; 870 } 871 } else { 872 $this->object->$field_name = $defaultProduct->$field_name; 873 } 874 } 875 } 876 $this->object->loadStockData(); 877 } 878 879 return $result; 880 } 881 882 /** 883 * Process image 884 * 885 * @since 1.0.0 886 */ 887 public function processImage() 888 { 889 $idImage = (int) Tools::getValue('id_image'); 890 $image = new Image((int) $idImage); 891 if (Validate::isLoadedObject($image)) { 892 /* Update product image/legend */ 893 // @todo : move in processEditProductImage 894 if (Tools::getIsset('editImage')) { 895 if ($image->cover) { 896 $_POST['cover'] = 1; 897 } 898 899 $_POST['id_image'] = $image->id; 900 } elseif (Tools::getIsset('coverImage')) { 901 /* Choose product cover image */ 902 Image::deleteCover($image->id_product); 903 $image->cover = 1; 904 if (!$image->update()) { 905 $this->errors[] = Tools::displayError('You cannot change the product\'s cover image.'); 906 } else { 907 $productId = (int) Tools::getValue('id_product'); 908 @unlink(_PS_TMP_IMG_DIR_.'product_'.$productId.'.jpg'); 909 @unlink(_PS_TMP_IMG_DIR_.'product_mini_'.$productId.'_'.$this->context->shop->id.'.jpg'); 910 $this->redirect_after = static::$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; 911 } 912 } elseif (Tools::getIsset('imgPosition') && Tools::getIsset('imgDirection')) { 913 /* Choose product image position */ 914 $image->updatePosition(Tools::getValue('imgDirection'), Tools::getValue('imgPosition')); 915 $this->redirect_after = static::$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; 916 } 917 } else { 918 $this->errors[] = Tools::displayError('The image could not be found. '); 919 } 920 } 921 922 /** 923 * This function is never called at the moment (specific prices cannot be edited) 924 * @todo: What should we be doing with it, then? 925 * 926 * @since 1.0.0 927 */ 928 public function processPricesModification() 929 { 930 $idSpecificPrices = Tools::getValue('spm_id_specific_price'); 931 $idCombinations = Tools::getValue('spm_id_product_attribute'); 932 $idShops = Tools::getValue('spm_id_shop'); 933 $idCurrencies = Tools::getValue('spm_id_currency'); 934 $idCountries = Tools::getValue('spm_id_country'); 935 $idGroups = Tools::getValue('spm_id_group'); 936 $idCustomers = Tools::getValue('spm_id_customer'); 937 $prices = Tools::getValue('spm_price'); 938 $fromQuantities = Tools::getValue('spm_from_quantity'); 939 $reductions = Tools::getValue('spm_reduction'); 940 $reductionTypes = Tools::getValue('spm_reduction_type'); 941 $froms = Tools::getValue('spm_from'); 942 $tos = Tools::getValue('spm_to'); 943 944 foreach ($idSpecificPrices as $key => $idSpecificPrice) { 945 if ($reductionTypes[$key] == 'percentage' && ((float) $reductions[$key] <= 0 || (float) $reductions[$key] > 100)) { 946 $this->errors[] = Tools::displayError('Submitted reduction value (0-100) is out-of-range'); 947 } elseif ($this->_validateSpecificPrice( 948 $idShops[$key], 949 $idCurrencies[$key], 950 $idCountries[$key], 951 $idGroups[$key], 952 $idCustomers[$key], 953 priceval($prices[$key]), 954 $fromQuantities[$key], 955 priceval($reductions[$key]), 956 $reductionTypes[$key], 957 $froms[$key], 958 $tos[$key], 959 $idCombinations[$key] 960 )) { 961 $specificPrice = new SpecificPrice((int) ($idSpecificPrice)); 962 $specificPrice->id_shop = (int) $idShops[$key]; 963 $specificPrice->id_product_attribute = (int) $idCombinations[$key]; 964 $specificPrice->id_currency = (int) ($idCurrencies[$key]); 965 $specificPrice->id_country = (int) ($idCountries[$key]); 966 $specificPrice->id_group = (int) ($idGroups[$key]); 967 $specificPrice->id_customer = (int) $idCustomers[$key]; 968 $specificPrice->price = priceval($prices[$key]); 969 $specificPrice->from_quantity = (int) ($fromQuantities[$key]); 970 $specificPrice->reduction = ($reductionTypes[$key] == 'percentage' ? ($reductions[$key] / 100) : priceval($reductions[$key])); 971 $specificPrice->reduction_type = !$reductions[$key] ? 'amount' : $reductionTypes[$key]; 972 $specificPrice->from = !$froms[$key] ? '0000-00-00 00:00:00' : $froms[$key]; 973 $specificPrice->to = !$tos[$key] ? '0000-00-00 00:00:00' : $tos[$key]; 974 if (!$specificPrice->update()) { 975 $this->errors[] = Tools::displayError('An error occurred while updating the specific price.'); 976 } 977 } 978 } 979 if (!count($this->errors)) { 980 $this->redirect_after = static::$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; 981 } 982 } 983 984 /** 985 * Validate a SpecificPrice 986 * 987 * @param $idShop 988 * @param $idCurrency 989 * @param $idCountry 990 * @param $idGroup 991 * @param $idCustomer 992 * @param $price 993 * @param $fromQuantity 994 * @param $reduction 995 * @param $reductionType 996 * @param $from 997 * @param $to 998 * @param int $idCombination 999 * 1000 * @return bool 1001 * 1002 * @since 1.0.0 1003 */ 1004 protected function _validateSpecificPrice($idShop, $idCurrency, $idCountry, $idGroup, $idCustomer, $price, $fromQuantity, $reduction, $reductionType, $from, $to, $idCombination = 0) 1005 { 1006 if (!Validate::isUnsignedId($idShop) || !Validate::isUnsignedId($idCurrency) || !Validate::isUnsignedId($idCountry) || !Validate::isUnsignedId($idGroup) || !Validate::isUnsignedId($idCustomer)) { 1007 $this->errors[] = Tools::displayError('Wrong IDs'); 1008 } elseif ((!isset($price) && !isset($reduction)) || (isset($price) && !Validate::isNegativePrice($price)) || (isset($reduction) && !Validate::isPrice($reduction))) { 1009 $this->errors[] = Tools::displayError('Invalid price/discount amount'); 1010 } elseif (!Validate::isUnsignedInt($fromQuantity)) { 1011 $this->errors[] = Tools::displayError('Invalid quantity'); 1012 } elseif ($reduction && !Validate::isReductionType($reductionType)) { 1013 $this->errors[] = Tools::displayError('Please select a discount type (amount or percentage).'); 1014 } elseif ($from && $to && (!Validate::isDateFormat($from) || !Validate::isDateFormat($to))) { 1015 $this->errors[] = Tools::displayError('The from/to date is invalid.'); 1016 } elseif (SpecificPrice::exists((int) $this->object->id, $idCombination, $idShop, $idGroup, $idCountry, $idCurrency, $idCustomer, $fromQuantity, $from, $to, false)) { 1017 $this->errors[] = Tools::displayError('A specific price already exists for these parameters.'); 1018 } else { 1019 return true; 1020 } 1021 1022 return false; 1023 } 1024 1025 /** 1026 * Ajax process delete SpecificPrice 1027 * 1028 * @since 1.0.0 1029 */ 1030 public function ajaxProcessDeleteSpecificPrice() 1031 { 1032 if ($this->tabAccess['delete'] === '1') { 1033 $idSpecificPrice = (int) Tools::getValue('id_specific_price'); 1034 if (!$idSpecificPrice || !Validate::isUnsignedId($idSpecificPrice)) { 1035 $error = Tools::displayError('The specific price ID is invalid.'); 1036 } else { 1037 $specificPrice = new SpecificPrice((int) $idSpecificPrice); 1038 if (!$specificPrice->delete()) { 1039 $error = Tools::displayError('An error occurred while attempting to delete the specific price.'); 1040 } 1041 } 1042 } else { 1043 $error = Tools::displayError('You do not have permission to delete this.'); 1044 } 1045 1046 if (isset($error)) { 1047 $json = [ 1048 'status' => 'error', 1049 'message' => $error, 1050 ]; 1051 } else { 1052 $json = [ 1053 'status' => 'ok', 1054 'message' => $this->_conf[1], 1055 ]; 1056 } 1057 1058 $this->ajaxDie(json_encode($json)); 1059 } 1060 1061 /** 1062 * Process product customization 1063 * 1064 * @since 1.0.0 1065 */ 1066 public function processProductCustomization() 1067 { 1068 if (Validate::isLoadedObject($product = new Product((int) Tools::getValue('id_product')))) { 1069 foreach ($_POST as $field => $value) { 1070 if (strncmp($field, 'label_', 6) == 0 && !Validate::isLabel($value)) { 1071 $this->errors[] = Tools::displayError('The label fields defined are invalid.'); 1072 } 1073 } 1074 if (empty($this->errors) && !$product->updateLabels()) { 1075 $this->errors[] = Tools::displayError('An error occurred while updating customization fields.'); 1076 } 1077 if (empty($this->errors)) { 1078 $this->confirmations[] = $this->l('Update successful'); 1079 } 1080 } else { 1081 $this->errors[] = Tools::displayError('A product must be created before adding customization.'); 1082 } 1083 } 1084 1085 /** 1086 * Overrides parent for custom redirect link 1087 * 1088 * @since 1.0.0 1089 */ 1090 public function processPosition() 1091 { 1092 /** @var Product $object */ 1093 if (!Validate::isLoadedObject($object = $this->loadObject())) { 1094 $this->errors[] = Tools::displayError('An error occurred while updating the status for an object.').' <b>'.$this->table.'</b> '.Tools::displayError('(cannot load object)'); 1095 } elseif (!$object->updatePosition((int) Tools::getValue('way'), (int) Tools::getValue('position'))) { 1096 $this->errors[] = Tools::displayError('Failed to update the position.'); 1097 } else { 1098 $category = new Category((int) Tools::getValue('id_category')); 1099 if (Validate::isLoadedObject($category)) { 1100 Hook::exec('actionCategoryUpdate', ['category' => $category]); 1101 } 1102 $this->redirect_after = static::$currentIndex.'&'.$this->table.'Orderby=position&'.$this->table.'Orderway=asc&action=Customization&conf=5'.(($idCategory = (Tools::getIsset('id_category') ? (int) Tools::getValue('id_category') : '')) ? ('&id_category='.$idCategory) : '').'&token='.Tools::getAdminTokenLite('AdminProducts'); 1103 } 1104 } 1105 1106 /** 1107 * Initialize processing 1108 * 1109 * @since 1.0.0 1110 */ 1111 public function initProcess() 1112 { 1113 if (Tools::isSubmit('submitAddproductAndStay') || Tools::isSubmit('submitAddproduct')) { 1114 // Clean up possible product type changes. 1115 $typeProduct = (int) Tools::getValue('type_product'); 1116 $idProduct = (int) Tools::getValue('id_product'); 1117 1118 if ($typeProduct !== Product::PTYPE_PACK) { 1119 if (!Pack::deleteItems($idProduct)) { 1120 $this->errors[] = Tools::displayError('Cannot delete product pack items.'); 1121 }; 1122 } 1123 if ($typeProduct !== Product::PTYPE_VIRTUAL) { 1124 $idProductDownload = ProductDownload::getIdFromIdProduct($idProduct, false); 1125 1126 if ($idProductDownload) { 1127 $productDownload = new ProductDownload($idProductDownload); 1128 if (!$productDownload->delete()) { 1129 $this->errors[] = Tools::displayError('Cannot delete product download.'); 1130 } 1131 } 1132 } 1133 } 1134 1135 // Delete a product in the download folder 1136 if (Tools::getValue('deleteVirtualProduct')) { 1137 if ($this->tabAccess['delete'] === '1') { 1138 $this->action = 'deleteVirtualProduct'; 1139 } else { 1140 $this->errors[] = Tools::displayError('You do not have permission to delete this.'); 1141 } 1142 } elseif (Tools::isSubmit('submitAddProductAndPreview')) { 1143 // Product preview 1144 $this->display = 'edit'; 1145 $this->action = 'save'; 1146 if (Tools::getValue('id_product')) { 1147 $this->id_object = Tools::getValue('id_product'); 1148 $this->object = new Product((int) Tools::getValue('id_product')); 1149 } 1150 } elseif (Tools::isSubmit('submitAttachments')) { 1151 if ($this->tabAccess['edit'] === '1') { 1152 $this->action = 'attachments'; 1153 $this->tab_display = 'attachments'; 1154 } else { 1155 $this->errors[] = Tools::displayError('You do not have permission to edit this.'); 1156 } 1157 } elseif (Tools::getIsset('duplicate'.$this->table)) { 1158 // Product duplication 1159 if ($this->tabAccess['add'] === '1') { 1160 $this->action = 'duplicate'; 1161 } else { 1162 $this->errors[] = Tools::displayError('You do not have permission to add this.'); 1163 } 1164 } elseif (Tools::getValue('id_image') && Tools::getValue('ajax')) { 1165 // Product images management 1166 if ($this->tabAccess['edit'] === '1') { 1167 $this->action = 'image'; 1168 } else { 1169 $this->errors[] = Tools::displayError('You do not have permission to edit this.'); 1170 } 1171 } elseif (Tools::isSubmit('submitProductAttribute')) { 1172 // Product attributes management 1173 if ($this->tabAccess['edit'] === '1') { 1174 $this->action = 'productAttribute'; 1175 } else { 1176 $this->errors[] = Tools::displayError('You do not have permission to edit this.'); 1177 } 1178 } elseif (Tools::isSubmit('submitFeatures') || Tools::isSubmit('submitFeaturesAndStay')) { 1179 // Product features management 1180 if ($this->tabAccess['edit'] === '1') { 1181 $this->action = 'features'; 1182 } else { 1183 $this->errors[] = Tools::displayError('You do not have permission to edit this.'); 1184 } 1185 } elseif (Tools::isSubmit('submitPricesModification')) { 1186 // Product specific prices management NEVER USED 1187 if ($this->tabAccess['add'] === '1') { 1188 $this->action = 'pricesModification'; 1189 } else { 1190 $this->errors[] = Tools::displayError('You do not have permission to add this.'); 1191 } 1192 } elseif (Tools::isSubmit('deleteSpecificPrice')) { 1193 if ($this->tabAccess['delete'] === '1') { 1194 $this->action = 'deleteSpecificPrice'; 1195 } else { 1196 $this->errors[] = Tools::displayError('You do not have permission to delete this.'); 1197 } 1198 } elseif (Tools::isSubmit('submitSpecificPricePriorities')) { 1199 if ($this->tabAccess['edit'] === '1') { 1200 $this->action = 'specificPricePriorities'; 1201 $this->tab_display = 'prices'; 1202 } else { 1203 $this->errors[] = Tools::displayError('You do not have permission to edit this.'); 1204 } 1205 } elseif (Tools::isSubmit('submitCustomizationConfiguration')) { 1206 // Customization management 1207 if ($this->tabAccess['edit'] === '1') { 1208 $this->action = 'customizationConfiguration'; 1209 $this->tab_display = 'customization'; 1210 $this->display = 'edit'; 1211 } else { 1212 $this->errors[] = Tools::displayError('You do not have permission to edit this.'); 1213 } 1214 } elseif (Tools::isSubmit('submitProductCustomization')) { 1215 if ($this->tabAccess['edit'] === '1') { 1216 $this->action = 'productCustomization'; 1217 $this->tab_display = 'customization'; 1218 $this->display = 'edit'; 1219 } else { 1220 $this->errors[] = Tools::displayError('You do not have permission to edit this.'); 1221 } 1222 } elseif (Tools::isSubmit('id_product')) { 1223 $postMaxSize = Tools::getMaxUploadSize(Configuration::get('PS_LIMIT_UPLOAD_FILE_VALUE') * 1024 * 1024); 1224 if ($postMaxSize && isset($_SERVER['CONTENT_LENGTH']) && $_SERVER['CONTENT_LENGTH'] && $_SERVER['CONTENT_LENGTH'] > $postMaxSize) { 1225 $this->errors[] = sprintf(Tools::displayError('The uploaded file exceeds the "Maximum size for a downloadable product" set in preferences (%1$dMB) or the post_max_size/ directive in php.ini (%2$dMB).'), number_format((Configuration::get('PS_LIMIT_UPLOAD_FILE_VALUE'))), ($postMaxSize / 1024 / 1024)); 1226 } 1227 } 1228 1229 if (!$this->action) { 1230 parent::initProcess(); 1231 } else { 1232 $this->id_object = (int) Tools::getValue($this->identifier); 1233 } 1234 1235 if (isset($this->available_tabs[Tools::getValue('key_tab')])) { 1236 $this->tab_display = Tools::getValue('key_tab'); 1237 } 1238 1239 // Set tab to display if not decided already 1240 if (!$this->tab_display && $this->action) { 1241 if (in_array($this->action, array_keys($this->available_tabs))) { 1242 $this->tab_display = $this->action; 1243 } 1244 } 1245 1246 // And if still not set, use default 1247 if (!$this->tab_display) { 1248 if (in_array($this->default_tab, $this->available_tabs)) { 1249 $this->tab_display = $this->default_tab; 1250 } else { 1251 $this->tab_display = key($this->available_tabs); 1252 } 1253 } 1254 } 1255 1256 /** 1257 * Has the given tab been submitted? 1258 * 1259 * @param string $tabName 1260 * 1261 * @return bool 1262 * 1263 * @since 1.0.0 1264 */ 1265 protected function isTabSubmitted($tabName) 1266 { 1267 if (!is_array($this->submitted_tabs)) { 1268 $this->submitted_tabs = Tools::getValue('submitted_tabs'); 1269 } 1270 1271 if (is_array($this->submitted_tabs) && in_array($tabName, $this->submitted_tabs)) { 1272 return true; 1273 } 1274 1275 return false; 1276 } 1277 1278 /** 1279 * postProcess handle every checks before saving products information 1280 * 1281 * @return void 1282 * 1283 * @since 1.0.0 1284 */ 1285 public function postProcess() 1286 { 1287 if (!$this->redirect_after) { 1288 parent::postProcess(); 1289 } 1290 1291 if ($this->display == 'edit' || $this->display == 'add') { 1292 $this->addJqueryUI( 1293 [ 1294 'ui.core', 1295 'ui.widget', 1296 ] 1297 ); 1298 1299 $this->addjQueryPlugin( 1300 [ 1301 'autocomplete', 1302 'tablednd', 1303 'thickbox', 1304 'ajaxfileupload', 1305 'date', 1306 'tagify', 1307 'select2', 1308 'validate', 1309 ] 1310 ); 1311 1312 $this->addJS( 1313 [ 1314 _PS_JS_DIR_.'admin/products.js', 1315 _PS_JS_DIR_.'admin/attributes.js', 1316 _PS_JS_DIR_.'admin/price.js', 1317 _PS_JS_DIR_.'tiny_mce/tiny_mce.js', 1318 _PS_JS_DIR_.'admin/tinymce.inc.js', 1319 _PS_JS_DIR_.'admin/dnd.js', 1320 _PS_JS_DIR_.'jquery/ui/jquery.ui.progressbar.min.js', 1321 _PS_JS_DIR_.'vendor/spin.js', 1322 _PS_JS_DIR_.'vendor/ladda.js', 1323 ] 1324 ); 1325 1326 $this->addJS(_PS_JS_DIR_.'jquery/plugins/select2/select2_locale_'.$this->context->language->iso_code.'.js'); 1327 $this->addJS(_PS_JS_DIR_.'jquery/plugins/validate/localization/messages_'.$this->context->language->iso_code.'.js'); 1328 1329 $this->addCSS( 1330 [ 1331 _PS_JS_DIR_.'jquery/plugins/timepicker/jquery-ui-timepicker-addon.css', 1332 ] 1333 ); 1334 } 1335 } 1336 1337 /** 1338 * Ajax process delete ProductAttribute 1339 * 1340 * @since 1.0.0 1341 */ 1342 public function ajaxProcessDeleteProductAttribute() 1343 { 1344 if (!Combination::isFeatureActive()) { 1345 return; 1346 } 1347 1348 if ($this->tabAccess['delete'] === '1') { 1349 $idProduct = (int) Tools::getValue('id_product'); 1350 $idProductAttribute = (int) Tools::getValue('id_product_attribute'); 1351 1352 if ($idProduct && Validate::isUnsignedId($idProduct) && Validate::isLoadedObject($product = new Product($idProduct))) { 1353 if (($dependsOnStock = StockAvailable::dependsOnStock($idProduct)) && StockAvailable::getQuantityAvailableByProduct($idProduct, $idProductAttribute)) { 1354 $json = [ 1355 'status' => 'error', 1356 'message' => $this->l('It is not possible to delete a combination while it still has some quantities in the Advanced Stock Management. You must delete its stock first.'), 1357 ]; 1358 } else { 1359 $product->deleteAttributeCombination((int) $idProductAttribute); 1360 $product->checkDefaultAttributes(); 1361 Tools::clearColorListCache((int) $product->id); 1362 if (!$product->hasAttributes()) { 1363 $product->cache_default_attribute = 0; 1364 $product->update(); 1365 } else { 1366 Product::updateDefaultAttribute($idProduct); 1367 } 1368 1369 if ($dependsOnStock && !Stock::deleteStockByIds($idProduct, $idProductAttribute)) { 1370 $json = [ 1371 'status' => 'error', 1372 'message' => $this->l('Error while deleting the stock'), 1373 ]; 1374 } else { 1375 $json = [ 1376 'status' => 'ok', 1377 'message' => $this->_conf[1], 1378 'id_product_attribute' => (int) $idProductAttribute, 1379 ]; 1380 } 1381 } 1382 } else { 1383 $json = [ 1384 'status' => 'error', 1385 'message' => $this->l('You cannot delete this attribute.'), 1386 ]; 1387 } 1388 } else { 1389 $json = [ 1390 'status' => 'error', 1391 'message' => $this->l('You do not have permission to delete this.'), 1392 ]; 1393 } 1394 1395 $this->ajaxDie(json_encode($json)); 1396 } 1397 1398 /** 1399 * Ajax process default ProductAttribute 1400 * 1401 * @since 1.0.0 1402 */ 1403 public function ajaxProcessDefaultProductAttribute() 1404 { 1405 if ($this->tabAccess['edit'] === '1') { 1406 if (!Combination::isFeatureActive()) { 1407 return; 1408 } 1409 1410 if (Validate::isLoadedObject($product = new Product((int) Tools::getValue('id_product')))) { 1411 $product->deleteDefaultAttributes(); 1412 $product->setDefaultAttribute((int) Tools::getValue('id_product_attribute')); 1413 $json = [ 1414 'status' => 'ok', 1415 'message' => $this->_conf[4], 1416 ]; 1417 } else { 1418 $json = [ 1419 'status' => 'error', 1420 'message' => $this->l('You cannot make this the default attribute.'), 1421 ]; 1422 } 1423 1424 $this->ajaxDie(json_encode($json)); 1425 } 1426 } 1427 1428 /** 1429 * Ajax process edit ProductAttribute 1430 * 1431 * @since 1.0.0 1432 */ 1433 public function ajaxProcessEditProductAttribute() 1434 { 1435 if ($this->tabAccess['edit'] === '1') { 1436 $idProduct = (int) Tools::getValue('id_product'); 1437 $idProductAttribute = (int) Tools::getValue('id_product_attribute'); 1438 if ($idProduct && Validate::isUnsignedId($idProduct) && Validate::isLoadedObject($product = new Product((int) $idProduct))) { 1439 $combinations = $product->getAttributeCombinationsById($idProductAttribute, $this->context->language->id); 1440 foreach ($combinations as $key => $combination) { 1441 $combinations[$key]['attributes'][] = [$combination['group_name'], $combination['attribute_name'], $combination['id_attribute']]; 1442 } 1443 1444 $this->ajaxDie(json_encode($combinations)); 1445 } 1446 } 1447 } 1448 1449 /** 1450 * Ajax pre process 1451 * 1452 * @since 1.0.0 1453 */ 1454 public function ajaxPreProcess() 1455 { 1456 if (Tools::getIsset('update'.$this->table) && Tools::getIsset('id_'.$this->table)) { 1457 $this->display = 'edit'; 1458 $this->action = Tools::getValue('action'); 1459 } 1460 } 1461 1462 /** 1463 * Ajax process update Product image shop association 1464 */ 1465 public function ajaxProcessUpdateProductImageShopAsso() 1466 { 1467 $idProduct = Tools::getValue('id_product'); 1468 if (($idImage = Tools::getValue('id_image')) && ($idShop = (int) Tools::getValue('id_shop'))) { 1469 if (Tools::getValue('active') == 'true') { 1470 $res = Db::getInstance()->execute('INSERT INTO '._DB_PREFIX_.'image_shop (`id_product`, `id_image`, `id_shop`, `cover`) VALUES('.(int) $idProduct.', '.(int) $idImage.', '.(int) $idShop.', NULL)'); 1471 } else { 1472 $res = Db::getInstance()->execute('DELETE FROM '._DB_PREFIX_.'image_shop WHERE `id_image` = '.(int) $idImage.' AND `id_shop` = '.(int) $idShop); 1473 } 1474 } 1475 1476 // Clean covers in image table 1477 if (isset($idShop)) { 1478 $countCoverImage = Db::getInstance()->getValue( 1479 ' 1480 SELECT COUNT(*) FROM '._DB_PREFIX_.'image i 1481 INNER JOIN '._DB_PREFIX_.'image_shop ish ON (i.id_image = ish.id_image AND ish.id_shop = '.(int) $idShop.') 1482 WHERE i.cover = 1 AND i.`id_product` = '.(int) $idProduct 1483 ); 1484 1485 if (!$idImage) { 1486 $idImage = Db::getInstance()->getValue( 1487 ' 1488 SELECT i.`id_image` FROM '._DB_PREFIX_.'image i 1489 INNER JOIN '._DB_PREFIX_.'image_shop ish ON (i.id_image = ish.id_image AND ish.id_shop = '.(int) $idShop.') 1490 WHERE i.`id_product` = '.(int) $idProduct 1491 ); 1492 } 1493 1494 if ($countCoverImage < 1) { 1495 Db::getInstance()->execute('UPDATE '._DB_PREFIX_.'image i SET i.cover = 1 WHERE i.id_image = '.(int) $idImage.' AND i.`id_product` = '.(int) $idProduct.' LIMIT 1'); 1496 } 1497 1498 // Clean covers in image_shop table 1499 $countCoverImageShop = Db::getInstance()->getValue( 1500 ' 1501 SELECT COUNT(*) 1502 FROM '._DB_PREFIX_.'image_shop ish 1503 WHERE ish.`id_product` = '.(int) $idProduct.' AND ish.id_shop = '.(int) $idShop.' AND ish.cover = 1' 1504 ); 1505 if ($countCoverImageShop < 1) { 1506 Db::getInstance()->execute('UPDATE '._DB_PREFIX_.'image_shop ish SET ish.cover = 1 WHERE ish.id_image = '.(int) $idImage.' AND ish.`id_product` = '.(int) $idProduct.' AND ish.id_shop = '.(int) $idShop.' LIMIT 1'); 1507 } 1508 } 1509 1510 if (isset($res) && $res) { 1511 $this->jsonConfirmation($this->_conf[27]); 1512 } else { 1513 $this->jsonError(Tools::displayError('An error occurred while attempting to associate this image with your shop. ')); 1514 } 1515 } 1516 1517 /** 1518 * Ajax process update image position 1519 * 1520 * @since 1.0.0 1521 */ 1522 public function ajaxProcessUpdateImagePosition() 1523 { 1524 if ($this->tabAccess['edit'] === '0') { 1525 $this->ajaxDie(json_encode(['error' => $this->l('You do not have the right permission')])); 1526 } 1527 $res = false; 1528 if ($json = Tools::getValue('json')) { 1529 // If there is an exception, at least the response is in JSON format. 1530 $this->json = true; 1531 1532 $res = true; 1533 $json = stripslashes($json); 1534 $images = json_decode($json, true); 1535 foreach ($images as $id => $position) { 1536 /* 1537 * If the the image is not associated with the currently 1538 * selected shop, the fields that are also in the image_shop 1539 * table (like id_product and cover) cannot be loaded properly, 1540 * so we have to load them separately. 1541 */ 1542 $img = new Image((int) $id); 1543 $def = $img::$definition; 1544 $sql = 'SELECT * FROM `' . _DB_PREFIX_ . $def['table'] . '` WHERE `' . $def['primary'] . '` = ' . (int) $id; 1545 $fields_from_table = Db::getInstance()->getRow($sql); 1546 foreach ($def['fields'] as $key => $value) { 1547 if (!$value['lang']) { 1548 $img->{$key} = $fields_from_table[$key]; 1549 } 1550 } 1551 $img->position = (int) $position; 1552 $res &= $img->update(); 1553 } 1554 } 1555 if ($res) { 1556 $this->jsonConfirmation($this->_conf[25]); 1557 } else { 1558 $this->jsonError(Tools::displayError('An error occurred while attempting to move this picture.')); 1559 } 1560 } 1561 1562 /** 1563 * Ajax process update cover 1564 * 1565 * @since 1.0.0 1566 */ 1567 public function ajaxProcessUpdateCover() 1568 { 1569 if ($this->tabAccess['edit'] === '0') { 1570 $this->ajaxDie(json_encode(['error' => $this->l('You do not have the right permission')])); 1571 } 1572 Image::deleteCover((int) Tools::getValue('id_product')); 1573 $id_image = (int) Tools::getValue('id_image'); 1574 1575 /* 1576 * If the the image is not associated with the currently selected shop, 1577 * the fields that are also in the image_shop table (like id_product and 1578 * cover) cannot be loaded properly, so we have to load them separately. 1579 */ 1580 $img = new Image($id_image); 1581 $def = $img::$definition; 1582 $sql = 'SELECT * FROM `' . _DB_PREFIX_ . $def['table'] . '` WHERE `' . $def['primary'] . '` = ' . $id_image; 1583 $fields_from_table = Db::getInstance()->getRow($sql); 1584 foreach ($def['fields'] as $key => $value) { 1585 if (!$value['lang']) { 1586 $img->{$key} = $fields_from_table[$key]; 1587 } 1588 } 1589 $img->cover = 1; 1590 1591 @unlink(_PS_TMP_IMG_DIR_.'product_'.(int) $img->id_product.'.jpg'); 1592 @unlink(_PS_TMP_IMG_DIR_.'product_mini_'.(int) $img->id_product.'_'.$this->context->shop->id.'.jpg'); 1593 1594 if ($img->update()) { 1595 $this->jsonConfirmation($this->_conf[26]); 1596 } else { 1597 $this->jsonError(Tools::displayError('An error occurred while attempting to update the cover picture.')); 1598 } 1599 } 1600 1601 /** 1602 * Ajax process delete product image 1603 * 1604 * @since 1.0.0 1605 */ 1606 public function ajaxProcessDeleteProductImage() 1607 { 1608 $this->display = 'content'; 1609 $res = true; 1610 /* Delete product image */ 1611 $image = new Image((int) Tools::getValue('id_image')); 1612 $this->content['id'] = $image->id; 1613 $res &= $image->delete(); 1614 // if deleted image was the cover, change it to the first one 1615 if (!Image::getCover($image->id_product)) { 1616 $res &= Db::getInstance()->execute( 1617 ' 1618 UPDATE `'._DB_PREFIX_.'image_shop` image_shop 1619 SET image_shop.`cover` = 1 1620 WHERE image_shop.`id_product` = '.(int) $image->id_product.' 1621 AND id_shop='.(int) $this->context->shop->id.' LIMIT 1' 1622 ); 1623 } 1624 1625 if (!Image::getGlobalCover($image->id_product)) { 1626 $res &= Db::getInstance()->execute( 1627 ' 1628 UPDATE `'._DB_PREFIX_.'image` i 1629 SET i.`cover` = 1 1630 WHERE i.`id_product` = '.(int) $image->id_product.' LIMIT 1' 1631 ); 1632 } 1633 1634 if (file_exists(_PS_TMP_IMG_DIR_.'product_'.$image->id_product.'.jpg')) { 1635 $res &= @unlink(_PS_TMP_IMG_DIR_.'product_'.$image->id_product.'.jpg'); 1636 } 1637 if (file_exists(_PS_TMP_IMG_DIR_.'product_mini_'.$image->id_product.'_'.$this->context->shop->id.'.jpg')) { 1638 $res &= @unlink(_PS_TMP_IMG_DIR_.'product_mini_'.$image->id_product.'_'.$this->context->shop->id.'.jpg'); 1639 } 1640 1641 if ($res) { 1642 $this->jsonConfirmation($this->_conf[7]); 1643 } else { 1644 $this->jsonError(Tools::displayError('An error occurred while attempting to delete the product image.')); 1645 } 1646 } 1647 1648 /** 1649 * Add or update a product image 1650 * 1651 * @param Product $product Product object to add image 1652 * @param string $method 1653 * 1654 * @return int|false 1655 * 1656 * @since 1.0.0 1657 */ 1658 public function addProductImage($product, $method = 'auto') 1659 { 1660 /* Updating an existing product image */ 1661 if ($idImage = (int) Tools::getValue('id_image')) { 1662 $image = new Image((int) $idImage); 1663 if (!Validate::isLoadedObject($image)) { 1664 $this->errors[] = Tools::displayError('An error occurred while loading the object image.'); 1665 } else { 1666 if (($cover = Tools::getValue('cover')) == 1) { 1667 Image::deleteCover($product->id); 1668 } 1669 $image->cover = $cover; 1670 $this->validateRules('Image'); 1671 $this->copyFromPost($image, 'image'); 1672 if (count($this->errors) || !$image->update()) { 1673 $this->errors[] = Tools::displayError('An error occurred while updating the image.'); 1674 } elseif (isset($_FILES['image_product']['tmp_name']) && $_FILES['image_product']['tmp_name'] != null) { 1675 $this->copyImage($product->id, $image->id, $method); 1676 } 1677 } 1678 } 1679 if (isset($image) && Validate::isLoadedObject($image) && !file_exists(_PS_PROD_IMG_DIR_.$image->getExistingImgPath().'.'.$image->image_format)) { 1680 $image->delete(); 1681 } 1682 if (count($this->errors)) { 1683 return false; 1684 } 1685 @unlink(_PS_TMP_IMG_DIR_.'product_'.$product->id.'.jpg'); 1686 @unlink(_PS_TMP_IMG_DIR_.'product_mini_'.$product->id.'_'.$this->context->shop->id.'.jpg'); 1687 1688 return ((isset($idImage) && is_int($idImage) && $idImage) ? $idImage : false); 1689 } 1690 1691 /** 1692 * @param Product|ObjectModel $object 1693 * @param string $table 1694 * 1695 * @since 1.0.0 1696 */ 1697 protected function copyFromPost(&$object, $table) 1698 { 1699 parent::copyFromPost($object, $table); 1700 if (get_class($object) != 'Product') { 1701 return; 1702 } 1703 1704 /* Additional fields */ 1705 foreach (Language::getIDs(false) as $idLang) { 1706 if (isset($_POST['meta_keywords_'.$idLang])) { 1707 $_POST['meta_keywords_'.$idLang] = $this->_cleanMetaKeywords(mb_strtolower($_POST['meta_keywords_'.$idLang])); 1708 // preg_replace('/ *,? +,* /', ',', strtolower($_POST['meta_keywords_'.$id_lang])); 1709 $object->meta_keywords[$idLang] = $_POST['meta_keywords_'.$idLang]; 1710 } 1711 } 1712 $_POST['width'] = empty($_POST['width']) ? '0' : $_POST['width']; 1713 $_POST['height'] = empty($_POST['height']) ? '0' : $_POST['height']; 1714 $_POST['depth'] = empty($_POST['depth']) ? '0' : $_POST['depth']; 1715 $_POST['weight'] = empty($_POST['weight']) ? '0' : $_POST['weight']; 1716 1717 if (Tools::getIsset('unit_price') != null) { 1718 $object->unit_price = priceval(Tools::getValue('unit_price')); 1719 } 1720 if (Tools::getIsset('ecotax') != null) { 1721 $object->ecotax = priceval(Tools::getValue('ecotax')); 1722 } 1723 1724 if ($this->isTabSubmitted('Informations')) { 1725 if ($this->checkMultishopBox('available_for_order', $this->context)) { 1726 $object->available_for_order = (int) Tools::getValue('available_for_order'); 1727 } 1728 1729 if ($this->checkMultishopBox('show_price', $this->context)) { 1730 $object->show_price = $object->available_for_order ? 1 : (int) Tools::getValue('show_price'); 1731 } 1732 1733 if ($this->checkMultishopBox('online_only', $this->context)) { 1734 $object->online_only = (int) Tools::getValue('online_only'); 1735 } 1736 } 1737 if ($this->isTabSubmitted('Prices')) { 1738 $object->on_sale = (int) Tools::getValue('on_sale'); 1739 } 1740 } 1741 1742 /** 1743 * Clean meta keywords 1744 * 1745 * @param array $keywords 1746 * 1747 * @return string 1748 */ 1749 protected function _cleanMetaKeywords($keywords) 1750 { 1751 if (!empty($keywords) && $keywords != '') { 1752 $out = []; 1753 $words = explode(',', $keywords); 1754 foreach ($words as $wordItem) { 1755 $wordItem = trim($wordItem); 1756 if (!empty($wordItem) && $wordItem != '') { 1757 $out[] = $wordItem; 1758 } 1759 } 1760 1761 return ((count($out) > 0) ? implode(',', $out) : ''); 1762 } else { 1763 return ''; 1764 } 1765 } 1766 1767 /** 1768 * @param string $field 1769 * @param null $context 1770 * 1771 * @return bool 1772 * 1773 * @since 1.0.0 1774 */ 1775 public function checkMultishopBox($field, $context = null) 1776 { 1777 static $checkbox = null; 1778 static $shopContext = null; 1779 1780 if ($context == null && $shopContext == null) { 1781 $context = $this->context; 1782 } 1783 1784 if ($shopContext == null) { 1785 $shopContext = $context->shop->getContext(); 1786 } 1787 1788 if ($checkbox == null) { 1789 $checkbox = Tools::getValue('multishop_check', []); 1790 } 1791 1792 if ($shopContext == Shop::CONTEXT_SHOP) { 1793 return true; 1794 } 1795 1796 if (isset($checkbox[$field]) && $checkbox[$field] == 1) { 1797 return true; 1798 } 1799 1800 return false; 1801 } 1802 1803 /** 1804 * Copy a product image 1805 * 1806 * @param int $idProduct Product Id for product image filename 1807 * @param int $idImage Image Id for product image filename 1808 * @param string $method 1809 * 1810 * @return void|false 1811 * @throws PrestaShopException 1812 */ 1813 public function copyImage($idProduct, $idImage, $method = 'auto') 1814 { 1815 if (!isset($_FILES['image_product']['tmp_name'])) { 1816 return false; 1817 } 1818 if ($error = ImageManager::validateUpload($_FILES['image_product'])) { 1819 $this->errors[] = $error; 1820 } else { 1821 $highDpi = (bool) Configuration::get('PS_HIGHT_DPI'); 1822 1823 $image = new Image($idImage); 1824 1825 if (!$newPath = $image->getPathForCreation()) { 1826 $this->errors[] = Tools::displayError('An error occurred while attempting to create a new folder.'); 1827 } 1828 if (!($tmpName = tempnam(_PS_TMP_IMG_DIR_, 'PS')) || !move_uploaded_file($_FILES['image_product']['tmp_name'], $tmpName)) { 1829 $this->errors[] = Tools::displayError('An error occurred during the image upload process.'); 1830 } elseif (!ImageManager::resize($tmpName, $newPath.'.'.$image->image_format)) { 1831 $this->errors[] = Tools::displayError('An error occurred while copying the image.'); 1832 } elseif ($method == 'auto') { 1833 $imagesTypes = ImageType::getImagesTypes('products'); 1834 foreach ($imagesTypes as $k => $imageType) { 1835 if (!ImageManager::resize( 1836 $tmpName, 1837 $newPath.'-'.stripslashes($imageType['name']).'.'.$image->image_format, 1838 (int) $imageType['width'], 1839 (int) $imageType['height'], 1840 $image->image_format 1841 )) { 1842 $this->errors[] = Tools::displayError('An error occurred while copying this image:').' '.stripslashes($imageType['name']); 1843 } else { 1844 if ($highDpi) { 1845 ImageManager::resize( 1846 $tmpName, 1847 $newPath.'-'.stripslashes($imageType['name']).'2x.'.$image->image_format, 1848 (int) $imageType['width'] * 2, 1849 (int) $imageType['height'] * 2, 1850 $image->image_format 1851 ); 1852 } 1853 1854 if (ImageManager::webpSupport()) { 1855 ImageManager::resize( 1856 $tmpName, 1857 $newPath.'-'.stripslashes($imageType['name']).'.webp', 1858 (int) $imageType['width'], 1859 (int) $imageType['height'], 1860 'webp' 1861 ); 1862 1863 if ($highDpi) { 1864 ImageManager::resize( 1865 $tmpName, 1866 $newPath.'-'.stripslashes($imageType['name']).'2x.webp', 1867 (int) $imageType['width'] * 2, 1868 (int) $imageType['height'] * 2, 1869 'webp' 1870 ); 1871 } 1872 } 1873 1874 if ((int) Configuration::get('TB_IMAGES_LAST_UPD_PRODUCTS') < $idProduct) { 1875 Configuration::updateValue('TB_IMAGES_LAST_UPD_PRODUCTS', $idProduct); 1876 } 1877 } 1878 } 1879 } 1880 1881 @unlink($tmpName); 1882 Hook::exec('actionWatermark', ['id_image' => $idImage, 'id_product' => $idProduct]); 1883 } 1884 } 1885 1886 /** 1887 * Process add 1888 * 1889 * @return bool|ObjectModel 1890 * 1891 * @since 1.0.0 1892 */ 1893 public function processAdd() 1894 { 1895 $this->checkProduct(); 1896 1897 if (!empty($this->errors)) { 1898 $this->display = 'add'; 1899 1900 return false; 1901 } 1902 1903 $this->object = new $this->className(); 1904 $this->_removeTaxFromEcotax(); 1905 $this->copyFromPost($this->object, $this->table); 1906 if ($this->object->add()) { 1907 Logger::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); 1908 $this->addCarriers($this->object); 1909 $this->updateAccessories($this->object); 1910 $this->updatePackItems($this->object); 1911 $this->updateDownloadProduct($this->object); 1912 1913 if (Configuration::get('PS_FORCE_ASM_NEW_PRODUCT') && Configuration::get('PS_ADVANCED_STOCK_MANAGEMENT') && $this->object->getType() != Product::PTYPE_VIRTUAL) { 1914 $this->object->advanced_stock_management = 1; 1915 $this->object->save(); 1916 $idShops = Shop::getContextListShopID(); 1917 foreach ($idShops as $idShop) { 1918 StockAvailable::setProductDependsOnStock($this->object->id, true, (int) $idShop, 0); 1919 } 1920 } 1921 1922 if (empty($this->errors)) { 1923 $languages = Language::getLanguages(false); 1924 if ($this->isProductFieldUpdated('category_box') && !$this->object->updateCategories(Tools::getValue('categoryBox'))) { 1925 $this->errors[] = Tools::displayError('An error occurred while linking the object.').' <b>'.$this->table.'</b> '.Tools::displayError('To categories'); 1926 } elseif (!$this->updateTags($languages, $this->object)) { 1927 $this->errors[] = Tools::displayError('An error occurred while adding tags.'); 1928 } else { 1929 Hook::exec('actionProductAdd', ['id_product' => (int) $this->object->id, 'product' => $this->object]); 1930 if (in_array($this->object->visibility, ['both', 'search']) && Configuration::get('PS_SEARCH_INDEXATION')) { 1931 Search::indexation(false, $this->object->id); 1932 } 1933 } 1934 1935 if (Configuration::get('PS_DEFAULT_WAREHOUSE_NEW_PRODUCT') != 0 && Configuration::get('PS_ADVANCED_STOCK_MANAGEMENT')) { 1936 $warehouseLocationEntity = new WarehouseProductLocation(); 1937 $warehouseLocationEntity->id_product = $this->object->id; 1938 $warehouseLocationEntity->id_product_attribute = 0; 1939 $warehouseLocationEntity->id_warehouse = Configuration::get('PS_DEFAULT_WAREHOUSE_NEW_PRODUCT'); 1940 $warehouseLocationEntity->location = pSQL(''); 1941 $warehouseLocationEntity->save(); 1942 } 1943 1944 // Apply groups reductions 1945 $this->object->setGroupReduction(); 1946 1947 // Save and preview 1948 if (Tools::isSubmit('submitAddProductAndPreview')) { 1949 $this->redirect_after = $this->getPreviewUrl($this->object); 1950 } 1951 1952 // Save and stay on same form 1953 if ($this->display == 'edit') { 1954 $this->redirect_after = static::$currentIndex.'&id_product='.(int) $this->object->id.(Tools::getIsset('id_category') ? '&id_category='.(int) Tools::getValue('id_category') : '').'&updateproduct&conf=3&key_tab='.Tools::safeOutput(Tools::getValue('key_tab')).'&token='.$this->token; 1955 } else { 1956 // Default behavior (save and back) 1957 $this->redirect_after = static::$currentIndex.(Tools::getIsset('id_category') ? '&id_category='.(int) Tools::getValue('id_category') : '').'&conf=3&token='.$this->token; 1958 } 1959 } else { 1960 $this->object->delete(); 1961 // if errors : stay on edit page 1962 $this->display = 'edit'; 1963 } 1964 } else { 1965 $this->errors[] = Tools::displayError('An error occurred while creating an object.').' <b>'.$this->table.'</b>'; 1966 } 1967 1968 return $this->object; 1969 } 1970 1971 /** 1972 * Check that a saved product is valid 1973 */ 1974 public function checkProduct() 1975 { 1976 $className = 'Product'; 1977 // @todo : the call_user_func seems to contains only statics values (className = 'Product') 1978 $rules = call_user_func([$this->className, 'getValidationRules'], $this->className); 1979 $defaultLanguage = new Language((int) Configuration::get('PS_LANG_DEFAULT')); 1980 $languages = Language::getLanguages(false); 1981 1982 // Check required fields 1983 foreach ($rules['required'] as $field) { 1984 if (!$this->isProductFieldUpdated($field)) { 1985 continue; 1986 } 1987 1988 if (($value = Tools::getValue($field)) == false && $value != '0') { 1989 if (Tools::getValue('id_'.$this->table) && $field == 'passwd') { 1990 continue; 1991 } 1992 $this->errors[] = sprintf( 1993 Tools::displayError('The %s field is required.'), 1994 call_user_func([$className, 'displayFieldName'], $field, $className) 1995 ); 1996 } 1997 } 1998 1999 // Check multilingual required fields 2000 foreach ($rules['requiredLang'] as $fieldLang) { 2001 if ($this->isProductFieldUpdated($fieldLang, $defaultLanguage->id) && !Tools::getValue($fieldLang.'_'.$defaultLanguage->id)) { 2002 $this->errors[] = sprintf( 2003 Tools::displayError('This %1$s field is required at least in %2$s'), 2004 call_user_func([$className, 'displayFieldName'], $fieldLang, $className), 2005 $defaultLanguage->name 2006 ); 2007 } 2008 } 2009 2010 // Check fields sizes 2011 foreach ($rules['size'] as $field => $maxLength) { 2012 if ($this->isProductFieldUpdated($field) && ($value = Tools::getValue($field)) && mb_strlen($value) > $maxLength) { 2013 $this->errors[] = sprintf( 2014 Tools::displayError('The %1$s field is too long (%2$d chars max).'), 2015 call_user_func([$className, 'displayFieldName'], $field, $className), 2016 $maxLength 2017 ); 2018 } 2019 } 2020 2021 if (Tools::getIsset('description_short') && $this->isProductFieldUpdated('description_short')) { 2022 $saveShort = Tools::getValue('description_short'); 2023 $_POST['description_short'] = strip_tags(Tools::getValue('description_short')); 2024 } 2025 2026 // Check description short size without html 2027 $limit = (int) Configuration::get('PS_PRODUCT_SHORT_DESC_LIMIT'); 2028 if ($limit <= 0) { 2029 $limit = 400; 2030 } 2031 foreach ($languages as $language) { 2032 if ($this->isProductFieldUpdated('description_short', $language['id_lang']) && ($value = Tools::getValue('description_short_'.$language['id_lang']))) { 2033 if (mb_strlen(strip_tags($value)) > $limit) { 2034 $this->errors[] = sprintf( 2035 Tools::displayError('This %1$s field (%2$s) is too long: %3$d chars max (current count %4$d).'), 2036 call_user_func([$className, 'displayFieldName'], 'description_short'), 2037 $language['name'], 2038 $limit, 2039 mb_strlen(strip_tags($value)) 2040 ); 2041 } 2042 } 2043 } 2044 2045 // Check multilingual fields sizes 2046 foreach ($rules['sizeLang'] as $fieldLang => $maxLength) { 2047 foreach ($languages as $language) { 2048 $value = Tools::getValue($fieldLang.'_'.$language['id_lang']); 2049 if ($value && mb_strlen($value) > $maxLength) { 2050 $this->errors[] = sprintf( 2051 Tools::displayError('The %1$s field is too long (%2$d chars max).'), 2052 call_user_func([$className, 'displayFieldName'], $fieldLang, $className), 2053 $maxLength 2054 ); 2055 } 2056 } 2057 } 2058 2059 if ($this->isProductFieldUpdated('description_short') && isset($_POST['description_short'])) { 2060 $_POST['description_short'] = $saveShort; 2061 } 2062 2063 // Check fields validity 2064 foreach ($rules['validate'] as $field => $function) { 2065 if ($this->isProductFieldUpdated($field) && ($value = Tools::getValue($field))) { 2066 $res = true; 2067 if (mb_strtolower($function) == 'iscleanhtml') { 2068 if (!Validate::$function($value, (int) Configuration::get('PS_ALLOW_HTML_IFRAME'))) { 2069 $res = false; 2070 } 2071 } elseif (!Validate::$function($value)) { 2072 $res = false; 2073 } 2074 2075 if (!$res) { 2076 $this->errors[] = sprintf( 2077 Tools::displayError('The %s field is invalid.'), 2078 call_user_func([$className, 'displayFieldName'], $field, $className) 2079 ); 2080 } 2081 } 2082 } 2083 // Check multilingual fields validity 2084 foreach ($rules['validateLang'] as $fieldLang => $function) { 2085 foreach ($languages as $language) { 2086 if ($this->isProductFieldUpdated($fieldLang, $language['id_lang']) && ($value = Tools::getValue($fieldLang.'_'.$language['id_lang']))) { 2087 if (!Validate::$function($value, (int) Configuration::get('PS_ALLOW_HTML_IFRAME'))) { 2088 $this->errors[] = sprintf( 2089 Tools::displayError('The %1$s field (%2$s) is invalid.'), 2090 call_user_func([$className, 'displayFieldName'], $fieldLang, $className), 2091 $language['name'] 2092 ); 2093 } 2094 } 2095 } 2096 } 2097 2098 // Categories 2099 if ($this->isProductFieldUpdated('id_category_default') && (!Tools::isSubmit('categoryBox') || !count(Tools::getValue('categoryBox')))) { 2100 $this->errors[] = $this->l('Products must be in at least one category.'); 2101 } 2102 2103 if ($this->isProductFieldUpdated('id_category_default') && (!is_array(Tools::getValue('categoryBox')) || !in_array(Tools::getValue('id_category_default'), Tools::getValue('categoryBox')))) { 2104 $this->errors[] = $this->l('This product must be in the default category.'); 2105 } 2106 2107 // Tags 2108 foreach ($languages as $language) { 2109 if ($value = Tools::getValue('tags_'.$language['id_lang'])) { 2110 if (!Validate::isTagsList($value)) { 2111 $this->errors[] = sprintf( 2112 Tools::displayError('The tags list (%s) is invalid.'), 2113 $language['name'] 2114 ); 2115 } 2116 } 2117 } 2118 } 2119 2120 /** 2121 * Check if a field is edited (if the checkbox is checked) 2122 * This method will do something only for multishop with a context all / group 2123 * 2124 * @param string $field Name of field 2125 * @param int $idLang 2126 * 2127 * @return bool 2128 */ 2129 protected function isProductFieldUpdated($field, $idLang = null) 2130 { 2131 // Cache this condition to improve performances 2132 static $isActivated = null; 2133 if (is_null($isActivated)) { 2134 $isActivated = Shop::isFeatureActive() && Shop::getContext() != Shop::CONTEXT_SHOP && $this->id_object; 2135 } 2136 2137 if (!$isActivated) { 2138 return true; 2139 } 2140 2141 $def = ObjectModel::getDefinition($this->object); 2142 if (!$this->object->isMultiShopField($field) && is_null($idLang) && isset($def['fields'][$field])) { 2143 return true; 2144 } 2145 2146 if (is_null($idLang)) { 2147 return !empty($_POST['multishop_check'][$field]); 2148 } else { 2149 return !empty($_POST['multishop_check'][$field][$idLang]); 2150 } 2151 } 2152 2153 /** 2154 * Checking customs feature 2155 * 2156 * @since 1.0.0 2157 */ 2158 protected function _removeTaxFromEcotax() 2159 { 2160 if ($ecotax = Tools::getValue('ecotax')) { 2161 $_POST['ecotax'] 2162 = priceval($ecotax / (1 + Tax::getProductEcotaxRate() / 100)); 2163 } 2164 } 2165 2166 protected function addCarriers($product = null) 2167 { 2168 if (!isset($product)) { 2169 $product = new Product((int) Tools::getValue('id_product')); 2170 } 2171 2172 if (Validate::isLoadedObject($product)) { 2173 $carriers = []; 2174 2175 if (Tools::getValue('selectedCarriers')) { 2176 $carriers = Tools::getValue('selectedCarriers'); 2177 } 2178 2179 $product->setCarriers($carriers); 2180 } 2181 } 2182 2183 /** 2184 * Update product accessories 2185 * 2186 * @param object $product Product 2187 * 2188 * @since 1.0.0 2189 */ 2190 public function updateAccessories($product) 2191 { 2192 $product->deleteAccessories(); 2193 if ($accessories = Tools::getValue('inputAccessories')) { 2194 $accessoriesId = array_unique(explode('-', $accessories)); 2195 if (count($accessoriesId)) { 2196 array_pop($accessoriesId); 2197 $product->changeAccessories($accessoriesId); 2198 } 2199 } 2200 } 2201 2202 /** 2203 * delete all items in pack, then check if type_product value is PTYPE_PACK. 2204 * if yes, add the pack items from input "inputPackItems" 2205 * 2206 * @param Product $product 2207 * 2208 * @return bool 2209 */ 2210 public function updatePackItems($product) 2211 { 2212 Pack::deleteItems($product->id); 2213 // lines format: QTY x ID-QTY x ID 2214 if (Tools::getValue('type_product') == Product::PTYPE_PACK) { 2215 $product->setDefaultAttribute(0);//reset cache_default_attribute 2216 $items = Tools::getValue('inputPackItems'); 2217 $lines = array_unique(explode('-', $items)); 2218 2219 // lines is an array of string with format : QTYxIDxID_PRODUCT_ATTRIBUTE 2220 if (count($lines)) { 2221 foreach ($lines as $line) { 2222 if (!empty($line)) { 2223 $itemIdAttribute = 0; 2224 count($array = explode('x', $line)) == 3 ? list($qty, $itemId, $itemIdAttribute) = $array : list($qty, $itemId) = $array; 2225 if ($qty > 0 && isset($itemId)) { 2226 if (Pack::isPack((int) $itemId || $product->id == (int) $itemId)) { 2227 $this->errors[] = Tools::displayError('You can\'t add product packs into a pack'); 2228 } elseif (!Pack::addItem((int) $product->id, (int) $itemId, (int) $qty, (int) $itemIdAttribute)) { 2229 $this->errors[] = Tools::displayError('An error occurred while attempting to add products to the pack.'); 2230 } 2231 } 2232 } 2233 } 2234 } 2235 } 2236 } 2237 2238 /** 2239 * Update product download 2240 * 2241 * @param Product $product 2242 * @param int $edit Deprecated in favor of autodetection. 2243 * 2244 * @return bool 2245 * 2246 * @since 1.0.3 Deprecate $edit in favor of autodetection. 2247 * @since 1.0.0 2248 */ 2249 public function updateDownloadProduct($product, $edit = 999) 2250 { 2251 if ($edit !== 999) { 2252 Tools::displayParameterAsDeprecated('edit'); 2253 } 2254 2255 $idProductDownload = ProductDownload::getIdFromIdProduct($product->id, false); 2256 if (!$idProductDownload) { 2257 $idProductDownload = (int) Tools::getValue('virtual_product_id'); 2258 } 2259 2260 if (Tools::getValue('type_product') == Product::PTYPE_VIRTUAL 2261 && Tools::getValue('is_virtual_file') == 1) { 2262 if (isset($_FILES['virtual_product_file_uploader']) && $_FILES['virtual_product_file_uploader']['size'] > 0) { 2263 $filename = ProductDownload::getNewFilename(); 2264 $helper = new HelperUploader('virtual_product_file_uploader'); 2265 $helper->setPostMaxSize(Tools::getOctets(ini_get('upload_max_filesize'))) 2266 ->setSavePath(_PS_DOWNLOAD_DIR_)->upload($_FILES['virtual_product_file_uploader'], $filename); 2267 } else { 2268 $filename = Tools::getValue('virtual_product_filename', ProductDownload::getNewFilename()); 2269 } 2270 2271 $product->setDefaultAttribute(0); //reset cache_default_attribute 2272 2273 $active = Tools::getValue('virtual_product_active'); 2274 $isShareable = Tools::getValue('virtual_product_is_shareable'); 2275 $name = Tools::getValue('virtual_product_name'); 2276 $nbDays = Tools::getValue('virtual_product_nb_days'); 2277 $nbDownloable = Tools::getValue('virtual_product_nb_downloable'); 2278 $expirationDate = Tools::getValue('virtual_product_expiration_date'); 2279 // This whould allow precision up to the second, not supported by 2280 // the datepicker in the GUI, yet. 2281 //if ($expirationDate 2282 // && !preg_match('/\d{1,2}\:\d{1,2}/', $expirationDate)) { 2283 // // No time given should mean the end of the day. 2284 // $dateExpiration .= ' 23:59:59'; 2285 //} 2286 if ($expirationDate) { 2287 // We want the end of the given day. 2288 $expirationDate .= ' 23:59:59'; 2289 } 2290 2291 $download = new ProductDownload($idProductDownload); 2292 $download->id_product = (int) $product->id; 2293 $download->display_filename = $name; 2294 $download->filename = $filename; 2295 $download->date_expiration = $expirationDate; 2296 $download->nb_days_accessible = (int) $nbDays; 2297 $download->nb_downloadable = (int) $nbDownloable; 2298 $download->active = (int) $active; 2299 $download->is_shareable = (int) $isShareable; 2300 if ($download->save()) { 2301 return true; 2302 } 2303 } else { 2304 // Delete the download and its file. 2305 if ($idProductDownload) { 2306 $productDownload = new ProductDownload($idProductDownload); 2307 2308 return $productDownload->delete(); 2309 } 2310 } 2311 2312 return false; 2313 } 2314 2315 /** 2316 * Update product tags 2317 * 2318 * @param array $languages Array languages 2319 * @param object $product Product 2320 * 2321 * @return bool Update result 2322 * 2323 * @since 1.0.0 2324 */ 2325 public function updateTags($languages, $product) 2326 { 2327 $tagSuccess = true; 2328 /* Reset all tags for THIS product */ 2329 if (!Tag::deleteTagsForProduct((int) $product->id)) { 2330 $this->errors[] = Tools::displayError('An error occurred while attempting to delete previous tags.'); 2331 } 2332 /* Assign tags to this product */ 2333 foreach ($languages as $language) { 2334 if ($value = Tools::getValue('tags_'.$language['id_lang'])) { 2335 $tagSuccess &= Tag::addTags($language['id_lang'], (int) $product->id, $value); 2336 } 2337 } 2338 2339 if (!$tagSuccess) { 2340 $this->errors[] = Tools::displayError('An error occurred while adding tags.'); 2341 } 2342 2343 return $tagSuccess; 2344 } 2345 2346 /** 2347 * @param Product $product 2348 * 2349 * @return bool|string 2350 * 2351 * @since 1.0.0 2352 */ 2353 public function getPreviewUrl(Product $product) 2354 { 2355 $idLang = Configuration::get('PS_LANG_DEFAULT', null, null, $this->context->shop->id); 2356 2357 if (!Validate::isLoadedObject($product) || !$product->id_category_default) { 2358 return $this->l('Unable to determine the preview URL. This product has not been linked with a category, yet.'); 2359 } 2360 2361 if (!ShopUrl::getMainShopDomain()) { 2362 return false; 2363 } 2364 2365 $isRewriteActive = (bool) Configuration::get('PS_REWRITING_SETTINGS'); 2366 $previewUrl = $this->context->link->getProductLink( 2367 $product, 2368 $this->getFieldValue($product, 'link_rewrite', $this->context->language->id), 2369 Category::getLinkRewrite($this->getFieldValue($product, 'id_category_default'), $this->context->language->id), 2370 null, 2371 $idLang, 2372 (int) $this->context->shop->id, 2373 0, 2374 $isRewriteActive 2375 ); 2376 2377 if (!$product->active) { 2378 $adminDir = dirname($_SERVER['PHP_SELF']); 2379 $adminDir = substr($adminDir, strrpos($adminDir, '/') + 1); 2380 $previewUrl .= ((strpos($previewUrl, '?') === false) ? '?' : '&').'adtoken='.$this->token.'&ad='.$adminDir.'&id_employee='.(int) $this->context->employee->id; 2381 } 2382 2383 return $previewUrl; 2384 } 2385 2386 /** 2387 * @return bool|false|ObjectModel 2388 * 2389 * @since 1.0.0 2390 */ 2391 public function processStatus() 2392 { 2393 $this->loadObject(true); 2394 if (!Validate::isLoadedObject($this->object)) { 2395 return false; 2396 } 2397 if (($error = $this->object->validateFields(false, true)) !== true) { 2398 $this->errors[] = $error; 2399 } 2400 if (($error = $this->object->validateFieldsLang(false, true)) !== true) { 2401 $this->errors[] = $error; 2402 } 2403 2404 if (count($this->errors)) { 2405 return false; 2406 } 2407 2408 $res = parent::processStatus(); 2409 2410 $query = trim(Tools::getValue('bo_query')); 2411 $searchType = (int) Tools::getValue('bo_search_type'); 2412 2413 if ($query) { 2414 $this->redirect_after = preg_replace('/[\?|&](bo_query|bo_search_type)=([^&]*)/i', '', $this->redirect_after); 2415 $this->redirect_after .= '&bo_query='.$query.'&bo_search_type='.$searchType; 2416 } 2417 2418 return $res; 2419 } 2420 2421 /** 2422 * Process update 2423 * 2424 * @return bool|Product 2425 * 2426 * @since 1.0.0 2427 */ 2428 public function processUpdate() 2429 { 2430 $existingProduct = $this->object; 2431 2432 $this->checkProduct(); 2433 2434 if (!empty($this->errors)) { 2435 $this->display = 'edit'; 2436 2437 return false; 2438 } 2439 2440 $id = (int) Tools::getValue('id_'.$this->table); 2441 /* Update an existing product */ 2442 if (isset($id) && !empty($id)) { 2443 /** @var Product $object */ 2444 $object = new $this->className((int) $id); 2445 $this->object = $object; 2446 2447 if (Validate::isLoadedObject($object)) { 2448 $this->_removeTaxFromEcotax(); 2449 $productTypeBefore = $object->getType(); 2450 $this->copyFromPost($object, $this->table); 2451 $object->indexed = 0; 2452 2453 if (Shop::isFeatureActive() && Shop::getContext() != Shop::CONTEXT_SHOP) { 2454 $object->setFieldsToUpdate((array) Tools::getValue('multishop_check', [])); 2455 } 2456 2457 // Duplicate combinations if not associated to shop 2458 if ($this->context->shop->getContext() == Shop::CONTEXT_SHOP && !$object->isAssociatedToShop()) { 2459 $isAssociatedToShop = false; 2460 $combinations = Product::getProductAttributesIds($object->id); 2461 if ($combinations) { 2462 foreach ($combinations as $idCombination) { 2463 $combination = new Combination((int) $idCombination['id_product_attribute']); 2464 $defaultCombination = new Combination((int) $idCombination['id_product_attribute'], null, (int) $this->object->id_shop_default); 2465 2466 $def = ObjectModel::getDefinition($defaultCombination); 2467 foreach ($def['fields'] as $fieldName => $row) { 2468 $combination->$fieldName = ObjectModel::formatValue($defaultCombination->$fieldName, $def['fields'][$fieldName]['type']); 2469 } 2470 2471 $combination->save(); 2472 } 2473 } 2474 } else { 2475 $isAssociatedToShop = true; 2476 } 2477 2478 if ($object->update()) { 2479 // If the product doesn't exist in the current shop but exists in another shop 2480 if (Shop::getContext() == Shop::CONTEXT_SHOP && !$existingProduct->isAssociatedToShop($this->context->shop->id)) { 2481 $outOfStock = StockAvailable::outOfStock($existingProduct->id, $existingProduct->id_shop_default); 2482 $dependsOnStock = StockAvailable::dependsOnStock($existingProduct->id, $existingProduct->id_shop_default); 2483 StockAvailable::setProductOutOfStock((int) $this->object->id, $outOfStock, $this->context->shop->id); 2484 StockAvailable::setProductDependsOnStock((int) $this->object->id, $dependsOnStock, $this->context->shop->id); 2485 } 2486 2487 Logger::addLog(sprintf($this->l('%s modification', 'AdminTab', false, false), $this->className), 1, null, $this->className, (int) $this->object->id, true, (int) $this->context->employee->id); 2488 if (in_array($this->context->shop->getContext(), [Shop::CONTEXT_SHOP, Shop::CONTEXT_ALL])) { 2489 if ($this->isTabSubmitted('Shipping')) { 2490 $this->addCarriers(); 2491 } 2492 if ($this->isTabSubmitted('Associations')) { 2493 $this->updateAccessories($object); 2494 } 2495 if ($this->isTabSubmitted('Suppliers')) { 2496 $this->processSuppliers(); 2497 } 2498 if ($this->isTabSubmitted('Features')) { 2499 $this->processFeatures(); 2500 } 2501 if ($this->isTabSubmitted('Combinations')) { 2502 $this->processProductAttribute(); 2503 } 2504 if ($this->isTabSubmitted('Prices')) { 2505 $this->processPriceAddition(); 2506 $this->processSpecificPricePriorities(); 2507 } 2508 if ($this->isTabSubmitted('Customization')) { 2509 $this->processCustomizationConfiguration(); 2510 } 2511 if ($this->isTabSubmitted('Attachments')) { 2512 $this->processAttachments(); 2513 } 2514 if ($this->isTabSubmitted('Images')) { 2515 $this->processImageLegends(); 2516 } 2517 2518 $this->updatePackItems($object); 2519 // Disallow avanced stock management if the product become a pack 2520 if ($productTypeBefore == Product::PTYPE_SIMPLE && $object->getType() == Product::PTYPE_PACK) { 2521 StockAvailable::setProductDependsOnStock((int) $object->id, false); 2522 } 2523 $this->updateDownloadProduct($object); 2524 $this->updateTags(Language::getLanguages(false), $object); 2525 2526 if ($this->isProductFieldUpdated('category_box') && !$object->updateCategories(Tools::getValue('categoryBox'))) { 2527 $this->errors[] = Tools::displayError('An error occurred while linking the object.').' <b>'.$this->table.'</b> '.Tools::displayError('To categories'); 2528 } 2529 } 2530 2531 if ($this->isTabSubmitted('Warehouses')) { 2532 $this->processWarehouses(); 2533 } 2534 if (empty($this->errors)) { 2535 if (in_array($object->visibility, ['both', 'search']) && Configuration::get('PS_SEARCH_INDEXATION')) { 2536 Search::indexation(false, $object->id); 2537 } 2538 2539 // Save and preview 2540 if (Tools::isSubmit('submitAddProductAndPreview')) { 2541 $this->redirect_after = $this->getPreviewUrl($object); 2542 } else { 2543 $page = (int) Tools::getValue('page'); 2544 // Save and stay on same form 2545 if ($this->display == 'edit') { 2546 $this->confirmations[] = $this->l('Update successful'); 2547 $this->redirect_after = static::$currentIndex.'&id_product='.(int) $this->object->id 2548 .(Tools::getIsset('id_category') ? '&id_category='.(int) Tools::getValue('id_category') : '') 2549 .'&updateproduct&conf=4&key_tab='.Tools::safeOutput(Tools::getValue('key_tab')).($page > 1 ? '&page='.(int) $page : '').'&token='.$this->token; 2550 } else { 2551 // Default behavior (save and back) 2552 $this->redirect_after = static::$currentIndex.(Tools::getIsset('id_category') ? '&id_category='.(int) Tools::getValue('id_category') : '').'&conf=4'.($page > 1 ? '&submitFilterproduct='.(int) $page : '').'&token='.$this->token; 2553 } 2554 } 2555 } // if errors : stay on edit page 2556 else { 2557 $this->display = 'edit'; 2558 } 2559 } else { 2560 if (!$isAssociatedToShop && isset($combinations) && $combinations) { 2561 foreach ($combinations as $idCombination) { 2562 $combination = new Combination((int) $idCombination['id_product_attribute']); 2563 $combination->delete(); 2564 } 2565 } 2566 $this->errors[] = Tools::displayError('An error occurred while updating an object.').' <b>'.$this->table.'</b> ('.Db::getInstance()->getMsgError().')'; 2567 } 2568 } else { 2569 $this->errors[] = Tools::displayError('An error occurred while updating an object.').' <b>'.$this->table.'</b> ('.Tools::displayError('The object cannot be loaded. ').')'; 2570 } 2571 2572 return $object; 2573 } 2574 } 2575 2576 /** 2577 * Post treatment for suppliers 2578 * 2579 * @since 1.0.0 2580 */ 2581 public function processSuppliers() 2582 { 2583 if ((int) Tools::getValue('supplier_loaded') === 1 && Validate::isLoadedObject($product = new Product((int) Tools::getValue('id_product')))) { 2584 // Get all id_product_attribute 2585 $attributes = $product->getAttributesResume($this->context->language->id); 2586 if (empty($attributes)) { 2587 $attributes[] = [ 2588 'id_product_attribute' => 0, 2589 'attribute_designation' => '', 2590 ]; 2591 } 2592 2593 // Get all available suppliers 2594 $suppliers = Supplier::getSuppliers(); 2595 2596 // Get already associated suppliers 2597 $associatedSuppliers = ProductSupplier::getSupplierCollection($product->id); 2598 2599 $suppliersToAssociate = []; 2600 $newDefaultSupplier = 0; 2601 2602 if (Tools::isSubmit('default_supplier')) { 2603 $newDefaultSupplier = (int) Tools::getValue('default_supplier'); 2604 } 2605 2606 // Get new associations 2607 foreach ($suppliers as $supplier) { 2608 if (Tools::isSubmit('check_supplier_'.$supplier['id_supplier'])) { 2609 $suppliersToAssociate[] = $supplier['id_supplier']; 2610 } 2611 } 2612 2613 // Delete already associated suppliers if needed 2614 foreach ($associatedSuppliers as $key => $associatedSupplier) { 2615 /** @var ProductSupplier $associatedSupplier */ 2616 if (!in_array($associatedSupplier->id_supplier, $suppliersToAssociate)) { 2617 $associatedSupplier->delete(); 2618 unset($associatedSuppliers[$key]); 2619 } 2620 } 2621 2622 // Associate suppliers 2623 foreach ($suppliersToAssociate as $id) { 2624 $toAdd = true; 2625 foreach ($associatedSuppliers as $as) { 2626 /** @var ProductSupplier $as */ 2627 if ($id == $as->id_supplier) { 2628 $toAdd = false; 2629 } 2630 } 2631 2632 if ($toAdd) { 2633 $productSupplier = new ProductSupplier(); 2634 $productSupplier->id_product = $product->id; 2635 $productSupplier->id_product_attribute = 0; 2636 $productSupplier->id_supplier = $id; 2637 if ($this->context->currency->id) { 2638 $productSupplier->id_currency = (int) $this->context->currency->id; 2639 } else { 2640 $productSupplier->id_currency = (int) Configuration::get('PS_CURRENCY_DEFAULT'); 2641 } 2642 $productSupplier->save(); 2643 2644 $associatedSuppliers[] = $productSupplier; 2645 foreach ($attributes as $attribute) { 2646 if ((int) $attribute['id_product_attribute'] > 0) { 2647 $productSupplier = new ProductSupplier(); 2648 $productSupplier->id_product = $product->id; 2649 $productSupplier->id_product_attribute = (int) $attribute['id_product_attribute']; 2650 $productSupplier->id_supplier = $id; 2651 $productSupplier->save(); 2652 } 2653 } 2654 } 2655 } 2656 2657 // Manage references and prices 2658 foreach ($attributes as $attribute) { 2659 foreach ($associatedSuppliers as $supplier) { 2660 /** @var ProductSupplier $supplier */ 2661 if (Tools::isSubmit('supplier_reference_'.$product->id.'_'.$attribute['id_product_attribute'].'_'.$supplier->id_supplier) || 2662 (Tools::isSubmit('product_price_'.$product->id.'_'.$attribute['id_product_attribute'].'_'.$supplier->id_supplier) && 2663 Tools::isSubmit('product_price_currency_'.$product->id.'_'.$attribute['id_product_attribute'].'_'.$supplier->id_supplier)) 2664 ) { 2665 $reference = pSQL( 2666 Tools::getValue( 2667 'supplier_reference_'.$product->id.'_'.$attribute['id_product_attribute'].'_'.$supplier->id_supplier, 2668 '' 2669 ) 2670 ); 2671 2672 $price = priceval( 2673 Tools::getValue( 2674 'product_price_'.$product->id.'_'.$attribute['id_product_attribute'].'_'.$supplier->id_supplier, 2675 0 2676 ) 2677 ); 2678 2679 $idCurrency = (int) Tools::getValue( 2680 'product_price_currency_'.$product->id.'_'.$attribute['id_product_attribute'].'_'.$supplier->id_supplier, 2681 0 2682 ); 2683 2684 if ($idCurrency <= 0 || (!($result = Currency::getCurrency($idCurrency)) || empty($result))) { 2685 $this->errors[] = Tools::displayError('The selected currency is not valid'); 2686 } 2687 2688 // Save product-supplier data 2689 $productSupplierId = (int) ProductSupplier::getIdByProductAndSupplier($product->id, $attribute['id_product_attribute'], $supplier->id_supplier); 2690 2691 if (!$productSupplierId) { 2692 $product->addSupplierReference($supplier->id_supplier, (int) $attribute['id_product_attribute'], $reference, (float) $price, (int) $idCurrency); 2693 if ($product->id_supplier == $supplier->id_supplier) { 2694 if ((int) $attribute['id_product_attribute'] > 0) { 2695 $data = [ 2696 'supplier_reference' => pSQL($reference), 2697 'wholesale_price' => Tools::convertPrice($price, $idCurrency), 2698 ]; 2699 $where = ' 2700 a.id_product = '.(int) $product->id.' 2701 AND a.id_product_attribute = '.(int) $attribute['id_product_attribute']; 2702 ObjectModel::updateMultishopTable('Combination', $data, $where); 2703 } else { 2704 $product->wholesale_price = Tools::convertPrice($price, $idCurrency); 2705 $product->supplier_reference = pSQL($reference); 2706 $product->update(); 2707 } 2708 } 2709 } else { 2710 $productSupplier = new ProductSupplier($productSupplierId); 2711 $productSupplier->id_currency = (int) $idCurrency; 2712 $productSupplier->product_supplier_price_te = $price; 2713 $productSupplier->product_supplier_reference = pSQL($reference); 2714 $productSupplier->update(); 2715 } 2716 } elseif (Tools::isSubmit('supplier_reference_'.$product->id.'_'.$attribute['id_product_attribute'].'_'.$supplier->id_supplier)) { 2717 //int attribute with default values if possible 2718 if ((int) $attribute['id_product_attribute'] > 0) { 2719 $productSupplier = new ProductSupplier(); 2720 $productSupplier->id_product = $product->id; 2721 $productSupplier->id_product_attribute = (int) $attribute['id_product_attribute']; 2722 $productSupplier->id_supplier = $supplier->id_supplier; 2723 $productSupplier->save(); 2724 } 2725 } 2726 } 2727 } 2728 // Manage defaut supplier for product 2729 if ($newDefaultSupplier != $product->id_supplier) { 2730 $this->object->id_supplier = $newDefaultSupplier; 2731 $this->object->update(); 2732 } 2733 } 2734 } 2735 2736 /** 2737 * Process features 2738 * 2739 * @since 1.0.0 2740 */ 2741 public function processFeatures() 2742 { 2743 if (!Feature::isFeatureActive()) { 2744 return; 2745 } 2746 2747 if (Validate::isLoadedObject($product = new Product((int) Tools::getValue('id_product')))) { 2748 // delete all objects 2749 $product->deleteFeatures(); 2750 2751 // add new objects 2752 $languages = Language::getLanguages(false); 2753 foreach ($_POST as $key => $val) { 2754 if (preg_match('/^feature_([0-9]+)_value/i', $key, $match)) { 2755 if ($val) { 2756 $product->addFeaturesToDB($match[1], $val); 2757 } else { 2758 if ($defaultValue = $this->checkFeatures($languages, $match[1])) { 2759 $idValue = $product->addFeaturesToDB($match[1], 0, 1); 2760 foreach ($languages as $language) { 2761 if ($cust = Tools::getValue('custom_'.$match[1].'_'.(int) $language['id_lang'])) { 2762 $product->addFeaturesCustomToDB($idValue, (int) $language['id_lang'], $cust); 2763 } else { 2764 $product->addFeaturesCustomToDB($idValue, (int) $language['id_lang'], $defaultValue); 2765 } 2766 } 2767 } 2768 } 2769 } 2770 } 2771 } else { 2772 $this->errors[] = Tools::displayError('A product must be created before adding features.'); 2773 } 2774 } 2775 2776 /** 2777 * Check features 2778 * 2779 * @param $languages 2780 * @param $featureId 2781 * 2782 * @return int|mixed 2783 * 2784 * @since 1.0.0 2785 */ 2786 protected function checkFeatures($languages, $featureId) 2787 { 2788 $rules = call_user_func(['FeatureValue', 'getValidationRules'], 'FeatureValue'); 2789 $feature = Feature::getFeature((int) Configuration::get('PS_LANG_DEFAULT'), $featureId); 2790 2791 foreach ($languages as $language) { 2792 if ($val = Tools::getValue('custom_'.$featureId.'_'.$language['id_lang'])) { 2793 $currentLanguage = new Language($language['id_lang']); 2794 if (mb_strlen($val) > $rules['sizeLang']['value']) { 2795 $this->errors[] = sprintf( 2796 Tools::displayError('The name for feature %1$s is too long in %2$s.'), 2797 ' <b>'.$feature['name'].'</b>', 2798 $currentLanguage->name 2799 ); 2800 } elseif (!call_user_func(['Validate', $rules['validateLang']['value']], $val)) { 2801 $this->errors[] = sprintf( 2802 Tools::displayError('A valid name required for feature. %1$s in %2$s.'), 2803 ' <b>'.$feature['name'].'</b>', 2804 $currentLanguage->name 2805 ); 2806 } 2807 if (count($this->errors)) { 2808 return 0; 2809 } 2810 // Getting default language 2811 if ($language['id_lang'] == Configuration::get('PS_LANG_DEFAULT')) { 2812 return $val; 2813 } 2814 } 2815 } 2816 2817 return 0; 2818 } 2819 2820 /** 2821 * Process product attribute 2822 * 2823 * @since 1.0.0 2824 */ 2825 public function processProductAttribute() 2826 { 2827 // Don't process if the combination fields have not been submitted 2828 if (!Combination::isFeatureActive() || !Tools::getValue('attribute_combination_list')) { 2829 return; 2830 } 2831 2832 if (Validate::isLoadedObject($product = $this->object)) { 2833 if ($this->isProductFieldUpdated('attribute_price') && (!Tools::getIsset('attribute_price') || Tools::getIsset('attribute_price') == null)) { 2834 $this->errors[] = Tools::displayError('The price attribute is required.'); 2835 } 2836 if (!Tools::getIsset('attribute_combination_list') || Tools::isEmpty(Tools::getValue('attribute_combination_list'))) { 2837 $this->errors[] = Tools::displayError('You must add at least one attribute.'); 2838 } 2839 2840 $arrayChecks = [ 2841 'reference' => 'isReference', 2842 'supplier_reference' => 'isReference', 2843 'location' => 'isReference', 2844 'ean13' => 'isEan13', 2845 'upc' => 'isUpc', 2846 'wholesale_price' => 'isPrice', 2847 'price' => 'isPrice', 2848 'ecotax' => 'isPrice', 2849 'quantity' => 'isInt', 2850 'weight' => 'isUnsignedFloat', 2851 'unit_price_impact' => 'isPrice', 2852 'default_on' => 'isBool', 2853 'minimal_quantity' => 'isUnsignedInt', 2854 'available_date' => 'isDateFormat', 2855 ]; 2856 foreach ($arrayChecks as $property => $check) { 2857 $key = 'attribute_'.$property; 2858 $value = Tools::getValue($key); 2859 2860 if ($check === 'isPrice') { 2861 $value = priceval($value); 2862 } 2863 if ($value !== false 2864 && ! call_user_func(['Validate', $check], $value)) { 2865 $this->errors[] = sprintf(Tools::displayError('Field %s is not valid'), $property); 2866 } 2867 } 2868 2869 if (!count($this->errors)) { 2870 if (!isset($_POST['attribute_wholesale_price'])) { 2871 $_POST['attribute_wholesale_price'] = 0; 2872 } 2873 if (!isset($_POST['attribute_price_impact'])) { 2874 $_POST['attribute_price_impact'] = 0; 2875 } 2876 if (!isset($_POST['attribute_weight_impact'])) { 2877 $_POST['attribute_weight_impact'] = 0; 2878 } 2879 if (!isset($_POST['attribute_ecotax'])) { 2880 $_POST['attribute_ecotax'] = 0; 2881 } 2882 if (Tools::getValue('attribute_default')) { 2883 $product->deleteDefaultAttributes(); 2884 } 2885 2886 // Change existing one 2887 if (($idProductAttribute = (int) Tools::getValue('id_product_attribute')) || ($idProductAttribute = $product->productAttributeExists(Tools::getValue('attribute_combination_list'), false, null, true, true))) { 2888 if ($this->tabAccess['edit'] === '1') { 2889 if ($this->isProductFieldUpdated('available_date_attribute') && (Tools::getValue('available_date_attribute') != '' && !Validate::isDateFormat(Tools::getValue('available_date_attribute')))) { 2890 $this->errors[] = Tools::displayError('Invalid date format.'); 2891 } else { 2892 $product->updateAttribute( 2893 (int) $idProductAttribute, 2894 $this->isProductFieldUpdated('attribute_wholesale_price') ? priceval(Tools::getValue('attribute_wholesale_price')) : null, 2895 $this->isProductFieldUpdated('attribute_price_impact') ? priceval(Tools::getValue('attribute_price')) * Tools::getValue('attribute_price_impact') : null, 2896 $this->isProductFieldUpdated('attribute_weight_impact') ? Tools::getValue('attribute_weight') * Tools::getValue('attribute_weight_impact') : null, 2897 $this->isProductFieldUpdated('attribute_unit_impact') ? Tools::getValue('attribute_unity') * Tools::getValue('attribute_unit_impact') : null, 2898 $this->isProductFieldUpdated('attribute_ecotax') ? priceval(Tools::getValue('attribute_ecotax')) : null, 2899 Tools::getValue('id_image_attr'), 2900 Tools::getValue('attribute_reference'), 2901 Tools::getValue('attribute_ean13'), 2902 $this->isProductFieldUpdated('attribute_default') ? Tools::getValue('attribute_default') : null, 2903 Tools::getValue('attribute_location'), 2904 Tools::getValue('attribute_upc'), 2905 $this->isProductFieldUpdated('attribute_minimal_quantity') ? Tools::getValue('attribute_minimal_quantity') : null, 2906 $this->isProductFieldUpdated('available_date_attribute') ? Tools::getValue('available_date_attribute') : null, 2907 false 2908 ); 2909 StockAvailable::setProductDependsOnStock((int) $product->id, $product->depends_on_stock, null, (int) $idProductAttribute); 2910 StockAvailable::setProductOutOfStock((int) $product->id, $product->out_of_stock, null, (int) $idProductAttribute); 2911 } 2912 } else { 2913 $this->errors[] = Tools::displayError('You do not have permission to add this.'); 2914 } 2915 } // Add new 2916 else { 2917 if ($this->tabAccess['add'] === '1') { 2918 /** @var Product $product */ 2919 if ($product->productAttributeExists(Tools::getValue('attribute_combination_list'))) { 2920 $this->errors[] = Tools::displayError('This combination already exists.'); 2921 } else { 2922 $idProductAttribute = $product->addCombinationEntity( 2923 priceval(Tools::getValue('attribute_wholesale_price')), 2924 priceval(Tools::getValue('attribute_price')) * Tools::getValue('attribute_price_impact'), 2925 Tools::getValue('attribute_weight') * Tools::getValue('attribute_weight_impact'), 2926 Tools::getValue('attribute_unity') * Tools::getValue('attribute_unit_impact'), 2927 priceval(Tools::getValue('attribute_ecotax')), 2928 0, 2929 Tools::getValue('id_image_attr'), 2930 Tools::getValue('attribute_reference'), 2931 null, 2932 Tools::getValue('attribute_ean13'), 2933 Tools::getValue('attribute_default'), 2934 Tools::getValue('attribute_location'), 2935 Tools::getValue('attribute_upc'), 2936 Tools::getValue('attribute_minimal_quantity'), 2937 [], 2938 Tools::getValue('available_date_attribute') 2939 ); 2940 StockAvailable::setProductDependsOnStock((int) $product->id, $product->depends_on_stock, null, (int) $idProductAttribute); 2941 StockAvailable::setProductOutOfStock((int) $product->id, $product->out_of_stock, null, (int) $idProductAttribute); 2942 } 2943 } else { 2944 $this->errors[] = Tools::displayError('You do not have permission to').'<hr>'.Tools::displayError('edit here.'); 2945 } 2946 } 2947 if (!count($this->errors)) { 2948 $combination = new Combination((int) $idProductAttribute); 2949 $combination->setAttributes(Tools::getValue('attribute_combination_list')); 2950 2951 // images could be deleted before 2952 $idImages = Tools::getValue('id_image_attr'); 2953 if (!empty($idImages)) { 2954 $combination->setImages($idImages); 2955 } 2956 2957 $product->checkDefaultAttributes(); 2958 if (Tools::getValue('attribute_default')) { 2959 Product::updateDefaultAttribute((int) $product->id); 2960 if (isset($idProductAttribute)) { 2961 $product->cache_default_attribute = (int) $idProductAttribute; 2962 } 2963 2964 if ($availableDate = Tools::getValue('available_date_attribute')) { 2965 $product->setAvailableDate($availableDate); 2966 } else { 2967 $product->setAvailableDate(); 2968 } 2969 } 2970 } 2971 } 2972 } 2973 } 2974 2975 /** 2976 * Process price addition 2977 * 2978 * @since 1.0.0 2979 */ 2980 public function processPriceAddition() 2981 { 2982 if (!Tools::getIsset('submitPriceAddition')) { 2983 return; 2984 } 2985 2986 $idProduct = Tools::getValue('id_product'); 2987 $idProductAttribute = Tools::getValue('sp_id_product_attribute'); 2988 $idShop = Tools::getValue('sp_id_shop'); 2989 $idCurrency = Tools::getValue('sp_id_currency'); 2990 $idCountry = Tools::getValue('sp_id_country'); 2991 $idGroup = Tools::getValue('sp_id_group'); 2992 $idCustomer = Tools::getValue('sp_id_customer'); 2993 $price = Tools::getValue('leave_bprice') ? '-1' : priceval(Tools::getValue('sp_price')); 2994 $fromQuantity = Tools::getValue('sp_from_quantity'); 2995 $reduction = priceval(Tools::getValue('sp_reduction')); 2996 $reductionTax = priceval(Tools::getValue('sp_reduction_tax')); 2997 $reductionType = !$reduction ? 'amount' : Tools::getValue('sp_reduction_type'); 2998 $reductionType = $reductionType == '-' ? 'amount' : $reductionType; 2999 $from = Tools::getValue('sp_from'); 3000 if (!$from) { 3001 $from = '0000-00-00 00:00:00'; 3002 } 3003 $to = Tools::getValue('sp_to'); 3004 if (!$to) { 3005 $to = '0000-00-00 00:00:00'; 3006 } 3007 3008 if (($price == '-1') && ((float) $reduction == '0')) { 3009 $this->errors[] = Tools::displayError('No reduction value has been submitted'); 3010 } elseif ($to != '0000-00-00 00:00:00' && strtotime($to) < strtotime($from)) { 3011 $this->errors[] = Tools::displayError('Invalid date range'); 3012 } elseif ($reductionType == 'percentage' && ((float) $reduction <= 0 || (float) $reduction > 100)) { 3013 $this->errors[] = Tools::displayError('Submitted reduction value (0-100) is out-of-range'); 3014 } elseif ($this->_validateSpecificPrice($idShop, $idCurrency, $idCountry, $idGroup, $idCustomer, $price, $fromQuantity, $reduction, $reductionType, $from, $to, $idProductAttribute)) { 3015 $specificPrice = new SpecificPrice(); 3016 $specificPrice->id_product = (int) $idProduct; 3017 $specificPrice->id_product_attribute = (int) $idProductAttribute; 3018 $specificPrice->id_shop = (int) $idShop; 3019 $specificPrice->id_currency = (int) ($idCurrency); 3020 $specificPrice->id_country = (int) ($idCountry); 3021 $specificPrice->id_group = (int) ($idGroup); 3022 $specificPrice->id_customer = (int) $idCustomer; 3023 $specificPrice->price = (float) ($price); 3024 $specificPrice->from_quantity = (int) ($fromQuantity); 3025 $specificPrice->reduction = (float) ($reductionType == 'percentage' ? $reduction / 100 : $reduction); 3026 $specificPrice->reduction_tax = $reductionTax; 3027 $specificPrice->reduction_type = $reductionType; 3028 $specificPrice->from = $from; 3029 $specificPrice->to = $to; 3030 if (!$specificPrice->add()) { 3031 $this->errors[] = Tools::displayError('An error occurred while updating the specific price.'); 3032 } 3033 } 3034 } 3035 3036 /** 3037 * Process specific price priorities 3038 * 3039 * @since 1.0.0 3040 */ 3041 public function processSpecificPricePriorities() 3042 { 3043 if (!($obj = $this->loadObject())) { 3044 return; 3045 } 3046 if (!$priorities = Tools::getValue('specificPricePriority')) { 3047 $this->errors[] = Tools::displayError('Please specify priorities.'); 3048 } elseif (Tools::isSubmit('specificPricePriorityToAll')) { 3049 if (!SpecificPrice::setPriorities($priorities)) { 3050 $this->errors[] = Tools::displayError('An error occurred while updating priorities.'); 3051 } else { 3052 $this->confirmations[] = $this->l('The price rule has successfully updated'); 3053 } 3054 } elseif (!SpecificPrice::setSpecificPriority((int) $obj->id, $priorities)) { 3055 $this->errors[] = Tools::displayError('An error occurred while setting priorities.'); 3056 } 3057 } 3058 3059 /** 3060 * Process customization configuration 3061 * 3062 * @since 1.0.0 3063 */ 3064 public function processCustomizationConfiguration() 3065 { 3066 $product = $this->object; 3067 // Get the number of existing customization fields ($product->text_fields is the updated value, not the existing value) 3068 $currentCustomization = $product->getCustomizationFieldIds(); 3069 $filesCount = 0; 3070 $textCount = 0; 3071 if (is_array($currentCustomization)) { 3072 foreach ($currentCustomization as $field) { 3073 if ($field['type'] == 1) { 3074 $textCount++; 3075 } else { 3076 $filesCount++; 3077 } 3078 } 3079 } 3080 3081 if (!$product->createLabels((int) $product->uploadable_files - $filesCount, (int) $product->text_fields - $textCount)) { 3082 $this->errors[] = Tools::displayError('An error occurred while creating customization fields.'); 3083 } 3084 if (!count($this->errors) && !$product->updateLabels()) { 3085 $this->errors[] = Tools::displayError('An error occurred while updating customization fields.'); 3086 } 3087 $product->customizable = ($product->uploadable_files > 0 || $product->text_fields > 0) ? 1 : 0; 3088 if (($product->uploadable_files != $filesCount || $product->text_fields != $textCount) && !count($this->errors) && !$product->update()) { 3089 $this->errors[] = Tools::displayError('An error occurred while updating the custom configuration.'); 3090 } 3091 } 3092 3093 /** 3094 * Attach an existing attachment to the product 3095 * 3096 * @return void 3097 * 3098 * @since 1.0.0 3099 */ 3100 public function processAttachments() 3101 { 3102 if ($id = (int) Tools::getValue($this->identifier)) { 3103 $attachments = trim(Tools::getValue('arrayAttachments'), ','); 3104 $attachments = explode(',', $attachments); 3105 if (!Attachment::attachToProduct($id, $attachments)) { 3106 $this->errors[] = Tools::displayError('An error occurred while saving product attachments.'); 3107 } 3108 } 3109 } 3110 3111 /** 3112 * Process image legends 3113 * 3114 * @since 1.0.0 3115 */ 3116 public function processImageLegends() 3117 { 3118 if (Tools::getValue('key_tab') == 'Images' && Tools::getValue('submitAddproductAndStay') == 'update_legends' && Validate::isLoadedObject($product = new Product((int) Tools::getValue('id_product')))) { 3119 $idImage = (int) Tools::getValue('id_caption'); 3120 $idImages = []; 3121 if ($idImage) { 3122 // Caption is for one image only. 3123 $idImages[] = $idImage; 3124 } else { 3125 // Same caption for all images. 3126 $images = Image::getImages(null, $product->id); 3127 foreach ($images as $image) { 3128 $idImages[] = $image['id_image']; 3129 } 3130 } 3131 $languageIds = Language::getIDs(false); 3132 foreach ($_POST as $key => $val) { 3133 if (preg_match('/^legend_([0-9]+)/', $key, $match)) { 3134 foreach ($languageIds as $idLang) { 3135 if ($idLang == $match[1]) { 3136 foreach ($idImages as $idImage) { 3137 // Insert missing entries, update already 3138 // existing ones. As SQL features no 'insert if 3139 // missing, update otherwise', try both. 3140 Db::getInstance(_PS_USE_SQL_SLAVE_)->insert( 3141 'image_lang', 3142 [ 3143 'id_image' => $idImage, 3144 'id_lang' => $idLang, 3145 'legend' => $val, 3146 ], 3147 false, 3148 true, 3149 Db::INSERT_IGNORE 3150 ); 3151 Db::getInstance(_PS_USE_SQL_SLAVE_)->update( 3152 'image_lang', 3153 [ 3154 'legend' => $val, 3155 ], 3156 '`id_image` = '.$idImage 3157 .' AND `id_lang` = '.$idLang 3158 ); 3159 } 3160 } 3161 } 3162 } 3163 } 3164 } 3165 } 3166 3167 /** 3168 * Post treatment for warehouses 3169 * 3170 * @since 1.0.0 3171 */ 3172 public function processWarehouses() 3173 { 3174 if ((int) Tools::getValue('warehouse_loaded') === 1 && Validate::isLoadedObject($product = new Product((int) $idProduct = Tools::getValue('id_product')))) { 3175 // Get all id_product_attribute 3176 $attributes = $product->getAttributesResume($this->context->language->id); 3177 if (empty($attributes)) { 3178 $attributes[] = [ 3179 'id_product_attribute' => 0, 3180 'attribute_designation' => '', 3181 ]; 3182 } 3183 3184 // Get all available warehouses 3185 $warehouses = Warehouse::getWarehouses(true); 3186 3187 // Get already associated warehouses 3188 $associatedWarehousesCollection = WarehouseProductLocation::getCollection($product->id); 3189 3190 $elementsToManage = []; 3191 3192 // get form inforamtion 3193 foreach ($attributes as $attribute) { 3194 foreach ($warehouses as $warehouse) { 3195 $key = $warehouse['id_warehouse'].'_'.$product->id.'_'.$attribute['id_product_attribute']; 3196 3197 // get elements to manage 3198 if (Tools::isSubmit('check_warehouse_'.$key)) { 3199 $location = Tools::getValue('location_warehouse_'.$key, ''); 3200 $elementsToManage[$key] = $location; 3201 } 3202 } 3203 } 3204 3205 // Delete entry if necessary 3206 foreach ($associatedWarehousesCollection as $awc) { 3207 /** @var WarehouseProductLocation $awc */ 3208 if (!array_key_exists($awc->id_warehouse.'_'.$awc->id_product.'_'.$awc->id_product_attribute, $elementsToManage)) { 3209 $awc->delete(); 3210 } 3211 } 3212 3213 // Manage locations 3214 foreach ($elementsToManage as $key => $location) { 3215 $params = explode('_', $key); 3216 3217 $wplId = (int) WarehouseProductLocation::getIdByProductAndWarehouse((int) $params[1], (int) $params[2], (int) $params[0]); 3218 3219 if (empty($wplId)) { 3220 //create new record 3221 $warehouseLocationEntity = new WarehouseProductLocation(); 3222 $warehouseLocationEntity->id_product = (int) $params[1]; 3223 $warehouseLocationEntity->id_product_attribute = (int) $params[2]; 3224 $warehouseLocationEntity->id_warehouse = (int) $params[0]; 3225 $warehouseLocationEntity->location = pSQL($location); 3226 $warehouseLocationEntity->save(); 3227 } else { 3228 $warehouseLocationEntity = new WarehouseProductLocation((int) $wplId); 3229 3230 $location = pSQL($location); 3231 3232 if ($location != $warehouseLocationEntity->location) { 3233 $warehouseLocationEntity->location = pSQL($location); 3234 $warehouseLocationEntity->update(); 3235 } 3236 } 3237 } 3238 StockAvailable::synchronize((int) $idProduct); 3239 } 3240 } 3241 3242 /** 3243 * Initialize content 3244 * 3245 * @param null $token 3246 * 3247 * @since 1.0.0 3248 */ 3249 public function initContent($token = null) 3250 { 3251 if ($this->display == 'edit' || $this->display == 'add') { 3252 $this->fields_form = []; 3253 3254 // Check if Module 3255 if (substr($this->tab_display, 0, 6) == 'Module') { 3256 $this->tab_display_module = strtolower(substr($this->tab_display, 6, mb_strlen($this->tab_display) - 6)); 3257 $this->tab_display = 'Modules'; 3258 } 3259 if (method_exists($this, 'initForm'.$this->tab_display)) { 3260 $this->tpl_form = strtolower($this->tab_display).'.tpl'; 3261 } 3262 3263 if ($this->ajax) { 3264 $this->content_only = true; 3265 } else { 3266 $productTabs = []; 3267 3268 // tab_display defines which tab to display first 3269 if (!method_exists($this, 'initForm'.$this->tab_display)) { 3270 $this->tab_display = $this->default_tab; 3271 } 3272 3273 $advancedStockManagementActive = Configuration::get('PS_ADVANCED_STOCK_MANAGEMENT'); 3274 foreach ($this->available_tabs as $productTab => $value) { 3275 // if it's the warehouses tab and advanced stock management is disabled, continue 3276 if ($advancedStockManagementActive == 0 && $productTab == 'Warehouses') { 3277 continue; 3278 } 3279 3280 $productTabs[$productTab] = [ 3281 'id' => $productTab, 3282 'selected' => (strtolower($productTab) == strtolower($this->tab_display) || (isset($this->tab_display_module) && 'module'.$this->tab_display_module == mb_strtolower($productTab))), 3283 'name' => $this->available_tabs_lang[$productTab], 3284 'href' => $this->context->link->getAdminLink('AdminProducts').'&id_product='.(int) Tools::getValue('id_product').'&action='.$productTab, 3285 ]; 3286 } 3287 $this->tpl_form_vars['product_tabs'] = $productTabs; 3288 } 3289 } else { 3290 if ($idCategory = (int) $this->id_current_category) { 3291 static::$currentIndex .= '&id_category='.(int) $this->id_current_category; 3292 } 3293 3294 // If products from all categories are displayed, we don't want to use sorting by position 3295 if (!$idCategory) { 3296 $this->_defaultOrderBy = $this->identifier; 3297 if ($this->context->cookie->{$this->table.'Orderby'} == 'position') { 3298 unset($this->context->cookie->{$this->table.'Orderby'}); 3299 unset($this->context->cookie->{$this->table.'Orderway'}); 3300 } 3301 } 3302 if (!$idCategory) { 3303 $idCategory = Configuration::get('PS_ROOT_CATEGORY'); 3304 } 3305 $this->tpl_list_vars['is_category_filter'] = (bool) $this->id_current_category; 3306 3307 // Generate category selection tree 3308 $tree = new HelperTreeCategories('categories-tree', $this->l('Filter by category')); 3309 $tree->setAttribute('is_category_filter', (bool) $this->id_current_category) 3310 ->setAttribute('base_url', preg_replace('#&id_category=[0-9]*#', '', static::$currentIndex).'&token='.$this->token) 3311 ->setInputName('id-category') 3312 ->setRootCategory(Category::getRootCategory()->id) 3313 ->setSelectedCategories([(int) $idCategory]); 3314 $this->tpl_list_vars['category_tree'] = $tree->render(); 3315 3316 // used to build the new url when changing category 3317 $this->tpl_list_vars['base_url'] = preg_replace('#&id_category=[0-9]*#', '', static::$currentIndex).'&token='.$this->token; 3318 } 3319 // @todo module free 3320 $this->tpl_form_vars['vat_number'] = file_exists(_PS_MODULE_DIR_.'vatnumber/ajax.php'); 3321 3322 parent::initContent(); 3323 } 3324 3325 /** 3326 * Render KPIs 3327 * 3328 * @return mixed 3329 * 3330 * @since 1.0.0 3331 */ 3332 public function renderKpis() 3333 { 3334 $time = time(); 3335 $kpis = []; 3336 3337 /* The data generation is located in AdminStatsControllerCore */ 3338 3339 if (Configuration::get('PS_STOCK_MANAGEMENT')) { 3340 $helper = new HelperKpi(); 3341 $helper->id = 'box-products-stock'; 3342 $helper->icon = 'icon-archive'; 3343 $helper->color = 'color1'; 3344 $helper->title = $this->l('Out of stock items', null, null, false); 3345 if (ConfigurationKPI::get('PERCENT_PRODUCT_OUT_OF_STOCK') !== false) { 3346 $helper->value = ConfigurationKPI::get('PERCENT_PRODUCT_OUT_OF_STOCK'); 3347 } 3348 $helper->source = $this->context->link->getAdminLink('AdminStats').'&ajax=1&action=getKpi&kpi=percent_product_out_of_stock'; 3349 $helper->tooltip = $this->l('X% of your products for sale are out of stock.', null, null, false); 3350 $helper->refresh = (bool) (ConfigurationKPI::get('PERCENT_PRODUCT_OUT_OF_STOCK_EXPIRE') < $time); 3351 $helper->href = $this->context->link->getAdminLink('AdminProducts').'&productFilter_sav!quantity=0&productFilter_active=1&submitFilterproduct=1'; 3352 $kpis[] = $helper->generate(); 3353 } 3354 3355 $helper = new HelperKpi(); 3356 $helper->id = 'box-avg-gross-margin'; 3357 $helper->icon = 'icon-tags'; 3358 $helper->color = 'color2'; 3359 $helper->title = $this->l('Average Gross Margin %', null, null, false); 3360 if (ConfigurationKPI::get('PRODUCT_AVG_GROSS_MARGIN') !== false) { 3361 $helper->value = ConfigurationKPI::get('PRODUCT_AVG_GROSS_MARGIN'); 3362 } 3363 $helper->source = $this->context->link->getAdminLink('AdminStats').'&ajax=1&action=getKpi&kpi=product_avg_gross_margin'; 3364 $helper->tooltip = $this->l('Gross margin expressed in percentage assesses how cost-effectively you sell your goods. Out of $100, you will retain $X to cover profit and expenses.', null, null, false); 3365 $helper->refresh = (bool) (ConfigurationKPI::get('PRODUCT_AVG_GROSS_MARGIN_EXPIRE') < $time); 3366 $kpis[] = $helper->generate(); 3367 3368 $helper = new HelperKpi(); 3369 $helper->id = 'box-8020-sales-catalog'; 3370 $helper->icon = 'icon-beaker'; 3371 $helper->color = 'color3'; 3372 $helper->title = $this->l('Purchased references', null, null, false); 3373 $helper->subtitle = $this->l('30 days', null, null, false); 3374 if (ConfigurationKPI::get('8020_SALES_CATALOG') !== false) { 3375 $helper->value = ConfigurationKPI::get('8020_SALES_CATALOG'); 3376 } 3377 $helper->source = $this->context->link->getAdminLink('AdminStats').'&ajax=1&action=getKpi&kpi=8020_sales_catalog'; 3378 $helper->tooltip = $this->l('X% of your references have been purchased for the past 30 days', null, null, false); 3379 $helper->refresh = (bool) (ConfigurationKPI::get('8020_SALES_CATALOG_EXPIRE') < $time); 3380 if (Module::isInstalled('statsbestproducts')) { 3381 $helper->href = $this->context->link->getAdminLink('AdminStats').'&module=statsbestproducts&datepickerFrom='.date('Y-m-d', strtotime('-30 days')).'&datepickerTo='.date('Y-m-d'); 3382 } 3383 $kpis[] = $helper->generate(); 3384 3385 $helper = new HelperKpi(); 3386 $helper->id = 'box-disabled-products'; 3387 $helper->icon = 'icon-off'; 3388 $helper->color = 'color4'; 3389 $helper->href = $this->context->link->getAdminLink('AdminProducts'); 3390 $helper->title = $this->l('Disabled Products', null, null, false); 3391 if (ConfigurationKPI::get('DISABLED_PRODUCTS') !== false) { 3392 $helper->value = ConfigurationKPI::get('DISABLED_PRODUCTS'); 3393 } 3394 $helper->source = $this->context->link->getAdminLink('AdminStats').'&ajax=1&action=getKpi&kpi=disabled_products'; 3395 $helper->refresh = (bool) (ConfigurationKPI::get('DISABLED_PRODUCTS_EXPIRE') < $time); 3396 $helper->tooltip = $this->l('X% of your products are disabled and not visible to your customers', null, null, false); 3397 $helper->href = $this->context->link->getAdminLink('AdminProducts').'&productFilter_active=0&submitFilterproduct=1'; 3398 $kpis[] = $helper->generate(); 3399 3400 $helper = new HelperKpiRow(); 3401 $helper->kpis = $kpis; 3402 3403 return $helper->generate(); 3404 } 3405 3406 public function renderList() 3407 { 3408 $this->addRowAction('edit'); 3409 $this->addRowAction('preview'); 3410 $this->addRowAction('duplicate'); 3411 $this->addRowAction('delete'); 3412 3413 return parent::renderList(); 3414 } 3415 3416 public function ajaxProcessProductManufacturers() 3417 { 3418 $manufacturers = Manufacturer::getManufacturers(); 3419 $jsonArray = []; 3420 3421 if ($manufacturers) { 3422 foreach ($manufacturers as $manufacturer) { 3423 $tmp = ["optionValue" => $manufacturer['id_manufacturer'], "optionDisplay" => htmlspecialchars(trim($manufacturer['name']))]; 3424 $jsonArray[] = json_encode($tmp); 3425 } 3426 } 3427 3428 die('['.implode(',', $jsonArray).']'); 3429 } 3430 3431 public function initPageHeaderToolbar() 3432 { 3433 if (empty($this->display)) { 3434 $this->page_header_toolbar_btn['new_product'] = [ 3435 'href' => static::$currentIndex.'&addproduct&token='.$this->token, 3436 'desc' => $this->l('Add new product', null, null, false), 3437 'icon' => 'process-icon-new', 3438 ]; 3439 } 3440 if ($this->display == 'edit') { 3441 if (($product = $this->loadObject(true)) && $product->isAssociatedToShop()) { 3442 // adding button for preview this product 3443 if ($url_preview = $this->getPreviewUrl($product)) { 3444 $this->page_header_toolbar_btn['preview'] = [ 3445 'short' => $this->l('Preview', null, null, false), 3446 'href' => $url_preview, 3447 'desc' => $this->l('Preview', null, null, false), 3448 'target' => true, 3449 'class' => 'previewUrl', 3450 ]; 3451 } 3452 3453 $js = (bool) Image::getImages($this->context->language->id, (int) $product->id) ? 3454 'confirm_link(\'\', \''.$this->l('This will copy the images too. If you wish to proceed, click "Yes". If not, click "No".', null, true, false).'\', \''.$this->l('Yes', null, true, false).'\', \''.$this->l('No', null, true, false).'\', \''.$this->context->link->getAdminLink('AdminProducts', true).'&id_product='.(int) $product->id.'&duplicateproduct'.'\', \''.$this->context->link->getAdminLink('AdminProducts', true).'&id_product='.(int) $product->id.'&duplicateproduct&noimage=1'.'\')' 3455 : 3456 'document.location = \''.$this->context->link->getAdminLink('AdminProducts', true).'&id_product='.(int) $product->id.'&duplicateproduct&noimage=1'.'\''; 3457 3458 // adding button for duplicate this product 3459 if ($this->tabAccess['add']) { 3460 $this->page_header_toolbar_btn['duplicate'] = [ 3461 'short' => $this->l('Duplicate', null, null, false), 3462 'desc' => $this->l('Duplicate', null, null, false), 3463 'confirm' => 1, 3464 'js' => $js, 3465 ]; 3466 } 3467 3468 // adding button for preview this product statistics 3469 if (Module::isEnabled('statsdata')) { 3470 $this->page_header_toolbar_btn['stats'] = [ 3471 'short' => $this->l('Statistics', null, null, false), 3472 'href' => $this->context->link->getAdminLink('AdminStats').'&module=statsproduct&id_product='.(int) $product->id, 3473 'desc' => $this->l('Product sales', null, null, false), 3474 ]; 3475 } 3476 3477 // adding button for delete this product 3478 if ($this->tabAccess['delete']) { 3479 $this->page_header_toolbar_btn['delete'] = [ 3480 'short' => $this->l('Delete', null, null, false), 3481 'href' => $this->context->link->getAdminLink('AdminProducts').'&id_product='.(int) $product->id.'&deleteproduct', 3482 'desc' => $this->l('Delete this product', null, null, false), 3483 'confirm' => 1, 3484 'js' => 'if (confirm(\''.$this->l('Delete product?', null, true, false).'\')){return true;}else{event.preventDefault();}', 3485 ]; 3486 } 3487 } 3488 } 3489 parent::initPageHeaderToolbar(); 3490 } 3491 3492 public function initToolbar() 3493 { 3494 parent::initToolbar(); 3495 if ($this->display == 'edit' || $this->display == 'add') { 3496 $this->toolbar_btn['save'] = [ 3497 'short' => 'Save', 3498 'href' => '#', 3499 'desc' => $this->l('Save'), 3500 ]; 3501 3502 $this->toolbar_btn['save-and-stay'] = [ 3503 'short' => 'SaveAndStay', 3504 'href' => '#', 3505 'desc' => $this->l('Save and stay'), 3506 ]; 3507 3508 // adding button for adding a new combination in Combination tab 3509 $this->toolbar_btn['newCombination'] = [ 3510 'short' => 'New combination', 3511 'desc' => $this->l('New combination'), 3512 'class' => 'toolbar-new', 3513 ]; 3514 } elseif ($this->can_import) { 3515 $this->toolbar_btn['import'] = [ 3516 'href' => $this->context->link->getAdminLink('AdminImport', true).'&import_type=products', 3517 'desc' => $this->l('Import'), 3518 ]; 3519 } 3520 3521 $this->context->smarty->assign('toolbar_scroll', 1); 3522 $this->context->smarty->assign('show_toolbar', 1); 3523 $this->context->smarty->assign('toolbar_btn', $this->toolbar_btn); 3524 } 3525 3526 /** 3527 * renderForm contains all necessary initialization needed for all tabs 3528 * 3529 * @return string 3530 * @throws PrestaShopException 3531 */ 3532 public function renderForm() 3533 { 3534 // This nice code (irony) is here to store the product name, because the row after will erase product name in multishop context 3535 $this->product_name = $this->object->name[$this->context->language->id]; 3536 3537 if (!method_exists($this, 'initForm'.$this->tab_display)) { 3538 return ''; 3539 } 3540 3541 $product = $this->object; 3542 3543 // Product for multishop 3544 $this->context->smarty->assign('bullet_common_field', ''); 3545 if (Shop::isFeatureActive() && $this->display == 'edit') { 3546 if (Shop::getContext() != Shop::CONTEXT_SHOP) { 3547 $this->context->smarty->assign( 3548 [ 3549 'display_multishop_checkboxes' => true, 3550 'multishop_check' => Tools::getValue('multishop_check'), 3551 ] 3552 ); 3553 } 3554 3555 if (Shop::getContext() != Shop::CONTEXT_ALL) { 3556 $this->context->smarty->assign('bullet_common_field', '<i class="icon-circle text-orange"></i>'); 3557 $this->context->smarty->assign('display_common_field', true); 3558 } 3559 } 3560 3561 $this->tpl_form_vars['tabs_preloaded'] = $this->available_tabs; 3562 3563 $this->tpl_form_vars['product_type'] = (int) Tools::getValue('type_product', $product->getType()); 3564 3565 $this->getLanguages(); 3566 3567 $this->tpl_form_vars['id_lang_default'] = Configuration::get('PS_LANG_DEFAULT'); 3568 3569 $this->tpl_form_vars['currentIndex'] = static::$currentIndex; 3570 $this->tpl_form_vars['display_multishop_checkboxes'] = (Shop::isFeatureActive() && Shop::getContext() != Shop::CONTEXT_SHOP && $this->display == 'edit'); 3571 $this->fields_form = ['']; 3572 3573 $this->tpl_form_vars['token'] = $this->token; 3574 $this->tpl_form_vars['combinationImagesJs'] = $this->getCombinationImagesJs(); 3575 $this->tpl_form_vars['PS_ALLOW_ACCENTED_CHARS_URL'] = (int) Configuration::get('PS_ALLOW_ACCENTED_CHARS_URL'); 3576 $this->tpl_form_vars['post_data'] = json_encode($_POST); 3577 $this->tpl_form_vars['save_error'] = !empty($this->errors); 3578 $this->tpl_form_vars['mod_evasive'] = Tools::apacheModExists('evasive'); 3579 $this->tpl_form_vars['mod_security'] = Tools::apacheModExists('security'); 3580 $this->tpl_form_vars['ps_force_friendly_product'] = Configuration::get('PS_FORCE_FRIENDLY_PRODUCT'); 3581 3582 // autoload rich text editor (tiny mce) 3583 $this->tpl_form_vars['tinymce'] = true; 3584 $iso = $this->context->language->iso_code; 3585 $this->tpl_form_vars['iso'] = file_exists(_PS_CORE_DIR_.'/js/tiny_mce/langs/'.$iso.'.js') ? $iso : 'en'; 3586 $this->tpl_form_vars['path_css'] = _THEME_CSS_DIR_; 3587 $this->tpl_form_vars['ad'] = __PS_BASE_URI__.basename(_PS_ADMIN_DIR_); 3588 3589 if (Validate::isLoadedObject(($this->object))) { 3590 $idProduct = (int) $this->object->id; 3591 } else { 3592 $idProduct = (int) Tools::getvalue('id_product'); 3593 } 3594 3595 $page = (int) Tools::getValue('page'); 3596 3597 $this->tpl_form_vars['form_action'] = $this->context->link->getAdminLink('AdminProducts').'&'.($idProduct ? 'updateproduct&id_product='.(int) $idProduct : 'addproduct').($page > 1 ? '&page='.(int) $page : ''); 3598 $this->tpl_form_vars['id_product'] = $idProduct; 3599 3600 // Transform configuration option 'upload_max_filesize' in octets 3601 $uploadMaxFilesize = Tools::getOctets(ini_get('upload_max_filesize')); 3602 3603 // Transform configuration option 'upload_max_filesize' in MegaOctets 3604 $uploadMaxFilesize = ($uploadMaxFilesize / 1024) / 1024; 3605 3606 $this->tpl_form_vars['upload_max_filesize'] = $uploadMaxFilesize; 3607 $this->tpl_form_vars['country_display_tax_label'] = $this->context->country->display_tax_label; 3608 $this->tpl_form_vars['has_combinations'] = $this->object->hasAttributes(); 3609 $this->product_exists_in_shop = true; 3610 3611 if ($this->display == 'edit' && Validate::isLoadedObject($product) && Shop::isFeatureActive() && Shop::getContext() == Shop::CONTEXT_SHOP && !$product->isAssociatedToShop($this->context->shop->id)) { 3612 $this->product_exists_in_shop = false; 3613 if ($this->tab_display == 'Informations') { 3614 $this->displayWarning($this->l('Warning: The product does not exist in this shop')); 3615 } 3616 3617 $defaultProduct = new Product(); 3618 $definition = ObjectModel::getDefinition($product); 3619 foreach ($definition['fields'] as $fieldName => $field) { 3620 if (isset($field['shop']) && $field['shop']) { 3621 $product->$fieldName = ObjectModel::formatValue($defaultProduct->$fieldName, $field['type']); 3622 } 3623 } 3624 } 3625 3626 // let's calculate this once for all 3627 if (!Validate::isLoadedObject($this->object) && Tools::getValue('id_product')) { 3628 $this->errors[] = 'Unable to load object'; 3629 } else { 3630 $this->_displayDraftWarning($this->object->active); 3631 3632 // if there was an error while saving, we don't want to lose posted data 3633 if (!empty($this->errors)) { 3634 $this->copyFromPost($this->object, $this->table); 3635 } 3636 3637 $this->initPack($this->object); 3638 $this->{'initForm'.$this->tab_display}($this->object); 3639 $this->tpl_form_vars['product'] = $this->object; 3640 3641 if ($this->ajax) { 3642 if (!isset($this->tpl_form_vars['custom_form'])) { 3643 throw new PrestaShopException('custom_form empty for action '.$this->tab_display); 3644 } else { 3645 return $this->tpl_form_vars['custom_form']; 3646 } 3647 } 3648 } 3649 3650 $parent = parent::renderForm(); 3651 $this->addJqueryPlugin(['autocomplete', 'fancybox', 'typewatch']); 3652 3653 return $parent; 3654 } 3655 3656 /** 3657 * Get combination images JS 3658 * 3659 * @return string 3660 * 3661 * @since 1.0.0 3662 */ 3663 public function getCombinationImagesJS() 3664 { 3665 /** @var Product $obj */ 3666 if (!($obj = $this->loadObject(true))) { 3667 return ''; 3668 } 3669 3670 $content = 'var combination_images = new Array();'; 3671 if (!$allCombinationImages = $obj->getCombinationImages($this->context->language->id)) { 3672 return $content; 3673 } 3674 foreach ($allCombinationImages as $idProductAttribute => $combinationImages) { 3675 $i = 0; 3676 $content .= 'combination_images['.(int) $idProductAttribute.'] = new Array();'; 3677 foreach ($combinationImages as $combinationImage) { 3678 $content .= 'combination_images['.(int) $idProductAttribute.']['.$i++.'] = '.(int) $combinationImage['id_image'].';'; 3679 } 3680 } 3681 3682 return $content; 3683 } 3684 3685 /** 3686 * Display draft warning 3687 * 3688 * @param $active 3689 * 3690 * @since 1.0.0 3691 */ 3692 protected function _displayDraftWarning($active) 3693 { 3694 $content = '<div class="warn draft" style="'.($active ? 'display:none' : '').'"> 3695 <span>'.$this->l('Your product will be saved as a draft.').'</span> 3696 <a href="#" class="btn btn-default pull-right" onclick="submitAddProductAndPreview()" ><i class="icon-external-link-sign"></i> '.$this->l('Save and preview').'</a> 3697 <input type="hidden" name="fakeSubmitAddProductAndPreview" id="fakeSubmitAddProductAndPreview" /> 3698 </div>'; 3699 $this->tpl_form_vars['draft_warning'] = $content; 3700 } 3701 3702 protected function initPack(Product $product) 3703 { 3704 $this->tpl_form_vars['is_pack'] = ($product->id && Pack::isPack($product->id)) || Tools::getValue('type_product') == Product::PTYPE_PACK; 3705 $product->packItems = Pack::getItems($product->id, $this->context->language->id); 3706 3707 $inputPackItems = ''; 3708 if (Tools::getValue('inputPackItems')) { 3709 $inputPackItems = Tools::getValue('inputPackItems'); 3710 } else { 3711 if (is_array($product->packItems)) { 3712 foreach ($product->packItems as $packItem) { 3713 $inputPackItems .= $packItem->pack_quantity.'x'.$packItem->id.'-'; 3714 } 3715 } 3716 } 3717 $this->tpl_form_vars['input_pack_items'] = $inputPackItems; 3718 3719 $inputNamepackItems = ''; 3720 if (Tools::getValue('namePackItems')) { 3721 $inputNamepackItems = Tools::getValue('namePackItems'); 3722 } else { 3723 if (is_array($product->packItems)) { 3724 foreach ($product->packItems as $packItem) { 3725 $inputNamepackItems .= $packItem->pack_quantity.' x '.$packItem->name.'¤'; 3726 } 3727 } 3728 } 3729 $this->tpl_form_vars['input_namepack_items'] = $inputNamepackItems; 3730 } 3731 3732 /** 3733 * @param Product $obj 3734 * 3735 * @throws Exception 3736 * @throws PrestaShopException 3737 * @throws SmartyException 3738 * 3739 * @since 1.0.0 3740 */ 3741 public function initFormAssociations($obj) 3742 { 3743 $product = $obj; 3744 $data = $this->createTemplate($this->tpl_form); 3745 // Prepare Categories tree for display in Associations tab 3746 $root = Category::getRootCategory(); 3747 $defaultCategory = $this->context->cookie->id_category_products_filter ? $this->context->cookie->id_category_products_filter : $this->context->shop->id_category; 3748 if (!$product->id || !$product->isAssociatedToShop()) { 3749 $selectedCat = Category::getCategoryInformations(Tools::getValue('categoryBox', [$defaultCategory]), $this->default_form_language); 3750 } else { 3751 if (Tools::isSubmit('categoryBox')) { 3752 $selectedCat = Category::getCategoryInformations(Tools::getValue('categoryBox', [$defaultCategory]), $this->default_form_language); 3753 } else { 3754 $selectedCat = Product::getProductCategoriesFull($product->id, $this->default_form_language); 3755 } 3756 } 3757 3758 // Multishop block 3759 $data->assign('feature_shop_active', Shop::isFeatureActive()); 3760 $helper = new HelperForm(); 3761 if ($this->object && $this->object->id) { 3762 $helper->id = $this->object->id; 3763 } else { 3764 $helper->id = null; 3765 } 3766 $helper->table = $this->table; 3767 $helper->identifier = $this->identifier; 3768 3769 // Accessories block 3770 $accessories = Product::getAccessoriesLight($this->context->language->id, $product->id); 3771 3772 if ($postAccessories = Tools::getValue('inputAccessories')) { 3773 $postAccessoriesTab = explode('-', $postAccessories); 3774 foreach ($postAccessoriesTab as $accessoryId) { 3775 if (!$this->haveThisAccessory($accessoryId, $accessories) && $accessory = Product::getAccessoryById($accessoryId)) { 3776 $accessories[] = $accessory; 3777 } 3778 } 3779 } 3780 $data->assign('accessories', $accessories); 3781 3782 $product->manufacturer_name = Manufacturer::getNameById($product->id_manufacturer); 3783 3784 $categories = []; 3785 foreach ($selectedCat as $key => $category) { 3786 $categories[] = $key; 3787 } 3788 3789 $tree = new HelperTreeCategories('associated-categories-tree', 'Associated categories'); 3790 $tree->setTemplate('tree_associated_categories.tpl') 3791 ->setHeaderTemplate('tree_associated_header.tpl') 3792 ->setRootCategory((int) $root->id) 3793 ->setUseCheckBox(true) 3794 ->setUseSearch(true) 3795 ->setSelectedCategories($categories); 3796 3797 $data->assign( 3798 [ 3799 'default_category' => $defaultCategory, 3800 'selected_cat_ids' => implode(',', array_keys($selectedCat)), 3801 'selected_cat' => $selectedCat, 3802 'id_category_default' => $product->getDefaultCategory(), 3803 'category_tree' => $tree->render(), 3804 'product' => $product, 3805 'link' => $this->context->link, 3806 'is_shop_context' => Shop::getContext() == Shop::CONTEXT_SHOP, 3807 ] 3808 ); 3809 3810 $this->tpl_form_vars['custom_form'] = $data->fetch(); 3811 } 3812 3813 /** 3814 * @param $accessoryId 3815 * @param $accessories 3816 * 3817 * @return bool 3818 * 3819 * @since 1.0.0 3820 */ 3821 public function haveThisAccessory($accessoryId, $accessories) 3822 { 3823 foreach ($accessories as $accessory) { 3824 if ((int) $accessory['id_product'] == (int) $accessoryId) { 3825 return true; 3826 } 3827 } 3828 3829 return false; 3830 } 3831 3832 /** 3833 * @param Product $obj 3834 * 3835 * @throws Exception 3836 * @throws SmartyException 3837 * 3838 * @since 1.0.0 3839 */ 3840 public function initFormPrices($obj) 3841 { 3842 $data = $this->createTemplate($this->tpl_form); 3843 $product = $obj; 3844 if ($obj->id) { 3845 $shops = Shop::getShops(); 3846 $countries = Country::getCountries($this->context->language->id); 3847 $groups = Group::getGroups($this->context->language->id, true); 3848 $currencies = Currency::getCurrencies(); 3849 $attributes = $obj->getAttributesGroups((int) $this->context->language->id); 3850 $combinations = []; 3851 foreach ($attributes as $attribute) { 3852 $combinations[$attribute['id_product_attribute']]['id_product_attribute'] = $attribute['id_product_attribute']; 3853 if (!isset($combinations[$attribute['id_product_attribute']]['attributes'])) { 3854 $combinations[$attribute['id_product_attribute']]['attributes'] = ''; 3855 } 3856 $combinations[$attribute['id_product_attribute']]['attributes'] .= $attribute['attribute_name'].' - '; 3857 3858 $combinations[$attribute['id_product_attribute']]['price'] = Tools::displayPrice( 3859 Tools::convertPrice( 3860 Product::getPriceStatic((int) $obj->id, false, $attribute['id_product_attribute']), 3861 $this->context->currency 3862 ), 3863 $this->context->currency 3864 ); 3865 } 3866 foreach ($combinations as &$combination) { 3867 $combination['attributes'] = rtrim($combination['attributes'], ' - '); 3868 } 3869 $data->assign( 3870 'specificPriceModificationForm', 3871 $this->_displaySpecificPriceModificationForm($this->context->currency, $shops, $currencies, $countries, $groups) 3872 ); 3873 3874 $data->assign('ecotax_tax_excl', (float) $obj->ecotax); 3875 $this->_applyTaxToEcotax($obj); 3876 3877 $data->assign( 3878 [ 3879 'shops' => $shops, 3880 'admin_one_shop' => count($this->context->employee->getAssociatedShops()) == 1, 3881 'currencies' => $currencies, 3882 'countries' => $countries, 3883 'groups' => $groups, 3884 'combinations' => $combinations, 3885 'multi_shop' => Shop::isFeatureActive(), 3886 'link' => new Link(), 3887 'pack' => new Pack(), 3888 ] 3889 ); 3890 } else { 3891 $this->displayWarning($this->l('You must save this product before adding specific pricing')); 3892 $product->id_tax_rules_group = (int) Product::getIdTaxRulesGroupMostUsed(); 3893 $data->assign('ecotax_tax_excl', 0); 3894 } 3895 3896 $address = new Address(); 3897 $address->id_country = (int) $this->context->country->id; 3898 $taxRulesGroups = TaxRulesGroup::getTaxRulesGroups(true); 3899 $taxRates = [ 3900 0 => [ 3901 'id_tax_rules_group' => 0, 3902 'rates' => [0], 3903 'computation_method' => 0, 3904 ], 3905 ]; 3906 3907 foreach ($taxRulesGroups as $taxRulesGroup) { 3908 $idTaxRulesGroup = (int) $taxRulesGroup['id_tax_rules_group']; 3909 $taxCalculator = TaxManagerFactory::getManager($address, $idTaxRulesGroup)->getTaxCalculator(); 3910 $taxRates[$idTaxRulesGroup] = [ 3911 'id_tax_rules_group' => $idTaxRulesGroup, 3912 'rates' => [], 3913 'computation_method' => (int) $taxCalculator->computation_method, 3914 ]; 3915 3916 if (isset($taxCalculator->taxes) && count($taxCalculator->taxes)) { 3917 foreach ($taxCalculator->taxes as $tax) { 3918 $taxRates[$idTaxRulesGroup]['rates'][] = (float) $tax->rate; 3919 } 3920 } else { 3921 $taxRates[$idTaxRulesGroup]['rates'][] = 0; 3922 } 3923 } 3924 3925 // prices part 3926 $data->assign( 3927 [ 3928 'link' => $this->context->link, 3929 'currency' => $currency = $this->context->currency, 3930 'tax_rules_groups' => $taxRulesGroups, 3931 'taxesRatesByGroup' => $taxRates, 3932 'ecotaxTaxRate' => Tax::getProductEcotaxRate(), 3933 'tax_exclude_taxe_option' => Tax::excludeTaxeOption(), 3934 'ps_use_ecotax' => Configuration::get('PS_USE_ECOTAX'), 3935 ] 3936 ); 3937 3938 $product->price = Tools::convertPrice($product->price, $this->context->currency, true, $this->context); 3939 if ($product->unit_price_ratio != 0) { 3940 $data->assign('unit_price', priceval( 3941 $product->price / $product->unit_price_ratio 3942 )); 3943 } else { 3944 $data->assign('unit_price', 0); 3945 } 3946 $data->assign('ps_tax', Configuration::get('PS_TAX')); 3947 3948 $data->assign('country_display_tax_label', $this->context->country->display_tax_label); 3949 $data->assign( 3950 [ 3951 'currency', $this->context->currency, 3952 'product' => $product, 3953 'token' => $this->token, 3954 ] 3955 ); 3956 3957 $this->tpl_form_vars['custom_form'] = $data->fetch(); 3958 } 3959 3960 /** 3961 * @param $defaultCurrency 3962 * @param $shops 3963 * @param $currencies 3964 * @param $countries 3965 * @param $groups 3966 * 3967 * @return string 3968 * 3969 * @since 1.0.0 3970 */ 3971 protected function _displaySpecificPriceModificationForm($defaultCurrency, $shops, $currencies, $countries, $groups) 3972 { 3973 /** @var Product $obj */ 3974 if (!($obj = $this->loadObject())) { 3975 return ''; 3976 } 3977 3978 $page = (int) Tools::getValue('page'); 3979 $content = ''; 3980 $specificPrices = SpecificPrice::getByProductId((int) $obj->id); 3981 $specificPricePriorities = SpecificPrice::getPriority((int) $obj->id); 3982 3983 $tmp = []; 3984 foreach ($shops as $shop) { 3985 $tmp[$shop['id_shop']] = $shop; 3986 } 3987 $shops = $tmp; 3988 $tmp = []; 3989 foreach ($currencies as $currency) { 3990 $tmp[$currency['id_currency']] = $currency; 3991 } 3992 $currencies = $tmp; 3993 3994 $tmp = []; 3995 foreach ($countries as $country) { 3996 $tmp[$country['id_country']] = $country; 3997 } 3998 $countries = $tmp; 3999 4000 $tmp = []; 4001 foreach ($groups as $group) { 4002 $tmp[$group['id_group']] = $group; 4003 } 4004 $groups = $tmp; 4005 4006 $lengthBefore = strlen($content); 4007 if (is_array($specificPrices) && count($specificPrices)) { 4008 $i = 0; 4009 foreach ($specificPrices as $specificPrice) { 4010 $idCurrency = $specificPrice['id_currency'] ? $specificPrice['id_currency'] : $defaultCurrency->id; 4011 if (!isset($currencies[$idCurrency])) { 4012 continue; 4013 } 4014 4015 $currentSpecificCurrency = $currencies[$idCurrency]; 4016 if ($specificPrice['reduction_type'] == 'percentage') { 4017 $impact = '- '.($specificPrice['reduction'] * 100).' %'; 4018 } elseif ($specificPrice['reduction'] > 0) { 4019 $impact = '- '.Tools::displayPrice( 4020 $specificPrice['reduction'], 4021 $currentSpecificCurrency 4022 ).' '; 4023 if ($specificPrice['reduction_tax']) { 4024 $impact .= '('.$this->l('Tax incl.').')'; 4025 } else { 4026 $impact .= '('.$this->l('Tax excl.').')'; 4027 } 4028 } else { 4029 $impact = '--'; 4030 } 4031 4032 if ($specificPrice['from'] == '0000-00-00 00:00:00' && $specificPrice['to'] == '0000-00-00 00:00:00') { 4033 $period = $this->l('Unlimited'); 4034 } else { 4035 $period = $this->l('From').' '.($specificPrice['from'] != '0000-00-00 00:00:00' ? $specificPrice['from'] : '0000-00-00 00:00:00').'<br />'.$this->l('To').' '.($specificPrice['to'] != '0000-00-00 00:00:00' ? $specificPrice['to'] : '0000-00-00 00:00:00'); 4036 } 4037 if ($specificPrice['id_product_attribute']) { 4038 $combination = new Combination((int) $specificPrice['id_product_attribute']); 4039 $attributes = $combination->getAttributesName((int) $this->context->language->id); 4040 $attributesName = ''; 4041 foreach ($attributes as $attribute) { 4042 $attributesName .= $attribute['name'].' - '; 4043 } 4044 $attributesName = rtrim($attributesName, ' - '); 4045 } else { 4046 $attributesName = $this->l('All combinations'); 4047 } 4048 4049 $rule = new SpecificPriceRule((int) $specificPrice['id_specific_price_rule']); 4050 $ruleName = ($rule->id ? $rule->name : '--'); 4051 4052 if ($specificPrice['id_customer']) { 4053 $customer = new Customer((int) $specificPrice['id_customer']); 4054 if (Validate::isLoadedObject($customer)) { 4055 $customerFullName = $customer->firstname.' '.$customer->lastname; 4056 } 4057 unset($customer); 4058 } 4059 4060 if (!$specificPrice['id_shop'] || in_array($specificPrice['id_shop'], Shop::getContextListShopID())) { 4061 $content .= ' 4062 <tr '.($i % 2 ? 'class="alt_row"' : '').'> 4063 <td>'.$ruleName.'</td> 4064 <td>'.$attributesName.'</td>'; 4065 4066 $canDeleteSpecificPrices = true; 4067 if (Shop::isFeatureActive()) { 4068 $idShopSp = $specificPrice['id_shop']; 4069 $canDeleteSpecificPrices = (count($this->context->employee->getAssociatedShops()) > 1 && !$idShopSp) || $idShopSp; 4070 $content .= ' 4071 <td>'.($idShopSp ? $shops[$idShopSp]['name'] : $this->l('All shops')).'</td>'; 4072 } 4073 $price = $specificPrice['price']; 4074 $fixedPrice = '--'; 4075 if ((string) $price === (string) $obj->price 4076 || $price == -1) { 4077 Tools::displayPrice($price, $currentSpecificCurrency); 4078 } 4079 $content .= ' 4080 <td>'.($specificPrice['id_currency'] ? $currencies[$specificPrice['id_currency']]['name'] : $this->l('All currencies')).'</td> 4081 <td>'.($specificPrice['id_country'] ? $countries[$specificPrice['id_country']]['name'] : $this->l('All countries')).'</td> 4082 <td>'.($specificPrice['id_group'] ? $groups[$specificPrice['id_group']]['name'] : $this->l('All groups')).'</td> 4083 <td title="'.$this->l('ID:').' '.$specificPrice['id_customer'].'">'.(isset($customerFullName) ? $customerFullName : $this->l('All customers')).'</td> 4084 <td>'.$fixedPrice.'</td> 4085 <td>'.$impact.'</td> 4086 <td>'.$period.'</td> 4087 <td>'.$specificPrice['from_quantity'].'</th> 4088 <td>'.((!$rule->id && $canDeleteSpecificPrices) ? '<a class="btn btn-default" name="delete_link" href="'.static::$currentIndex.'&id_product='.(int) Tools::getValue('id_product').'&action=deleteSpecificPrice&id_specific_price='.(int) ($specificPrice['id_specific_price']).'&token='.Tools::getValue('token').'"><i class="icon-trash"></i></a>' : '').'</td> 4089 </tr>'; 4090 $i++; 4091 unset($customerFullName); 4092 } 4093 } 4094 } 4095 4096 if ($lengthBefore === strlen($content)) { 4097 $content .= ' 4098 <tr> 4099 <td class="text-center" colspan="13"><i class="icon-warning-sign"></i> '.$this->l('No specific prices.').'</td> 4100 </tr>'; 4101 } 4102 4103 $content .= ' 4104 </tbody> 4105 </table> 4106 </div> 4107 <div class="panel-footer"> 4108 <a href="'.$this->context->link->getAdminLink('AdminProducts').($page > 1 ? '&submitFilter'.$this->table.'='.(int) $page : '').'" class="btn btn-default"><i class="process-icon-cancel"></i> '.$this->l('Cancel').'</a> 4109 <button id="product_form_submit_btn" type="submit" name="submitAddproduct" class="btn btn-default pull-right" disabled="disabled"><i class="process-icon-loading"></i> '.$this->l('Save').'</button> 4110 <button id="product_form_submit_btn" type="submit" name="submitAddproductAndStay" class="btn btn-default pull-right" disabled="disabled"><i class="process-icon-loading"></i> '.$this->l('Save and stay').'</button> 4111 </div> 4112 </div>'; 4113 4114 $content .= ' 4115 <script type="text/javascript"> 4116 var currencies = []; 4117 currencies[0] = []; 4118 currencies[0]["sign"] = "'.$defaultCurrency->sign.'"; 4119 currencies[0]["format"] = '.intval($defaultCurrency->format).'; 4120 '; 4121 foreach ($currencies as $currency) { 4122 $content .= ' 4123 currencies['.$currency['id_currency'].'] = new Array(); 4124 currencies['.$currency['id_currency'].']["sign"] = "'.$currency['sign'].'"; 4125 currencies['.$currency['id_currency'].']["format"] = '.intval($currency['format']).'; 4126 '; 4127 } 4128 $content .= ' 4129 </script> 4130 '; 4131 4132 // Not use id_customer 4133 if ($specificPricePriorities[0] == 'id_customer') { 4134 unset($specificPricePriorities[0]); 4135 } 4136 // Reindex array starting from 0 4137 $specificPricePriorities = array_values($specificPricePriorities); 4138 4139 $content .= '<div class="panel"> 4140 <h3>'.$this->l('Priority management').'</h3> 4141 <div class="alert alert-info"> 4142 '.$this->l('Sometimes one customer can fit into multiple price rules. Priorities allow you to define which rule applies to the customer.').' 4143 </div>'; 4144 4145 $content .= ' 4146 <div class="form-group"> 4147 <label class="control-label col-lg-3" for="specificPricePriority1">'.$this->l('Priorities').'</label> 4148 <div class="input-group col-lg-9"> 4149 <select id="specificPricePriority1" name="specificPricePriority[]"> 4150 <option value="id_shop"'.($specificPricePriorities[0] == 'id_shop' ? ' selected="selected"' : '').'>'.$this->l('Shop').'</option> 4151 <option value="id_currency"'.($specificPricePriorities[0] == 'id_currency' ? ' selected="selected"' : '').'>'.$this->l('Currency').'</option> 4152 <option value="id_country"'.($specificPricePriorities[0] == 'id_country' ? ' selected="selected"' : '').'>'.$this->l('Country').'</option> 4153 <option value="id_group"'.($specificPricePriorities[0] == 'id_group' ? ' selected="selected"' : '').'>'.$this->l('Group').'</option> 4154 </select> 4155 <span class="input-group-addon"><i class="icon-chevron-right"></i></span> 4156 <select name="specificPricePriority[]"> 4157 <option value="id_shop"'.($specificPricePriorities[1] == 'id_shop' ? ' selected="selected"' : '').'>'.$this->l('Shop').'</option> 4158 <option value="id_currency"'.($specificPricePriorities[1] == 'id_currency' ? ' selected="selected"' : '').'>'.$this->l('Currency').'</option> 4159 <option value="id_country"'.($specificPricePriorities[1] == 'id_country' ? ' selected="selected"' : '').'>'.$this->l('Country').'</option> 4160 <option value="id_group"'.($specificPricePriorities[1] == 'id_group' ? ' selected="selected"' : '').'>'.$this->l('Group').'</option> 4161 </select> 4162 <span class="input-group-addon"><i class="icon-chevron-right"></i></span> 4163 <select name="specificPricePriority[]"> 4164 <option value="id_shop"'.($specificPricePriorities[2] == 'id_shop' ? ' selected="selected"' : '').'>'.$this->l('Shop').'</option> 4165 <option value="id_currency"'.($specificPricePriorities[2] == 'id_currency' ? ' selected="selected"' : '').'>'.$this->l('Currency').'</option> 4166 <option value="id_country"'.($specificPricePriorities[2] == 'id_country' ? ' selected="selected"' : '').'>'.$this->l('Country').'</option> 4167 <option value="id_group"'.($specificPricePriorities[2] == 'id_group' ? ' selected="selected"' : '').'>'.$this->l('Group').'</option> 4168 </select> 4169 <span class="input-group-addon"><i class="icon-chevron-right"></i></span> 4170 <select name="specificPricePriority[]"> 4171 <option value="id_shop"'.($specificPricePriorities[3] == 'id_shop' ? ' selected="selected"' : '').'>'.$this->l('Shop').'</option> 4172 <option value="id_currency"'.($specificPricePriorities[3] == 'id_currency' ? ' selected="selected"' : '').'>'.$this->l('Currency').'</option> 4173 <option value="id_country"'.($specificPricePriorities[3] == 'id_country' ? ' selected="selected"' : '').'>'.$this->l('Country').'</option> 4174 <option value="id_group"'.($specificPricePriorities[3] == 'id_group' ? ' selected="selected"' : '').'>'.$this->l('Group').'</option> 4175 </select> 4176 </div> 4177 </div> 4178 <div class="form-group"> 4179 <div class="col-lg-9 col-lg-offset-3"> 4180 <p class="checkbox"> 4181 <label for="specificPricePriorityToAll"><input type="checkbox" name="specificPricePriorityToAll" id="specificPricePriorityToAll" />'.$this->l('Apply to all products').'</label> 4182 </p> 4183 </div> 4184 </div> 4185 <div class="panel-footer"> 4186 <a href="'.$this->context->link->getAdminLink('AdminProducts').($page > 1 ? '&submitFilter'.$this->table.'='.(int) $page : '').'" class="btn btn-default"><i class="process-icon-cancel"></i> '.$this->l('Cancel').'</a> 4187 <button id="product_form_submit_btn" type="submit" name="submitAddproduct" class="btn btn-default pull-right" disabled="disabled"><i class="process-icon-loading"></i> '.$this->l('Save').'</button> 4188 <button id="product_form_submit_btn" type="submit" name="submitAddproductAndStay" class="btn btn-default pull-right" disabled="disabled"><i class="process-icon-loading"></i> '.$this->l('Save and stay').'</button> 4189 </div> 4190 </div> 4191 '; 4192 4193 return $content; 4194 } 4195 4196 /** 4197 * Apply tax to eco tax 4198 * 4199 * @param Product $product 4200 * 4201 * @since 1.0.0 4202 */ 4203 protected function _applyTaxToEcotax($product) 4204 { 4205 if ($product->ecotax) { 4206 $product->ecotax = priceval( 4207 $product->ecotax * (1 + Tax::getProductEcotaxRate() / 100) 4208 ); 4209 } 4210 } 4211 4212 /** 4213 * Initialize SEO form 4214 * 4215 * @param Product $product 4216 * 4217 * @since 1.0.0 4218 */ 4219 public function initFormSeo($product) 4220 { 4221 if (!$this->default_form_language) { 4222 $this->getLanguages(); 4223 } 4224 4225 $data = $this->createTemplate($this->tpl_form); 4226 4227 $context = $this->context; 4228 $rewrittenLinks = []; 4229 if (!Validate::isLoadedObject($product) || !$product->id_category_default) { 4230 foreach ($this->_languages as $language) { 4231 $rewrittenLinks[(int) $language['id_lang']] = [$this->l('Unable to determine the preview URL. This product has not been linked with a category, yet.')]; 4232 } 4233 } else { 4234 foreach ($this->_languages as $language) { 4235 $rewrittenLinks[(int) $language['id_lang']] = explode( 4236 '[REWRITE]', 4237 $context->link->getProductLink($product->id, '[REWRITE]', (int) $product->id_category_default) 4238 ); 4239 } 4240 } 4241 4242 $data->assign( 4243 [ 4244 'product' => $product, 4245 'languages' => $this->_languages, 4246 'id_lang' => $this->context->language->id, 4247 'ps_ssl_enabled' => Configuration::get('PS_SSL_ENABLED'), 4248 'curent_shop_url' => $this->context->shop->getBaseURL(), 4249 'default_form_language' => $this->default_form_language, 4250 'rewritten_links' => $rewrittenLinks, 4251 ] 4252 ); 4253 4254 $this->tpl_form_vars['custom_form'] = $data->fetch(); 4255 } 4256 4257 /** 4258 * @param Product $product 4259 * 4260 * @throws Exception 4261 * @throws SmartyException 4262 * 4263 * @since 1.0.0 4264 */ 4265 public function initFormPack($product) 4266 { 4267 $data = $this->createTemplate($this->tpl_form); 4268 4269 // If pack items have been submitted, we want to display them instead of the actuel content of the pack 4270 // in database. In case of a submit error, the posted data is not lost and can be sent again. 4271 if (Tools::getValue('namePackItems')) { 4272 $inputPackItems = Tools::getValue('inputPackItems'); 4273 $inputNamepackItems = Tools::getValue('namePackItems'); 4274 $packItems = $this->getPackItems(); 4275 } else { 4276 $product->packItems = Pack::getItems($product->id, $this->context->language->id); 4277 $packItems = $this->getPackItems($product); 4278 $inputNamepackItems = ''; 4279 $inputPackItems = ''; 4280 foreach ($packItems as $packItem) { 4281 $inputPackItems .= $packItem['pack_quantity'].'x'.$packItem['id'].'x'.$packItem['id_product_attribute'].'-'; 4282 $inputNamepackItems .= $packItem['pack_quantity'].' x '.$packItem['name'].'¤'; 4283 } 4284 } 4285 4286 $data->assign( 4287 [ 4288 'input_pack_items' => $inputPackItems, 4289 'input_namepack_items' => $inputNamepackItems, 4290 'pack_items' => $packItems, 4291 'product_type' => (int) Tools::getValue('type_product', $product->getType()), 4292 ] 4293 ); 4294 4295 $this->tpl_form_vars['custom_form'] = $data->fetch(); 4296 } 4297 4298 /** 4299 * Get an array of pack items for display from the product object if specified, else from POST/GET values 4300 * 4301 * @param Product $product 4302 * 4303 * @return array of pack items 4304 * 4305 * @since 1.0.0 4306 */ 4307 public function getPackItems($product = null) 4308 { 4309 $packItems = []; 4310 4311 if (!$product) { 4312 $namesInput = Tools::getValue('namePackItems'); 4313 $idsInput = Tools::getValue('inputPackItems'); 4314 if (!$namesInput || !$idsInput) { 4315 return []; 4316 } 4317 // ids is an array of string with format : QTYxID 4318 $ids = array_unique(explode('-', $idsInput)); 4319 $names = array_unique(explode('¤', $namesInput)); 4320 4321 if (!empty($ids)) { 4322 $length = count($ids); 4323 for ($i = 0; $i < $length; $i++) { 4324 if (!empty($ids[$i]) && !empty($names[$i])) { 4325 list($packItems[$i]['pack_quantity'], $packItems[$i]['id']) = explode('x', $ids[$i]); 4326 $explodedName = explode('x', $names[$i]); 4327 $packItems[$i]['name'] = $explodedName[1]; 4328 } 4329 } 4330 } 4331 } else { 4332 if (is_array($product->packItems)) { 4333 $i = 0; 4334 foreach ($product->packItems as $packItem) { 4335 $packItems[$i]['id'] = $packItem->id; 4336 $packItems[$i]['pack_quantity'] = $packItem->pack_quantity; 4337 $packItems[$i]['name'] = $packItem->name; 4338 $packItems[$i]['reference'] = $packItem->reference; 4339 $packItems[$i]['id_product_attribute'] = isset($packItem->id_pack_product_attribute) && $packItem->id_pack_product_attribute ? $packItem->id_pack_product_attribute : 0; 4340 $cover = $packItem->id_pack_product_attribute ? Product::getCombinationImageById($packItem->id_pack_product_attribute, $this->context->language->id) : Product::getCover($packItem->id); 4341 $packItems[$i]['image'] = $this->context->link->getImageLink($packItem->link_rewrite, $cover['id_image'], 'home_default'); 4342 // @todo: don't rely on 'home_default' 4343 //$path_to_image = _PS_IMG_DIR_.'p/'.Image::getImgFolderStatic($cover['id_image']).(int)$cover['id_image'].'.jpg'; 4344 //$pack_items[$i]['image'] = ImageManager::thumbnail($path_to_image, 'pack_mini_'.$pack_item->id.'_'.$this->context->shop->id.'.jpg', 120); 4345 $i++; 4346 } 4347 } 4348 } 4349 4350 return $packItems; 4351 } 4352 4353 /** 4354 * Initialize virtual product form 4355 * 4356 * @param Product $product 4357 * 4358 * @since 1.0.0 4359 */ 4360 public function initFormVirtualProduct($product) 4361 { 4362 $data = $this->createTemplate($this->tpl_form); 4363 4364 $currency = $this->context->currency; 4365 4366 $idProductDownload = ProductDownload::getIdFromIdProduct($product->id, false); 4367 // This might give an empty record, which is fine. 4368 $productDownload = new ProductDownload($idProductDownload); 4369 4370 if (!ProductDownload::checkWritableDir()) { 4371 $this->errors[] = Tools::displayError('Download repository is not writable.'); 4372 $this->tab_display = 'VirtualProduct'; 4373 } 4374 4375 if ($productDownload->id) { 4376 // Check the downloadable file. 4377 $fileNotRight = false; 4378 if (!$productDownload->filename) { 4379 $this->errors[] = Tools::displayError('A downloadable file is missing.'); 4380 $fileNotRight = true; 4381 } else { 4382 if (!$productDownload->display_filename) { 4383 $this->errors[] = Tools::displayError('A file name is required.'); 4384 $fileNotRight = true; 4385 } 4386 if (!$productDownload->checkFile()) { 4387 $this->errors[] = Tools::displayError('File on the server is missing, should be').' '._PS_DOWNLOAD_DIR_.$productDownload->filename.'.'; 4388 $fileNotRight = true; 4389 } 4390 } 4391 if ($fileNotRight) { 4392 if ($productDownload->active) { 4393 $productDownload->active = false; 4394 $productDownload->update(); 4395 } 4396 4397 $this->tab_display = 'VirtualProduct'; 4398 } 4399 } 4400 4401 $product->productDownload = $productDownload; 4402 4403 $virtualProductFileUploader = new HelperUploader('virtual_product_file_uploader'); 4404 $virtualProductFileUploader->setMultiple(false)->setUrl( 4405 $this->context->link->getAdminLink('AdminProducts').'&ajax=1&id_product='.(int) $product->id 4406 .'&action=AddVirtualProductFile' 4407 )->setPostMaxSize(Tools::getOctets(ini_get('upload_max_filesize'))) 4408 ->setTemplate('virtual_product.tpl'); 4409 4410 if ($productDownload->date_expiration !== '0000-00-00 00:00:00') { 4411 $productDownload->date_expiration = substr($productDownload->date_expiration, 0, 10); 4412 } else { 4413 $productDownload->date_expiration = ''; 4414 } 4415 4416 $data->assign( 4417 [ 4418 'product' => $product, 4419 'token' => $this->token, 4420 'currency' => $currency, 4421 'link' => $this->context->link, 4422 'is_file' => $product->productDownload->checkFile(), 4423 'virtual_product_file_uploader' => $virtualProductFileUploader->render(), 4424 ] 4425 ); 4426 $data->assign($this->tpl_form_vars); 4427 $this->tpl_form_vars['product'] = $product; 4428 $this->tpl_form_vars['custom_form'] = $data->fetch(); 4429 } 4430 4431 /** 4432 * @param Product $obj 4433 * 4434 * @throws Exception 4435 * @throws SmartyException 4436 */ 4437 public function initFormCustomization($obj) 4438 { 4439 $data = $this->createTemplate($this->tpl_form); 4440 4441 if ((bool) $obj->id) { 4442 if ($this->product_exists_in_shop) { 4443 $labels = $obj->getCustomizationFields(); 4444 4445 $hasFileLabels = (int) $this->getFieldValue($obj, 'uploadable_files'); 4446 $hasTextLabels = (int) $this->getFieldValue($obj, 'text_fields'); 4447 4448 $data->assign( 4449 [ 4450 'obj' => $obj, 4451 'table' => $this->table, 4452 'languages' => $this->_languages, 4453 'has_file_labels' => $hasFileLabels, 4454 'display_file_labels' => $this->_displayLabelFields($obj, $labels, $this->_languages, Configuration::get('PS_LANG_DEFAULT'), Product::CUSTOMIZE_FILE), 4455 'has_text_labels' => $hasTextLabels, 4456 'display_text_labels' => $this->_displayLabelFields($obj, $labels, $this->_languages, Configuration::get('PS_LANG_DEFAULT'), Product::CUSTOMIZE_TEXTFIELD), 4457 'uploadable_files' => (int) ($this->getFieldValue($obj, 'uploadable_files') ? (int) $this->getFieldValue($obj, 'uploadable_files') : '0'), 4458 'text_fields' => (int) ($this->getFieldValue($obj, 'text_fields') ? (int) $this->getFieldValue($obj, 'text_fields') : '0'), 4459 ] 4460 ); 4461 } else { 4462 $this->displayWarning($this->l('You must save the product in this shop before adding customization.')); 4463 } 4464 } else { 4465 $this->displayWarning($this->l('You must save this product before adding customization.')); 4466 } 4467 4468 $this->tpl_form_vars['custom_form'] = $data->fetch(); 4469 } 4470 4471 /** 4472 * @param $obj 4473 * @param $labels 4474 * @param $languages 4475 * @param $defaultLanguage 4476 * @param $type 4477 * 4478 * @return string 4479 * 4480 * @since 1.0.0 4481 */ 4482 protected function _displayLabelFields(&$obj, &$labels, $languages, $defaultLanguage, $type) 4483 { 4484 $content = ''; 4485 $type = (int) ($type); 4486 $labelGenerated = [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)]; 4487 4488 $fieldIds = $this->_getCustomizationFieldIds($labels, $labelGenerated, $obj); 4489 if (isset($labels[$type])) { 4490 foreach ($labels[$type] as $idCustomizationField => $label) { 4491 $content .= $this->_displayLabelField($label, $languages, $defaultLanguage, $type, $fieldIds, (int) ($idCustomizationField)); 4492 } 4493 } 4494 4495 return $content; 4496 } 4497 4498 /** 4499 * @param $labels 4500 * @param $alreadyGenerated 4501 * @param $obj 4502 * 4503 * @return string 4504 * 4505 * @since 1.0.0 4506 */ 4507 protected function _getCustomizationFieldIds($labels, $alreadyGenerated, $obj) 4508 { 4509 $customizableFieldIds = []; 4510 if (isset($labels[Product::CUSTOMIZE_FILE])) { 4511 foreach ($labels[Product::CUSTOMIZE_FILE] as $idCustomizationField => $label) { 4512 $customizableFieldIds[] = 'label_'.Product::CUSTOMIZE_FILE.'_'.(int) ($idCustomizationField); 4513 } 4514 } 4515 if (isset($labels[Product::CUSTOMIZE_TEXTFIELD])) { 4516 foreach ($labels[Product::CUSTOMIZE_TEXTFIELD] as $idCustomizationField => $label) { 4517 $customizableFieldIds[] = 'label_'.Product::CUSTOMIZE_TEXTFIELD.'_'.(int) ($idCustomizationField); 4518 } 4519 } 4520 $j = 0; 4521 for ($i = $alreadyGenerated[Product::CUSTOMIZE_FILE]; $i < (int) ($this->getFieldValue($obj, 'uploadable_files')); $i++) { 4522 $customizableFieldIds[] = 'newLabel_'.Product::CUSTOMIZE_FILE.'_'.$j++; 4523 } 4524 $j = 0; 4525 for ($i = $alreadyGenerated[Product::CUSTOMIZE_TEXTFIELD]; $i < (int) ($this->getFieldValue($obj, 'text_fields')); $i++) { 4526 $customizableFieldIds[] = 'newLabel_'.Product::CUSTOMIZE_TEXTFIELD.'_'.$j++; 4527 } 4528 4529 return implode('¤', $customizableFieldIds); 4530 } 4531 4532 /** 4533 * @param $label 4534 * @param $languages 4535 * @param $defaultLanguage 4536 * @param $type 4537 * @param $fieldIds 4538 * @param $idCustomizationField 4539 * 4540 * @return string 4541 * 4542 * @since 1.0.0 4543 */ 4544 protected function _displayLabelField(&$label, $languages, $defaultLanguage, $type, $fieldIds, $idCustomizationField) 4545 { 4546 foreach ($languages as $language) { 4547 $inputValue[$language['id_lang']] = (isset($label[(int) ($language['id_lang'])])) ? $label[(int) ($language['id_lang'])]['name'] : ''; 4548 } 4549 4550 $required = (isset($label[(int) ($language['id_lang'])])) ? $label[(int) ($language['id_lang'])]['required'] : false; 4551 4552 $template = $this->context->smarty->createTemplate( 4553 'controllers/products/input_text_lang.tpl', 4554 $this->context->smarty 4555 ); 4556 4557 return '<div class="form-group">' 4558 .'<div class="col-lg-6">' 4559 .$template->assign( 4560 [ 4561 'languages' => $languages, 4562 'input_name' => 'label_'.$type.'_'.(int) ($idCustomizationField), 4563 'input_value' => $inputValue, 4564 ] 4565 )->fetch() 4566 .'</div>' 4567 .'<div class="col-lg-6">' 4568 .'<div class="checkbox">' 4569 .'<label for="require_'.$type.'_'.(int) ($idCustomizationField).'">' 4570 .'<input type="checkbox" name="require_'.$type.'_'.(int) ($idCustomizationField).'" id="require_'.$type.'_'.(int) ($idCustomizationField).'" value="1" '.($required ? 'checked="checked"' : '').'/>' 4571 .$this->l('Required') 4572 .'</label>' 4573 .'</div>' 4574 .'</div>' 4575 .'</div>'; 4576 } 4577 4578 public function initFormAttachments($obj) 4579 { 4580 if (!$this->default_form_language) { 4581 $this->getLanguages(); 4582 } 4583 4584 $data = $this->createTemplate($this->tpl_form); 4585 $data->assign('default_form_language', $this->default_form_language); 4586 4587 if ((bool) $obj->id) { 4588 if ($this->product_exists_in_shop) { 4589 $attachmentName = []; 4590 $attachmentDescription = []; 4591 foreach ($this->_languages as $language) { 4592 $attachmentName[$language['id_lang']] = ''; 4593 $attachmentDescription[$language['id_lang']] = ''; 4594 } 4595 4596 $isoTinyMce = $this->context->language->iso_code; 4597 $isoTinyMce = (file_exists(_PS_JS_DIR_.'tiny_mce/langs/'.$isoTinyMce.'.js') ? $isoTinyMce : 'en'); 4598 4599 $attachmentUploader = new HelperUploader('attachment_file'); 4600 $attachmentUploader 4601 ->setMultiple(false) 4602 ->setUseAjax(true) 4603 ->setUrl($this->context->link->getAdminLink('AdminProducts').'&ajax=1&id_product='.(int) $obj->id.'&action=AddAttachment') 4604 ->setPostMaxSize((Configuration::get('PS_ATTACHMENT_MAXIMUM_SIZE') * 1024 * 1024)) 4605 ->setTemplate('attachment_ajax.tpl'); 4606 4607 $data->assign( 4608 [ 4609 'obj' => $obj, 4610 'table' => $this->table, 4611 'ad' => __PS_BASE_URI__.basename(_PS_ADMIN_DIR_), 4612 'iso_tiny_mce' => $isoTinyMce, 4613 'languages' => $this->_languages, 4614 'id_lang' => $this->context->language->id, 4615 'attach1' => Attachment::getAttachments($this->context->language->id, $obj->id, true), 4616 'attach2' => Attachment::getAttachments($this->context->language->id, $obj->id, false), 4617 'default_form_language' => (int) Configuration::get('PS_LANG_DEFAULT'), 4618 'attachment_name' => $attachmentName, 4619 'attachment_description' => $attachmentDescription, 4620 'attachment_uploader' => $attachmentUploader->render(), 4621 ] 4622 ); 4623 } else { 4624 $this->displayWarning($this->l('You must save the product in this shop before adding attachements.')); 4625 } 4626 } else { 4627 $this->displayWarning($this->l('You must save this product before adding attachements.')); 4628 } 4629 4630 $this->tpl_form_vars['custom_form'] = $data->fetch(); 4631 } 4632 4633 /** 4634 * @param Product $product 4635 * 4636 * @throws Exception 4637 * @throws SmartyException 4638 * 4639 * @since 1.0.0 4640 */ 4641 public function initFormInformations($product) 4642 { 4643 if (!$this->default_form_language) { 4644 $this->getLanguages(); 4645 } 4646 4647 $data = $this->createTemplate($this->tpl_form); 4648 4649 $currency = $this->context->currency; 4650 4651 $data->assign( 4652 [ 4653 'languages' => $this->_languages, 4654 'default_form_language' => $this->default_form_language, 4655 'currency' => $currency, 4656 ] 4657 ); 4658 $this->object = $product; 4659 //$this->display = 'edit'; 4660 $data->assign('product_name_redirected', Product::getProductName((int) $product->id_product_redirected, null, (int) $this->context->language->id)); 4661 /* 4662 * Form for adding a virtual product like software, mp3, etc... 4663 */ 4664 $productDownload = new ProductDownload(); 4665 if ($idProductDownload = $productDownload->getIdFromIdProduct($this->getFieldValue($product, 'id'))) { 4666 $productDownload = new ProductDownload($idProductDownload); 4667 } 4668 4669 $product->{'productDownload'} = $productDownload; 4670 4671 $productProps = []; 4672 // global informations 4673 array_push($productProps, 'reference', 'ean13', 'upc', 'available_for_order', 'show_price', 'online_only', 'id_manufacturer'); 4674 4675 // specific / detailled information 4676 array_push( 4677 $productProps, 4678 // physical product 4679 'width', 'height', 'weight', 'active', 4680 // virtual product 4681 'is_virtual', 'cache_default_attribute', 4682 // customization 4683 'uploadable_files', 'text_fields' 4684 ); 4685 // prices 4686 array_push( 4687 $productProps, 4688 'price', 4689 'wholesale_price', 4690 'id_tax_rules_group', 4691 'unit_price_ratio', 4692 'on_sale', 4693 'unity', 4694 'minimum_quantity', 4695 'additional_shipping_cost', 4696 'available_now', 4697 'available_later', 4698 'available_date' 4699 ); 4700 4701 if (Configuration::get('PS_USE_ECOTAX')) { 4702 array_push($productProps, 'ecotax'); 4703 } 4704 4705 foreach ($productProps as $prop) { 4706 $product->$prop = $this->getFieldValue($product, $prop); 4707 } 4708 4709 $product->name['class'] = 'updateCurrentText'; 4710 if (!$product->id || Configuration::get('PS_FORCE_FRIENDLY_PRODUCT')) { 4711 $product->name['class'] .= ' copy2friendlyUrl'; 4712 } 4713 4714 $images = Image::getImages($this->context->language->id, $product->id); 4715 4716 if (is_array($images)) { 4717 foreach ($images as $k => $image) { 4718 $images[$k]['src'] = $this->context->link->getImageLink($product->link_rewrite[$this->context->language->id], $product->id.'-'.$image['id_image'], ImageType::getFormatedName('small')); 4719 } 4720 $data->assign('images', $images); 4721 } 4722 $data->assign('imagesTypes', ImageType::getImagesTypes('products')); 4723 4724 $product->tags = Tag::getProductTags($product->id); 4725 4726 $data->assign('product_type', (int) Tools::getValue('type_product', $product->getType())); 4727 $data->assign('is_in_pack', (int) Pack::isPacked($product->id)); 4728 4729 $checkProductAssociationAjax = false; 4730 if (Shop::isFeatureActive() && Shop::getContext() != Shop::CONTEXT_ALL) { 4731 $checkProductAssociationAjax = true; 4732 } 4733 4734 // TinyMCE 4735 $isoTinyMce = $this->context->language->iso_code; 4736 $isoTinyMce = (file_exists(_PS_ROOT_DIR_.'/js/tiny_mce/langs/'.$isoTinyMce.'.js') ? $isoTinyMce : 'en'); 4737 $data->assign( 4738 [ 4739 'ad' => dirname($_SERVER['PHP_SELF']), 4740 'iso_tiny_mce' => $isoTinyMce, 4741 'check_product_association_ajax' => $checkProductAssociationAjax, 4742 'id_lang' => $this->context->language->id, 4743 'product' => $product, 4744 'token' => $this->token, 4745 'currency' => $currency, 4746 'link' => $this->context->link, 4747 'PS_PRODUCT_SHORT_DESC_LIMIT' => Configuration::get('PS_PRODUCT_SHORT_DESC_LIMIT') ? Configuration::get('PS_PRODUCT_SHORT_DESC_LIMIT') : 400, 4748 ] 4749 ); 4750 $data->assign($this->tpl_form_vars); 4751 4752 $this->tpl_form_vars['product'] = $product; 4753 $this->tpl_form_vars['custom_form'] = $data->fetch(); 4754 } 4755 4756 /** 4757 * Initialize shipping form 4758 * 4759 * @param $obj 4760 * 4761 * @since 1.0.0 4762 */ 4763 public function initFormShipping($obj) 4764 { 4765 $data = $this->createTemplate($this->tpl_form); 4766 $data->assign( 4767 [ 4768 'product' => $obj, 4769 'ps_dimension_unit' => Configuration::get('PS_DIMENSION_UNIT'), 4770 'ps_weight_unit' => Configuration::get('PS_WEIGHT_UNIT'), 4771 'carrier_list' => $this->getCarrierList(), 4772 'currency' => $this->context->currency, 4773 'country_display_tax_label' => $this->context->country->display_tax_label, 4774 ] 4775 ); 4776 $this->tpl_form_vars['custom_form'] = $data->fetch(); 4777 } 4778 4779 /** 4780 * Get carrier list 4781 * 4782 * @return array 4783 * 4784 * @since 1.0.0 4785 */ 4786 protected function getCarrierList() 4787 { 4788 $carrierList = Carrier::getCarriers($this->context->language->id, false, false, false, null, Carrier::ALL_CARRIERS); 4789 4790 if ($product = $this->loadObject(true)) { 4791 /** @var Product $product */ 4792 $carrierSelectedList = $product->getCarriers(); 4793 foreach ($carrierList as &$carrier) { 4794 foreach ($carrierSelectedList as $carrierSelected) { 4795 if ($carrierSelected['id_reference'] == $carrier['id_reference']) { 4796 $carrier['selected'] = true; 4797 continue; 4798 } 4799 } 4800 } 4801 } 4802 4803 return $carrierList; 4804 } 4805 4806 /** 4807 * Ajax process add product image 4808 * 4809 * @since 1.0.0 4810 */ 4811 public function ajaxProcessAddProductImage() 4812 { 4813 static::$currentIndex = 'index.php?tab=AdminProducts'; 4814 $product = new Product((int) Tools::getValue('id_product')); 4815 $legends = Tools::getValue('legend'); 4816 4817 if (!is_array($legends)) { 4818 $legends = (array) $legends; 4819 } 4820 4821 if (!Validate::isLoadedObject($product)) { 4822 $files = []; 4823 $files[0]['error'] = Tools::displayError('Cannot add image because product creation failed.'); 4824 } 4825 4826 $imageUploader = new HelperImageUploader('file'); 4827 $imageUploader->setAcceptTypes(['jpeg', 'gif', 'png', 'jpg'])->setMaxSize($this->max_image_size); 4828 $files = $imageUploader->process(); 4829 4830 foreach ($files as &$file) { 4831 $image = new Image(); 4832 $image->id_product = (int) ($product->id); 4833 $image->position = Image::getHighestPosition($product->id) + 1; 4834 4835 foreach ($legends as $key => $legend) { 4836 if (!empty($legend)) { 4837 $image->legend[(int) $key] = $legend; 4838 } 4839 } 4840 4841 if (!Image::getCover($image->id_product)) { 4842 $image->cover = 1; 4843 } else { 4844 $image->cover = 0; 4845 } 4846 4847 if (($validate = $image->validateFieldsLang(false, true)) !== true) { 4848 $file['error'] = Tools::displayError($validate); 4849 } 4850 4851 if (isset($file['error']) && (!is_numeric($file['error']) || $file['error'] != 0)) { 4852 continue; 4853 } 4854 4855 if (!$image->add()) { 4856 $file['error'] = Tools::displayError('Error while creating additional image'); 4857 } else { 4858 if (!$newPath = $image->getPathForCreation()) { 4859 $file['error'] = Tools::displayError('An error occurred during new folder creation'); 4860 continue; 4861 } 4862 4863 $error = 0; 4864 4865 if (!ImageManager::resize($file['save_path'], $newPath.'.'.$image->image_format, null, null, 'jpg', false, $error)) { 4866 switch ($error) { 4867 case ImageManager::ERROR_FILE_NOT_EXIST: 4868 $file['error'] = Tools::displayError('An error occurred while copying image, the file does not exist anymore.'); 4869 break; 4870 4871 case ImageManager::ERROR_FILE_WIDTH: 4872 $file['error'] = Tools::displayError('An error occurred while copying image, the file width is 0px.'); 4873 break; 4874 4875 case ImageManager::ERROR_MEMORY_LIMIT: 4876 $file['error'] = Tools::displayError('An error occurred while copying image, check your memory limit.'); 4877 break; 4878 4879 default: 4880 $file['error'] = Tools::displayError('An error occurred while copying image.'); 4881 break; 4882 } 4883 continue; 4884 } else { 4885 $imagesTypes = ImageType::getImagesTypes('products'); 4886 $generateHighDpiImages = (bool) Configuration::get('PS_HIGHT_DPI'); 4887 4888 foreach ($imagesTypes as $imageType) { 4889 if (!ImageManager::resize($file['save_path'], $newPath.'-'.stripslashes($imageType['name']).'.'.$image->image_format, $imageType['width'], $imageType['height'], $image->image_format)) { 4890 $file['error'] = Tools::displayError('An error occurred while copying image:').' '.stripslashes($imageType['name']); 4891 continue; 4892 } 4893 4894 if ($generateHighDpiImages) { 4895 if (!ImageManager::resize($file['save_path'], $newPath.'-'.stripslashes($imageType['name']).'2x.'.$image->image_format, (int) $imageType['width'] * 2, (int) $imageType['height'] * 2, $image->image_format)) { 4896 $file['error'] = Tools::displayError('An error occurred while copying image:').' '.stripslashes($imageType['name']); 4897 continue; 4898 } 4899 } 4900 } 4901 } 4902 4903 unlink($file['save_path']); 4904 //Necesary to prevent hacking 4905 unset($file['save_path']); 4906 Hook::exec('actionWatermark', ['id_image' => $image->id, 'id_product' => $product->id]); 4907 4908 if (!$image->update()) { 4909 $file['error'] = Tools::displayError('Error while updating status'); 4910 continue; 4911 } 4912 4913 // Associate image to shop from context 4914 $shops = Shop::getContextListShopID(); 4915 $image->associateTo($shops); 4916 $jsonShops = []; 4917 4918 foreach ($shops as $id_shop) { 4919 $jsonShops[$id_shop] = true; 4920 } 4921 4922 $file['status'] = 'ok'; 4923 $file['id'] = $image->id; 4924 $file['position'] = $image->position; 4925 $file['cover'] = $image->cover; 4926 $file['legend'] = $image->legend; 4927 $file['path'] = $image->getExistingImgPath(); 4928 $file['shops'] = $jsonShops; 4929 4930 @unlink(_PS_TMP_IMG_DIR_.'product_'.(int) $product->id.'.jpg'); 4931 @unlink(_PS_TMP_IMG_DIR_.'product_mini_'.(int) $product->id.'_'.$this->context->shop->id.'.jpg'); 4932 } 4933 } 4934 4935 $this->ajaxDie(json_encode([$imageUploader->getName() => $files])); 4936 } 4937 4938 /** 4939 * @param Product $obj 4940 * 4941 * @throws Exception 4942 * @throws SmartyException 4943 * 4944 * @since 1.0.0 4945 */ 4946 public function initFormImages($obj) 4947 { 4948 $data = $this->createTemplate($this->tpl_form); 4949 4950 if ((bool) $obj->id) { 4951 if ($this->product_exists_in_shop) { 4952 $data->assign('product', $this->loadObject()); 4953 4954 $shops = false; 4955 if (Shop::isFeatureActive()) { 4956 $shops = Shop::getShops(); 4957 } 4958 4959 if ($shops) { 4960 foreach ($shops as $key => $shop) { 4961 if (!$obj->isAssociatedToShop($shop['id_shop'])) { 4962 unset($shops[$key]); 4963 } 4964 } 4965 } 4966 4967 $data->assign('shops', $shops); 4968 4969 $images = Image::getImages(null, $obj->id); 4970 foreach ($images as $k => $image) { 4971 $images[$k] = new Image($image['id_image']); 4972 } 4973 4974 if ($this->context->shop->getContext() == Shop::CONTEXT_SHOP) { 4975 $currentShopId = (int) $this->context->shop->id; 4976 } else { 4977 $currentShopId = 0; 4978 } 4979 4980 $languages = Language::getLanguages(true); 4981 $imageUploader = new HelperImageUploader('file'); 4982 $imageUploader->setMultiple(!(Tools::getUserBrowser() == 'Apple Safari' && Tools::getUserPlatform() == 'Windows')) 4983 ->setUseAjax(true)->setUrl($this->context->link->getAdminLink('AdminProducts').'&ajax=1&id_product='.(int) $obj->id.'&action=addProductImage'); 4984 4985 $data->assign( 4986 [ 4987 'countImages' => count($images), 4988 'id_product' => (int) Tools::getValue('id_product'), 4989 'id_category_default' => (int) $this->_category->id, 4990 'images' => $images, 4991 'iso_lang' => $languages[0]['iso_code'], 4992 'token' => $this->token, 4993 'table' => $this->table, 4994 'max_image_size' => $this->max_image_size / 1024 / 1024, 4995 'currency' => $this->context->currency, 4996 'current_shop_id' => $currentShopId, 4997 'languages' => $this->_languages, 4998 'default_language' => (int) Configuration::get('PS_LANG_DEFAULT'), 4999 'image_uploader' => $imageUploader->render(), 5000 ] 5001 ); 5002 5003 $type = ImageType::getByNameNType('%', 'products', 'height'); 5004 if (isset($type['name'])) { 5005 $data->assign('imageType', $type['name']); 5006 } else { 5007 $data->assign('imageType', ImageType::getFormatedName('small')); 5008 } 5009 } else { 5010 $this->displayWarning($this->l('You must save the product in this shop before adding images.')); 5011 } 5012 } else { 5013 $this->displayWarning($this->l('You must save this product before adding images.')); 5014 } 5015 5016 $this->tpl_form_vars['custom_form'] = $data->fetch(); 5017 } 5018 5019 /** 5020 * Initialize combinations form 5021 * 5022 * @param $obj 5023 * 5024 * @since 1.0.0 5025 */ 5026 public function initFormCombinations($obj) 5027 { 5028 return $this->initFormAttributes($obj); 5029 } 5030 5031 /** 5032 * @param Product $product 5033 * 5034 * @throws Exception 5035 * @throws SmartyException 5036 */ 5037 public function initFormAttributes($product) 5038 { 5039 $data = $this->createTemplate($this->tpl_form); 5040 if (!Combination::isFeatureActive()) { 5041 $this->displayWarning( 5042 $this->l('This feature has been disabled. '). 5043 ' <a href="index.php?tab=AdminPerformance&token='.Tools::getAdminTokenLite('AdminPerformance').'#featuresDetachables">'.$this->l('Performances').'</a>' 5044 ); 5045 } elseif (Validate::isLoadedObject($product)) { 5046 if ($this->product_exists_in_shop) { 5047 if ($product->is_virtual) { 5048 $data->assign('product', $product); 5049 $this->displayWarning($this->l('A virtual product cannot have combinations.')); 5050 } else { 5051 $attributeJs = []; 5052 $attributes = Attribute::getAttributes($this->context->language->id, true); 5053 foreach ($attributes as $k => $attribute) { 5054 $attributeJs[$attribute['id_attribute_group']][$attribute['id_attribute']] = $attribute['name']; 5055 } 5056 foreach ($attributeJs as $k => $ajs) { 5057 natsort($attributeJs[$k]); 5058 } 5059 5060 $currency = $this->context->currency; 5061 5062 $data->assign('attributeJs', $attributeJs); 5063 $data->assign('attributes_groups', AttributeGroup::getAttributesGroups($this->context->language->id)); 5064 5065 $data->assign('currency', $currency); 5066 5067 $images = Image::getImages($this->context->language->id, $product->id); 5068 5069 $data->assign('tax_exclude_option', Tax::excludeTaxeOption()); 5070 $data->assign('ps_weight_unit', Configuration::get('PS_WEIGHT_UNIT')); 5071 5072 $data->assign('ps_use_ecotax', Configuration::get('PS_USE_ECOTAX')); 5073 $data->assign('field_value_unity', $this->getFieldValue($product, 'unity')); 5074 5075 $data->assign('reasons', $reasons = StockMvtReason::getStockMvtReasons($this->context->language->id)); 5076 $data->assign('ps_stock_mvt_reason_default', $psStockMvtReasonDefault = Configuration::get('PS_STOCK_MVT_REASON_DEFAULT')); 5077 $data->assign('minimal_quantity', $this->getFieldValue($product, 'minimal_quantity') ? $this->getFieldValue($product, 'minimal_quantity') : 1); 5078 $data->assign('available_date', ($this->getFieldValue($product, 'available_date') != 0) ? stripslashes(htmlentities($this->getFieldValue($product, 'available_date'), $this->context->language->id)) : '0000-00-00'); 5079 5080 $i = 0; 5081 $type = ImageType::getByNameNType('%', 'products', 'height'); 5082 if (isset($type['name'])) { 5083 $data->assign('imageType', $type['name']); 5084 } else { 5085 $data->assign('imageType', ImageType::getFormatedName('small')); 5086 } 5087 $data->assign('imageWidth', (isset($imageType['width']) ? (int) ($imageType['width']) : 64) + 25); 5088 foreach ($images as $k => $image) { 5089 $images[$k]['obj'] = new Image($image['id_image']); 5090 ++$i; 5091 } 5092 $data->assign('images', $images); 5093 5094 $data->assign($this->tpl_form_vars); 5095 $data->assign( 5096 [ 5097 'list' => $this->renderListAttributes($product, $currency), 5098 'product' => $product, 5099 'id_category' => $product->getDefaultCategory(), 5100 'token_generator' => Tools::getAdminTokenLite('AdminAttributeGenerator'), 5101 'combination_exists' => (Shop::isFeatureActive() && (Shop::getContextShopGroup()->share_stock) && count(AttributeGroup::getAttributesGroups($this->context->language->id)) > 0 && $product->hasAttributes()), 5102 ] 5103 ); 5104 } 5105 } else { 5106 $this->displayWarning($this->l('You must save the product in this shop before adding combinations.')); 5107 } 5108 } else { 5109 $data->assign('product', $product); 5110 $this->displayWarning($this->l('You must save this product before adding combinations.')); 5111 } 5112 5113 $this->tpl_form_vars['custom_form'] = $data->fetch(); 5114 } 5115 5116 /** 5117 * @param Product $product 5118 * @param Currency|array|int $currency 5119 * 5120 * @return string 5121 * 5122 * @since 1.0.0 5123 */ 5124 public function renderListAttributes($product, $currency) 5125 { 5126 $this->bulk_actions = ['delete' => ['text' => $this->l('Delete selected'), 'confirm' => $this->l('Delete selected items?')]]; 5127 $this->addRowAction('edit'); 5128 $this->addRowAction('default'); 5129 $this->addRowAction('delete'); 5130 5131 $defaultClass = 'highlighted'; 5132 5133 $this->fields_list = [ 5134 'attributes' => ['title' => $this->l('Attribute - value pair'), 'align' => 'left'], 5135 'price' => ['title' => $this->l('Impact on price'), 'type' => 'price', 'align' => 'left'], 5136 'weight' => ['title' => $this->l('Impact on weight'), 'align' => 'left'], 5137 'reference' => ['title' => $this->l('Reference'), 'align' => 'left'], 5138 'ean13' => ['title' => $this->l('EAN-13'), 'align' => 'left'], 5139 'upc' => ['title' => $this->l('UPC'), 'align' => 'left'], 5140 ]; 5141 5142 if ($product->id) { 5143 /* Build attributes combinations */ 5144 $combinations = $product->getAttributeCombinations($this->context->language->id); 5145 $groups = []; 5146 $combArray = []; 5147 if (is_array($combinations)) { 5148 $combinationImages = $product->getCombinationImages($this->context->language->id); 5149 foreach ($combinations as $k => $combination) { 5150 $priceToConvert = Tools::convertPrice($combination['price'], $currency); 5151 $price = Tools::displayPrice($priceToConvert, $currency); 5152 5153 $combArray[$combination['id_product_attribute']]['id_product_attribute'] = $combination['id_product_attribute']; 5154 $combArray[$combination['id_product_attribute']]['attributes'][] = [$combination['group_name'], $combination['attribute_name'], $combination['id_attribute']]; 5155 $combArray[$combination['id_product_attribute']]['wholesale_price'] = $combination['wholesale_price']; 5156 $combArray[$combination['id_product_attribute']]['price'] = $price; 5157 $combArray[$combination['id_product_attribute']]['weight'] = $combination['weight'].Configuration::get('PS_WEIGHT_UNIT'); 5158 $combArray[$combination['id_product_attribute']]['unit_impact'] = $combination['unit_price_impact']; 5159 $combArray[$combination['id_product_attribute']]['reference'] = $combination['reference']; 5160 $combArray[$combination['id_product_attribute']]['ean13'] = $combination['ean13']; 5161 $combArray[$combination['id_product_attribute']]['upc'] = $combination['upc']; 5162 $combArray[$combination['id_product_attribute']]['id_image'] = isset($combinationImages[$combination['id_product_attribute']][0]['id_image']) ? $combinationImages[$combination['id_product_attribute']][0]['id_image'] : 0; 5163 $combArray[$combination['id_product_attribute']]['available_date'] = strftime($combination['available_date']); 5164 $combArray[$combination['id_product_attribute']]['default_on'] = $combination['default_on']; 5165 if ($combination['is_color_group']) { 5166 $groups[$combination['id_attribute_group']] = $combination['group_name']; 5167 } 5168 } 5169 } 5170 5171 if (isset($combArray)) { 5172 foreach ($combArray as $id_product_attribute => $product_attribute) { 5173 $list = ''; 5174 5175 /* In order to keep the same attributes order */ 5176 asort($product_attribute['attributes']); 5177 5178 foreach ($product_attribute['attributes'] as $attribute) { 5179 $list .= $attribute[0].' - '.$attribute[1].', '; 5180 } 5181 5182 $list = rtrim($list, ', '); 5183 $combArray[$id_product_attribute]['image'] = $product_attribute['id_image'] ? new Image($product_attribute['id_image']) : false; 5184 $combArray[$id_product_attribute]['available_date'] = $product_attribute['available_date'] != 0 ? date('Y-m-d', strtotime($product_attribute['available_date'])) : '0000-00-00'; 5185 $combArray[$id_product_attribute]['attributes'] = $list; 5186 $combArray[$id_product_attribute]['name'] = $list; 5187 5188 if ($product_attribute['default_on']) { 5189 $combArray[$id_product_attribute]['class'] = $defaultClass; 5190 } 5191 } 5192 } 5193 } 5194 5195 foreach ($this->actions_available as $action) { 5196 if (!in_array($action, $this->actions) && isset($this->$action) && $this->$action) { 5197 $this->actions[] = $action; 5198 } 5199 } 5200 5201 $helper = new HelperList(); 5202 $helper->identifier = 'id_product_attribute'; 5203 $helper->table_id = 'combinations-list'; 5204 $helper->token = $this->token; 5205 $helper->currentIndex = static::$currentIndex; 5206 $helper->no_link = true; 5207 $helper->simple_header = true; 5208 $helper->show_toolbar = false; 5209 $helper->shopLinkType = $this->shopLinkType; 5210 $helper->actions = $this->actions; 5211 $helper->list_skip_actions = $this->list_skip_actions; 5212 $helper->colorOnBackground = true; 5213 $helper->override_folder = $this->tpl_folder.'combination/'; 5214 5215 return $helper->generateList($combArray, $this->fields_list); 5216 } 5217 5218 /** 5219 * @param Product $obj 5220 * 5221 * @throws Exception 5222 * @throws SmartyException 5223 * 5224 * @since 1.0.0 5225 */ 5226 public function initFormQuantities($obj) 5227 { 5228 if (!$this->default_form_language) { 5229 $this->getLanguages(); 5230 } 5231 5232 $data = $this->createTemplate($this->tpl_form); 5233 $data->assign('default_form_language', $this->default_form_language); 5234 5235 if ($obj->id) { 5236 if ($this->product_exists_in_shop) { 5237 // Get all id_product_attribute 5238 $attributes = $obj->getAttributesResume($this->context->language->id); 5239 if (empty($attributes)) { 5240 $attributes[] = [ 5241 'id_product_attribute' => 0, 5242 'attribute_designation' => '', 5243 ]; 5244 } 5245 5246 // Get available quantities 5247 $available_quantity = []; 5248 $product_designation = []; 5249 5250 foreach ($attributes as $attribute) { 5251 // Get available quantity for the current product attribute in the current shop 5252 $available_quantity[$attribute['id_product_attribute']] = isset($attribute['id_product_attribute']) && $attribute['id_product_attribute'] ? (int) $attribute['quantity'] : (int) $obj->quantity; 5253 // Get all product designation 5254 $product_designation[$attribute['id_product_attribute']] = rtrim( 5255 $obj->name[$this->context->language->id].' - '.$attribute['attribute_designation'], 5256 ' - ' 5257 ); 5258 } 5259 5260 $show_quantities = true; 5261 $shop_context = Shop::getContext(); 5262 $shop_group = new ShopGroup((int) Shop::getContextShopGroupID()); 5263 5264 // if we are in all shops context, it's not possible to manage quantities at this level 5265 if (Shop::isFeatureActive() && $shop_context == Shop::CONTEXT_ALL) { 5266 $show_quantities = false; 5267 } // if we are in group shop context 5268 elseif (Shop::isFeatureActive() && $shop_context == Shop::CONTEXT_GROUP) { 5269 // if quantities are not shared between shops of the group, it's not possible to manage them at group level 5270 if (!$shop_group->share_stock) { 5271 $show_quantities = false; 5272 } 5273 } // if we are in shop context 5274 elseif (Shop::isFeatureActive()) { 5275 // if quantities are shared between shops of the group, it's not possible to manage them for a given shop 5276 if ($shop_group->share_stock) { 5277 $show_quantities = false; 5278 } 5279 } 5280 5281 $data->assign('ps_stock_management', Configuration::get('PS_STOCK_MANAGEMENT')); 5282 $data->assign('has_attribute', $obj->hasAttributes()); 5283 // Check if product has combination, to display the available date only for the product or for each combination 5284 if (Combination::isFeatureActive()) { 5285 $data->assign('countAttributes', (int) Db::getInstance(_PS_USE_SQL_SLAVE_)->getValue( 5286 (new DbQuery()) 5287 ->select('COUNT(`id_product`)') 5288 ->from('product_attribute') 5289 ->where('`id_product` = '.(int) $obj->id) 5290 )); 5291 } else { 5292 $data->assign('countAttributes', false); 5293 } 5294 // if advanced stock management is active, checks associations 5295 $advanced_stock_management_warning = false; 5296 if (Configuration::get('PS_ADVANCED_STOCK_MANAGEMENT') && $obj->advanced_stock_management) { 5297 $p_attributes = Product::getProductAttributesIds($obj->id); 5298 $warehouses = []; 5299 5300 if (!$p_attributes) { 5301 $warehouses[] = Warehouse::getProductWarehouseList($obj->id, 0); 5302 } 5303 5304 foreach ($p_attributes as $p_attribute) { 5305 $ws = Warehouse::getProductWarehouseList($obj->id, $p_attribute['id_product_attribute']); 5306 if ($ws) { 5307 $warehouses[] = $ws; 5308 } 5309 } 5310 $warehouses = Tools::arrayUnique($warehouses); 5311 5312 if (empty($warehouses)) { 5313 $advanced_stock_management_warning = true; 5314 } 5315 } 5316 if ($advanced_stock_management_warning) { 5317 $this->displayWarning($this->l('If you wish to use the advanced stock management, you must:')); 5318 $this->displayWarning('- '.$this->l('associate your products with warehouses.')); 5319 $this->displayWarning('- '.$this->l('associate your warehouses with carriers.')); 5320 $this->displayWarning('- '.$this->l('associate your warehouses with the appropriate shops.')); 5321 } 5322 5323 $pack_quantity = null; 5324 // if product is a pack 5325 if (Pack::isPack($obj->id)) { 5326 $items = Pack::getItems((int) $obj->id, Configuration::get('PS_LANG_DEFAULT')); 5327 5328 // gets an array of quantities (quantity for the product / quantity in pack) 5329 $pack_quantities = []; 5330 foreach ($items as $item) { 5331 /** @var Product $item */ 5332 if (!$item->isAvailableWhenOutOfStock((int) $item->out_of_stock)) { 5333 $pack_id_product_attribute = Product::getDefaultAttribute($item->id, 1); 5334 $pack_quantities[] = Product::getQuantity($item->id, $pack_id_product_attribute) / ($item->pack_quantity !== 0 ? $item->pack_quantity : 1); 5335 } 5336 } 5337 5338 // gets the minimum 5339 if (count($pack_quantities)) { 5340 $pack_quantity = $pack_quantities[0]; 5341 foreach ($pack_quantities as $value) { 5342 if ($pack_quantity > $value) { 5343 $pack_quantity = $value; 5344 } 5345 } 5346 } 5347 5348 if (Configuration::get('PS_ADVANCED_STOCK_MANAGEMENT') && !Warehouse::getPackWarehouses((int) $obj->id)) { 5349 $this->displayWarning($this->l('You must have a common warehouse between this pack and its product.')); 5350 } 5351 } 5352 5353 $data->assign( 5354 [ 5355 'attributes' => $attributes, 5356 'available_quantity' => $available_quantity, 5357 'pack_quantity' => $pack_quantity, 5358 'stock_management_active' => Configuration::get('PS_ADVANCED_STOCK_MANAGEMENT'), 5359 'product_designation' => $product_designation, 5360 'product' => $obj, 5361 'show_quantities' => $show_quantities, 5362 'order_out_of_stock' => Configuration::get('PS_ORDER_OUT_OF_STOCK'), 5363 'pack_stock_type' => Configuration::get('PS_PACK_STOCK_TYPE'), 5364 'token_preferences' => Tools::getAdminTokenLite('AdminPPreferences'), 5365 'token' => $this->token, 5366 'languages' => $this->_languages, 5367 'id_lang' => $this->context->language->id, 5368 ] 5369 ); 5370 } else { 5371 $this->displayWarning($this->l('You must save the product in this shop before managing quantities.')); 5372 } 5373 } else { 5374 $this->displayWarning($this->l('You must save this product before managing quantities.')); 5375 } 5376 5377 $this->tpl_form_vars['custom_form'] = $data->fetch(); 5378 } 5379 5380 /** 5381 * @param Product $obj 5382 * 5383 * @throws Exception 5384 * @throws SmartyException 5385 * 5386 * @since 1.0.0 5387 */ 5388 public function initFormSuppliers($obj) 5389 { 5390 $data = $this->createTemplate($this->tpl_form); 5391 5392 if ($obj->id) { 5393 if ($this->product_exists_in_shop) { 5394 // Get all id_product_attribute 5395 $attributes = $obj->getAttributesResume($this->context->language->id); 5396 if (empty($attributes)) { 5397 $attributes[] = [ 5398 'id_product' => $obj->id, 5399 'id_product_attribute' => 0, 5400 'attribute_designation' => '', 5401 ]; 5402 } 5403 5404 $product_designation = []; 5405 5406 foreach ($attributes as $attribute) { 5407 $product_designation[$attribute['id_product_attribute']] = rtrim( 5408 $obj->name[$this->context->language->id].' - '.$attribute['attribute_designation'], 5409 ' - ' 5410 ); 5411 } 5412 5413 // Get all available suppliers 5414 $suppliers = Supplier::getSuppliers(); 5415 5416 // Get already associated suppliers 5417 $associated_suppliers = ProductSupplier::getSupplierCollection($obj->id); 5418 5419 // Get already associated suppliers and force to retreive product declinaisons 5420 $product_supplier_collection = ProductSupplier::getSupplierCollection($obj->id, false); 5421 5422 $default_supplier = 0; 5423 5424 foreach ($suppliers as &$supplier) { 5425 $supplier['is_selected'] = false; 5426 $supplier['is_default'] = false; 5427 5428 foreach ($associated_suppliers as $associated_supplier) { 5429 /** @var ProductSupplier $associated_supplier */ 5430 if ($associated_supplier->id_supplier == $supplier['id_supplier']) { 5431 $associated_supplier->name = $supplier['name']; 5432 $supplier['is_selected'] = true; 5433 5434 if ($obj->id_supplier == $supplier['id_supplier']) { 5435 $supplier['is_default'] = true; 5436 $default_supplier = $supplier['id_supplier']; 5437 } 5438 } 5439 } 5440 } 5441 5442 $data->assign( 5443 [ 5444 'attributes' => $attributes, 5445 'suppliers' => $suppliers, 5446 'default_supplier' => $default_supplier, 5447 'associated_suppliers' => $associated_suppliers, 5448 'associated_suppliers_collection' => $product_supplier_collection, 5449 'product_designation' => $product_designation, 5450 'currencies' => Currency::getCurrencies(false, true, true), 5451 'product' => $obj, 5452 'link' => $this->context->link, 5453 'token' => $this->token, 5454 'id_default_currency' => Configuration::get('PS_CURRENCY_DEFAULT'), 5455 ] 5456 ); 5457 } else { 5458 $this->displayWarning($this->l('You must save the product in this shop before managing suppliers.')); 5459 } 5460 } else { 5461 $this->displayWarning($this->l('You must save this product before managing suppliers.')); 5462 } 5463 5464 $this->tpl_form_vars['custom_form'] = $data->fetch(); 5465 } 5466 5467 /** 5468 * @param Product $obj 5469 * 5470 * @throws Exception 5471 * @throws SmartyException 5472 * 5473 * @since 1.0.0 5474 */ 5475 public function initFormWarehouses($obj) 5476 { 5477 $data = $this->createTemplate($this->tpl_form); 5478 5479 if ($obj->id) { 5480 if ($this->product_exists_in_shop) { 5481 // Get all id_product_attribute 5482 $attributes = $obj->getAttributesResume($this->context->language->id); 5483 if (empty($attributes)) { 5484 $attributes[] = [ 5485 'id_product' => $obj->id, 5486 'id_product_attribute' => 0, 5487 'attribute_designation' => '', 5488 ]; 5489 } 5490 5491 $product_designation = []; 5492 5493 foreach ($attributes as $attribute) { 5494 $product_designation[$attribute['id_product_attribute']] = rtrim( 5495 $obj->name[$this->context->language->id].' - '.$attribute['attribute_designation'], 5496 ' - ' 5497 ); 5498 } 5499 5500 // Get all available warehouses 5501 $warehouses = Warehouse::getWarehouses(true); 5502 5503 // Get already associated warehouses 5504 $associated_warehouses_collection = WarehouseProductLocation::getCollection($obj->id); 5505 5506 $data->assign( 5507 [ 5508 'attributes' => $attributes, 5509 'warehouses' => $warehouses, 5510 'associated_warehouses' => $associated_warehouses_collection, 5511 'product_designation' => $product_designation, 5512 'product' => $obj, 5513 'link' => $this->context->link, 5514 'token' => $this->token, 5515 ] 5516 ); 5517 } else { 5518 $this->displayWarning($this->l('You must save the product in this shop before managing warehouses.')); 5519 } 5520 } else { 5521 $this->displayWarning($this->l('You must save this product before managing warehouses.')); 5522 } 5523 5524 $this->tpl_form_vars['custom_form'] = $data->fetch(); 5525 } 5526 5527 /** 5528 * @param Product $obj 5529 * 5530 * @throws Exception 5531 * @throws SmartyException 5532 * 5533 * @since 1.0.0 5534 */ 5535 public function initFormFeatures($obj) 5536 { 5537 if (!$this->default_form_language) { 5538 $this->getLanguages(); 5539 } 5540 5541 $data = $this->createTemplate($this->tpl_form); 5542 $data->assign('default_form_language', $this->default_form_language); 5543 $data->assign('languages', $this->_languages); 5544 5545 if (!Feature::isFeatureActive()) { 5546 $this->displayWarning($this->l('This feature has been disabled. ').' <a href="index.php?tab=AdminPerformance&token='.Tools::getAdminTokenLite('AdminPerformance').'#featuresDetachables">'.$this->l('Performances').'</a>'); 5547 } else { 5548 if ($obj->id) { 5549 if ($this->product_exists_in_shop) { 5550 $features = Feature::getFeatures($this->context->language->id, (Shop::isFeatureActive() && Shop::getContext() == Shop::CONTEXT_SHOP)); 5551 5552 foreach ($features as $k => $tab_features) { 5553 $features[$k]['current_item'] = false; 5554 $features[$k]['val'] = []; 5555 5556 $custom = true; 5557 foreach ($obj->getFeatures() as $tab_products) { 5558 if ($tab_products['id_feature'] == $tab_features['id_feature']) { 5559 $features[$k]['current_item'] = $tab_products['id_feature_value']; 5560 } 5561 } 5562 5563 $features[$k]['featureValues'] = FeatureValue::getFeatureValuesWithLang($this->context->language->id, (int) $tab_features['id_feature']); 5564 if (count($features[$k]['featureValues'])) { 5565 foreach ($features[$k]['featureValues'] as $value) { 5566 if ($features[$k]['current_item'] == $value['id_feature_value']) { 5567 $custom = false; 5568 } 5569 } 5570 } 5571 5572 if ($custom) { 5573 $feature_values_lang = FeatureValue::getFeatureValueLang($features[$k]['current_item']); 5574 foreach ($feature_values_lang as $feature_value) { 5575 $features[$k]['val'][$feature_value['id_lang']] = $feature_value; 5576 } 5577 } 5578 } 5579 5580 $data->assign('available_features', $features); 5581 $data->assign('product', $obj); 5582 $data->assign('link', $this->context->link); 5583 $data->assign('default_form_language', $this->default_form_language); 5584 } else { 5585 $this->displayWarning($this->l('You must save the product in this shop before adding features.')); 5586 } 5587 } else { 5588 $this->displayWarning($this->l('You must save this product before adding features.')); 5589 } 5590 } 5591 $this->tpl_form_vars['custom_form'] = $data->fetch(); 5592 } 5593 5594 /** 5595 * Ajax process product quantity 5596 * 5597 * @return string|void 5598 * 5599 * @since 1.0.0 5600 */ 5601 public function ajaxProcessProductQuantity() 5602 { 5603 if ($this->tabAccess['edit'] === '0') { 5604 $this->ajaxDie(json_encode(['error' => $this->l('You do not have the right permission')])); 5605 } 5606 if (!Tools::getValue('actionQty')) { 5607 $this->ajaxDie(json_encode(['error' => $this->l('Undefined action')])); 5608 } 5609 5610 $product = new Product((int) Tools::getValue('id_product'), true); 5611 switch (Tools::getValue('actionQty')) { 5612 case 'depends_on_stock': 5613 if (Tools::getValue('value') === false) { 5614 $this->ajaxDie(json_encode(['error' => $this->l('Undefined value')])); 5615 } 5616 if ((int) Tools::getValue('value') != 0 && (int) Tools::getValue('value') != 1) { 5617 $this->ajaxDie(json_encode(['error' => $this->l('Incorrect value')])); 5618 } 5619 if (!$product->advanced_stock_management && (int) Tools::getValue('value') == 1) { 5620 $this->ajaxDie(json_encode(['error' => $this->l('Not possible if advanced stock management is disabled. ')])); 5621 } 5622 if (Configuration::get('PS_ADVANCED_STOCK_MANAGEMENT') && (int) Tools::getValue('value') == 1 && (Pack::isPack($product->id) && !Pack::allUsesAdvancedStockManagement($product->id) 5623 && ($product->pack_stock_type == 2 || $product->pack_stock_type == 1 || 5624 ($product->pack_stock_type == 3 && (Configuration::get('PS_PACK_STOCK_TYPE') == 1 || Configuration::get('PS_PACK_STOCK_TYPE') == 2)))) 5625 ) { 5626 $this->ajaxDie( 5627 json_encode( 5628 [ 5629 'error' => $this->l('You cannot use advanced stock management for this pack because').'<br />'. 5630 $this->l('- advanced stock management is not enabled for these products').'<br />'. 5631 $this->l('- you have chosen to decrement products quantities.'), 5632 ] 5633 ) 5634 ); 5635 } 5636 5637 StockAvailable::setProductDependsOnStock($product->id, (int) Tools::getValue('value')); 5638 break; 5639 5640 case 'pack_stock_type': 5641 $value = Tools::getValue('value'); 5642 if ($value === false) { 5643 $this->ajaxDie(json_encode(['error' => $this->l('Undefined value')])); 5644 } 5645 if ((int) $value != 0 && (int) $value != 1 5646 && (int) $value != 2 && (int) $value != 3 5647 ) { 5648 $this->ajaxDie(json_encode(['error' => $this->l('Incorrect value')])); 5649 } 5650 if ($product->depends_on_stock && !Pack::allUsesAdvancedStockManagement($product->id) && ((int) $value == 1 5651 || (int) $value == 2 || ((int) $value == 3 && (Configuration::get('PS_PACK_STOCK_TYPE') == 1 || Configuration::get('PS_PACK_STOCK_TYPE') == 2))) 5652 ) { 5653 $this->ajaxDie( 5654 json_encode( 5655 [ 5656 'error' => $this->l('You cannot use this stock management option because:').'<br />'. 5657 $this->l('- advanced stock management is not enabled for these products').'<br />'. 5658 $this->l('- advanced stock management is enabled for the pack'), 5659 ] 5660 ) 5661 ); 5662 } 5663 5664 Product::setPackStockType($product->id, $value); 5665 break; 5666 5667 case 'out_of_stock': 5668 if (Tools::getValue('value') === false) { 5669 $this->ajaxDie(json_encode(['error' => $this->l('Undefined value')])); 5670 } 5671 if (!in_array((int) Tools::getValue('value'), [0, 1, 2])) { 5672 $this->ajaxDie(json_encode(['error' => $this->l('Incorrect value')])); 5673 } 5674 5675 StockAvailable::setProductOutOfStock($product->id, (int) Tools::getValue('value')); 5676 break; 5677 5678 case 'set_qty': 5679 if (Tools::getValue('value') === false || (!is_numeric(trim(Tools::getValue('value'))))) { 5680 $this->ajaxDie(json_encode(['error' => $this->l('Undefined value')])); 5681 } 5682 if (Tools::getValue('id_product_attribute') === false) { 5683 $this->ajaxDie(json_encode(['error' => $this->l('Undefined id product attribute')])); 5684 } 5685 5686 StockAvailable::setQuantity($product->id, (int) Tools::getValue('id_product_attribute'), (int) Tools::getValue('value')); 5687 Hook::exec('actionProductUpdate', ['id_product' => (int) $product->id, 'product' => $product]); 5688 5689 // Catch potential echo from modules 5690 $error = ob_get_contents(); 5691 if (!empty($error)) { 5692 ob_end_clean(); 5693 $this->ajaxDie(json_encode(['error' => $error])); 5694 } 5695 break; 5696 case 'advanced_stock_management' : 5697 if (Tools::getValue('value') === false) { 5698 $this->ajaxDie(json_encode(['error' => $this->l('Undefined value')])); 5699 } 5700 if ((int) Tools::getValue('value') != 1 && (int) Tools::getValue('value') != 0) { 5701 $this->ajaxDie(json_encode(['error' => $this->l('Incorrect value')])); 5702 } 5703 if (!Configuration::get('PS_ADVANCED_STOCK_MANAGEMENT') && (int) Tools::getValue('value') == 1) { 5704 $this->ajaxDie(json_encode(['error' => $this->l('Not possible if advanced stock management is disabled. ')])); 5705 } 5706 5707 $product->setAdvancedStockManagement((int) Tools::getValue('value')); 5708 if (StockAvailable::dependsOnStock($product->id) == 1 && (int) Tools::getValue('value') == 0) { 5709 StockAvailable::setProductDependsOnStock($product->id, 0); 5710 } 5711 break; 5712 5713 } 5714 $this->ajaxDie(json_encode(['error' => false])); 5715 } 5716 5717 /** 5718 * AdminProducts display hook 5719 * 5720 * @param $obj 5721 * 5722 * @throws PrestaShopException 5723 * 5724 * @since 1.0.0 5725 */ 5726 public function initFormModules($obj) 5727 { 5728 $idModule = Db::getInstance(_PS_USE_SQL_SLAVE_)->getValue( 5729 (new DbQuery()) 5730 ->select('`id_module`') 5731 ->from('module') 5732 ->where('`name` = \''.pSQL($this->tab_display_module).'\'') 5733 ); 5734 $this->tpl_form_vars['custom_form'] = Hook::exec('displayAdminProductsExtra', [], (int) $idModule); 5735 } 5736 5737 public function getL($key) 5738 { 5739 $trad = [ 5740 'Default category:' => $this->l('Default category'), 5741 'Catalog:' => $this->l('Catalog'), 5742 'Consider changing the default category.' => $this->l('Consider changing the default category.'), 5743 'ID' => $this->l('ID'), 5744 'Name' => $this->l('Name'), 5745 '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.'), 5746 ]; 5747 5748 return $trad[$key]; 5749 } 5750 5751 /** 5752 * Ajax process product name check 5753 * 5754 * @since 1.0.0 5755 */ 5756 public function ajaxProcessCheckProductName() 5757 { 5758 if ($this->tabAccess['view'] === '1') { 5759 $search = Tools::getValue('q'); 5760 $id_lang = Tools::getValue('id_lang'); 5761 $limit = Tools::getValue('limit'); 5762 if ($this->context->shop->getContext() != Shop::CONTEXT_SHOP) { 5763 $result = false; 5764 } else { 5765 $result = Db::getInstance()->executeS( 5766 ' 5767 SELECT DISTINCT pl.`name`, p.`id_product`, pl.`id_shop` 5768 FROM `'._DB_PREFIX_.'product` p 5769 LEFT JOIN `'._DB_PREFIX_.'product_shop` ps ON (ps.id_product = p.id_product AND ps.id_shop ='.(int) $this->context->shop->id.') 5770 LEFT JOIN `'._DB_PREFIX_.'product_lang` pl 5771 ON (pl.`id_product` = p.`id_product` AND pl.`id_lang` = '.(int) $id_lang.') 5772 WHERE pl.`name` LIKE "%'.pSQL($search).'%" AND ps.id_product IS NULL 5773 GROUP BY pl.`id_product` 5774 LIMIT '.(int) $limit 5775 ); 5776 } 5777 $this->ajaxDie(json_encode($result)); 5778 } 5779 } 5780 5781 /** 5782 * Ajax process update positions 5783 * 5784 * @since 1.0.0 5785 */ 5786 public function ajaxProcessUpdatePositions() 5787 { 5788 if ($this->tabAccess['edit'] === '1') { 5789 $way = (int) (Tools::getValue('way')); 5790 $id_product = (int) Tools::getValue('id_product'); 5791 $id_category = (int) Tools::getValue('id_category'); 5792 $positions = Tools::getValue('product'); 5793 $page = (int) Tools::getValue('page'); 5794 $selected_pagination = (int) Tools::getValue('selected_pagination'); 5795 5796 if (is_array($positions)) { 5797 foreach ($positions as $position => $value) { 5798 $pos = explode('_', $value); 5799 5800 if ((isset($pos[1]) && isset($pos[2])) && ($pos[1] == $id_category && (int) $pos[2] === $id_product)) { 5801 if ($page > 1) { 5802 $position = $position + (($page - 1) * $selected_pagination); 5803 } 5804 5805 if ($product = new Product((int) $pos[2])) { 5806 if (isset($position) && $product->updatePosition($way, $position)) { 5807 $category = new Category((int) $id_category); 5808 if (Validate::isLoadedObject($category)) { 5809 hook::Exec('categoryUpdate', ['category' => $category]); 5810 } 5811 echo 'ok position '.(int) $position.' for product '.(int) $pos[2]."\r\n"; 5812 } else { 5813 echo '{"hasError" : true, "errors" : "Can not update product '.(int) $id_product.' to position '.(int) $position.' "}'; 5814 } 5815 } else { 5816 echo '{"hasError" : true, "errors" : "This product ('.(int) $id_product.') can t be loaded"}'; 5817 } 5818 5819 break; 5820 } 5821 } 5822 } 5823 } 5824 } 5825 5826 /** 5827 * Ajax process publish product 5828 * 5829 * @since 1.0.0 5830 */ 5831 public function ajaxProcessPublishProduct() 5832 { 5833 if ($this->tabAccess['edit'] === '1') { 5834 if ($id_product = (int) Tools::getValue('id_product')) { 5835 $bo_product_url = dirname($_SERVER['PHP_SELF']).'/index.php?tab=AdminProducts&id_product='.$id_product.'&updateproduct&token='.$this->token; 5836 5837 if (Tools::getValue('redirect')) { 5838 die($bo_product_url); 5839 } 5840 5841 $product = new Product((int) $id_product); 5842 if (!Validate::isLoadedObject($product)) { 5843 die('error: invalid id'); 5844 } 5845 5846 $product->active = 1; 5847 5848 if ($product->save()) { 5849 die($bo_product_url); 5850 } else { 5851 die('error: saving'); 5852 } 5853 } 5854 } 5855 } 5856 5857 /** 5858 * Display preview link 5859 * 5860 * @param null $token 5861 * @param $id 5862 * @param null $name 5863 * 5864 * @return string 5865 * 5866 * @since 1.0.0 5867 */ 5868 public function displayPreviewLink($token = null, $id, $name = null) 5869 { 5870 $tpl = $this->createTemplate('helpers/list/list_action_preview.tpl'); 5871 if (!array_key_exists('Bad SQL query', static::$cache_lang)) { 5872 static::$cache_lang['Preview'] = $this->l('Preview', 'Helper'); 5873 } 5874 5875 $tpl->assign( 5876 [ 5877 'href' => $this->getPreviewUrl(new Product((int) $id)), 5878 'action' => static::$cache_lang['Preview'], 5879 ] 5880 ); 5881 5882 return $tpl->fetch(); 5883 } 5884 5885 protected function processBulkDelete() 5886 { 5887 if ($this->tabAccess['delete'] === '1') { 5888 if (is_array($this->boxes) && !empty($this->boxes)) { 5889 $object = new $this->className(); 5890 5891 if (isset($object->noZeroObject) && 5892 // Check if all object will be deleted 5893 (count(call_user_func([$this->className, $object->noZeroObject])) <= 1 || count($_POST[$this->table.'Box']) == count(call_user_func([$this->className, $object->noZeroObject]))) 5894 ) { 5895 $this->errors[] = Tools::displayError('You need at least one object.').' <b>'.$this->table.'</b><br />'.Tools::displayError('You cannot delete all of the items.'); 5896 } else { 5897 $success = 1; 5898 $products = Tools::getValue($this->table.'Box'); 5899 if (is_array($products) && ($count = count($products))) { 5900 // Deleting products can be quite long on a cheap server. Let's say 1.5 seconds by product (I've seen it!). 5901 if (intval(ini_get('max_execution_time')) < round($count * 1.5)) { 5902 ini_set('max_execution_time', round($count * 1.5)); 5903 } 5904 5905 if (Configuration::get('PS_ADVANCED_STOCK_MANAGEMENT')) { 5906 $stockManager = StockManagerFactory::getManager(); 5907 } 5908 5909 foreach ($products as $id_product) { 5910 $product = new Product((int) $id_product); 5911 /* 5912 * @since 1.5.0 5913 * It is NOT possible to delete a product if there are currently: 5914 * - physical stock for this product 5915 * - supply order(s) for this product 5916 */ 5917 if (Configuration::get('PS_ADVANCED_STOCK_MANAGEMENT') && $product->advanced_stock_management) { 5918 $physical_quantity = $stockManager->getProductPhysicalQuantities($product->id, 0); 5919 $real_quantity = $stockManager->getProductRealQuantities($product->id, 0); 5920 if ($physical_quantity > 0 || $real_quantity > $physical_quantity) { 5921 $this->errors[] = sprintf(Tools::displayError('You cannot delete the product #%d because there is physical stock left.'), $product->id); 5922 } 5923 } 5924 if (!count($this->errors)) { 5925 if ($product->delete()) { 5926 Logger::addLog(sprintf($this->l('%s deletion', 'AdminTab', false, false), $this->className), 1, null, $this->className, (int) $product->id, true, (int) $this->context->employee->id); 5927 } else { 5928 $success = false; 5929 } 5930 } else { 5931 $success = 0; 5932 } 5933 } 5934 } 5935 5936 if ($success) { 5937 $id_category = (int) Tools::getValue('id_category'); 5938 $category_url = empty($id_category) ? '' : '&id_category='.(int) $id_category; 5939 $this->redirect_after = static::$currentIndex.'&conf=2&token='.$this->token.$category_url; 5940 } else { 5941 $this->errors[] = Tools::displayError('An error occurred while deleting this selection.'); 5942 } 5943 } 5944 } else { 5945 $this->errors[] = Tools::displayError('You must select at least one element to delete.'); 5946 } 5947 } else { 5948 $this->errors[] = Tools::displayError('You do not have permission to delete this.'); 5949 } 5950 } 5951 5952 /** 5953 * Update shop association 5954 * 5955 * @param int $idObject 5956 * 5957 * @since 1.0.0 5958 * 5959 * @return void 5960 */ 5961 protected function updateAssoShop($idObject) 5962 { 5963 return; 5964 } 5965 5966 /** 5967 * Get final price 5968 * 5969 * @param $specificPrice 5970 * @param $productPrice 5971 * @param $taxRate 5972 * 5973 * @return float 5974 * 5975 * @since 1.0.0 5976 * @deprecated 1.1.0 Nowhere in use. Remove entirely for 1.2.0. 5977 */ 5978 protected function _getFinalPrice($specificPrice, $productPrice, $taxRate) 5979 { 5980 Tools::displayAsDeprecated('Use Product->getPrice() directly.'); 5981 5982 $decimals = 0; 5983 if ($this->context->currency->decimals) { 5984 $decimals = Configuration::get('PS_PRICE_DISPLAY_PRECISION'); 5985 } 5986 5987 return $this->object->getPrice( 5988 false, 5989 $specificPrice['id_product_attribute'], 5990 $decimals 5991 ); 5992 } 5993 5994 /** 5995 * Display product unavailable warning 5996 * 5997 * @since 1.0.0 5998 * @deprecated 1.1.0 Nowhere in use. Remove entirely for 1.2.0. 5999 */ 6000 protected function _displayUnavailableProductWarning() 6001 { 6002 Tools::displayAsDeprecated(); 6003 6004 $content = '<div class="alert"> 6005 <span>'.$this->l('Your product will be saved as a draft.').'</span> 6006 <a href="#" class="btn btn-default pull-right" onclick="submitAddProductAndPreview()" ><i class="icon-external-link-sign"></i> '.$this->l('Save and preview').'</a> 6007 <input type="hidden" name="fakeSubmitAddProductAndPreview" id="fakeSubmitAddProductAndPreview" /> 6008 </div>'; 6009 $this->tpl_form_vars['warning_unavailable_product'] = $content; 6010 } 6011} 6012