1<?php 2/** 3 * Copyright since 2007 PrestaShop SA and Contributors 4 * PrestaShop is an International Registered Trademark & Property of PrestaShop SA 5 * 6 * NOTICE OF LICENSE 7 * 8 * This source file is subject to the Open Software License (OSL 3.0) 9 * that is bundled with this package in the file LICENSE.md. 10 * It is also available through the world-wide-web at this URL: 11 * https://opensource.org/licenses/OSL-3.0 12 * If you did not receive a copy of the license and are unable to 13 * obtain it through the world-wide-web, please send an email 14 * to license@prestashop.com so we can send you a copy immediately. 15 * 16 * DISCLAIMER 17 * 18 * Do not edit or add to this file if you wish to upgrade PrestaShop to newer 19 * versions in the future. If you wish to customize PrestaShop for your 20 * needs please refer to https://devdocs.prestashop.com/ for more information. 21 * 22 * @author PrestaShop SA and Contributors <contact@prestashop.com> 23 * @copyright Since 2007 PrestaShop SA and Contributors 24 * @license https://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0) 25 */ 26use PrestaShop\PrestaShop\Adapter\SymfonyContainer; 27 28/** 29 * @property Product $object 30 */ 31class AdminProductsControllerCore extends AdminController 32{ 33 /** @var int Max image size for upload 34 * As of 1.5 it is recommended to not set a limit to max image size 35 */ 36 protected $max_file_size = null; 37 protected $max_image_size = null; 38 39 protected $_category; 40 /** 41 * @var string name of the tab to display 42 */ 43 protected $tab_display; 44 protected $tab_display_module; 45 46 /** 47 * 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. 48 * The tabs are preloaded from the smallest to the highest number. 49 * 50 * @var array product tabs 51 */ 52 protected $available_tabs = []; 53 54 protected $default_tab = 'Informations'; 55 56 protected $available_tabs_lang = []; 57 58 protected $position_identifier = 'id_product'; 59 60 protected $submitted_tabs; 61 62 protected $id_current_category; 63 64 public function __construct($theme_name = 'default') 65 { 66 $this->bootstrap = true; 67 $this->table = 'product'; 68 $this->className = 'Product'; 69 parent::__construct('', $theme_name); 70 } 71 72 public function init() 73 { 74 if (Tools::getIsset('id_product')) { 75 if (Tools::getIsset('addproduct') || Tools::getIsset('updateproduct')) { 76 $sfContainer = SymfonyContainer::getInstance(); 77 if (null !== $sfContainer) { 78 $sfRouter = $sfContainer->get('router'); 79 Tools::redirectAdmin($sfRouter->generate( 80 'admin_product_form', 81 ['id' => Tools::getValue('id_product')] 82 )); 83 } 84 } 85 } 86 87 return parent::init(); 88 } 89 90 public static function getQuantities($echo, $tr) 91 { 92 if ((int) $tr['is_virtual'] == 1 && $tr['nb_downloadable'] == 0) { 93 return '∞'; 94 } else { 95 return $echo; 96 } 97 } 98 99 protected function _cleanMetaKeywords($keywords) 100 { 101 if (!empty($keywords) && $keywords != '') { 102 $out = []; 103 $words = explode(',', $keywords); 104 foreach ($words as $word_item) { 105 $word_item = trim($word_item); 106 if (!empty($word_item) && $word_item != '') { 107 $out[] = $word_item; 108 } 109 } 110 111 return (count($out) > 0) ? implode(',', $out) : ''; 112 } else { 113 return ''; 114 } 115 } 116 117 /** 118 * @param Product|ObjectModel $object 119 * @param string $table 120 */ 121 protected function copyFromPost(&$object, $table) 122 { 123 parent::copyFromPost($object, $table); 124 if (get_class($object) != 'Product') { 125 return; 126 } 127 128 /* Additional fields */ 129 foreach (Language::getIDs(false) as $id_lang) { 130 if (isset($_POST['meta_keywords_' . $id_lang])) { 131 $_POST['meta_keywords_' . $id_lang] = $this->_cleanMetaKeywords(Tools::strtolower($_POST['meta_keywords_' . $id_lang])); 132 // preg_replace('/ *,? +,* /', ',', strtolower($_POST['meta_keywords_'.$id_lang])); 133 $object->meta_keywords[$id_lang] = $_POST['meta_keywords_' . $id_lang]; 134 } 135 } 136 $_POST['width'] = empty($_POST['width']) ? '0' : str_replace(',', '.', $_POST['width']); 137 $_POST['height'] = empty($_POST['height']) ? '0' : str_replace(',', '.', $_POST['height']); 138 $_POST['depth'] = empty($_POST['depth']) ? '0' : str_replace(',', '.', $_POST['depth']); 139 $_POST['weight'] = empty($_POST['weight']) ? '0' : str_replace(',', '.', $_POST['weight']); 140 141 if (Tools::getIsset('unit_price') != null) { 142 $object->unit_price = str_replace(',', '.', Tools::getValue('unit_price')); 143 } 144 if (Tools::getIsset('ecotax') != null) { 145 $object->ecotax = str_replace(',', '.', Tools::getValue('ecotax')); 146 } 147 148 if ($this->isTabSubmitted('Informations')) { 149 if ($this->checkMultishopBox('available_for_order', $this->context)) { 150 $object->available_for_order = (int) Tools::getValue('available_for_order'); 151 } 152 153 if ($this->checkMultishopBox('show_price', $this->context)) { 154 $object->show_price = $object->available_for_order ? 1 : (int) Tools::getValue('show_price'); 155 } 156 157 if ($this->checkMultishopBox('online_only', $this->context)) { 158 $object->online_only = (int) Tools::getValue('online_only'); 159 } 160 161 if ($this->checkMultishopBox('show_condition', $this->context)) { 162 $object->show_condition = (int) Tools::getValue('show_condition'); 163 } 164 } 165 if ($this->isTabSubmitted('Prices')) { 166 $object->on_sale = (int) Tools::getValue('on_sale'); 167 } 168 } 169 170 public function checkMultishopBox($field, $context = null) 171 { 172 static $checkbox = null; 173 static $shop_context = null; 174 175 if ($context == null && $shop_context == null) { 176 $context = Context::getContext(); 177 } 178 179 if ($shop_context == null) { 180 $shop_context = $context->shop->getContext(); 181 } 182 183 if ($checkbox == null) { 184 $checkbox = Tools::getValue('multishop_check', []); 185 } 186 187 if ($shop_context == Shop::CONTEXT_SHOP) { 188 return true; 189 } 190 191 if (isset($checkbox[$field]) && $checkbox[$field] == 1) { 192 return true; 193 } 194 195 return false; 196 } 197 198 /** 199 * @param int $id_lang 200 * @param string $orderBy 201 * @param string $orderWay 202 * @param int $start 203 * @param int $limit 204 * @param null $id_lang_shop 205 * 206 * @throws PrestaShopDatabaseException 207 * @throws PrestaShopException 208 * 209 * @deprecated 210 */ 211 public function getList($id_lang, $orderBy = null, $orderWay = null, $start = 0, $limit = null, $id_lang_shop = null) 212 { 213 $orderByPriceFinal = (empty($orderBy) ? ($this->context->cookie->__get($this->table . 'Orderby') ? $this->context->cookie->__get($this->table . 'Orderby') : 'id_' . $this->table) : $orderBy); 214 $orderWayPriceFinal = (empty($orderWay) ? ($this->context->cookie->__get($this->table . 'Orderway') ? $this->context->cookie->__get($this->table . 'Orderby') : 'ASC') : $orderWay); 215 if ($orderByPriceFinal == 'price_final') { 216 $orderBy = 'id_' . $this->table; 217 $orderWay = 'ASC'; 218 } 219 parent::getList($id_lang, $orderBy, $orderWay, $start, $limit, $this->context->shop->id); 220 221 /* update product quantity with attributes ...*/ 222 $nb = count($this->_list); 223 if ($this->_list) { 224 $context = $this->context->cloneContext(); 225 $context->shop = clone $context->shop; 226 /* update product final price */ 227 for ($i = 0; $i < $nb; ++$i) { 228 if (Context::getContext()->shop->getContext() != Shop::CONTEXT_SHOP) { 229 $context->shop = new Shop((int) $this->_list[$i]['id_shop_default']); 230 } 231 232 // convert price with the currency from context 233 $this->_list[$i]['price'] = Tools::convertPrice($this->_list[$i]['price'], $this->context->currency, true, $this->context); 234 $this->_list[$i]['price_tmp'] = Product::getPriceStatic( 235 $this->_list[$i]['id_product'], 236 true, 237 null, 238 (int) Configuration::get('PS_PRICE_DISPLAY_PRECISION'), 239 null, 240 false, 241 true, 242 1, 243 true, 244 null, 245 null, 246 null, 247 $nothing, 248 true, 249 true, 250 $context 251 ); 252 } 253 } 254 255 if ($orderByPriceFinal == 'price_final') { 256 if (strtolower($orderWayPriceFinal) == 'desc') { 257 uasort($this->_list, 'cmpPriceDesc'); 258 } else { 259 uasort($this->_list, 'cmpPriceAsc'); 260 } 261 } 262 for ($i = 0; $this->_list && $i < $nb; ++$i) { 263 $this->_list[$i]['price_final'] = $this->_list[$i]['price_tmp']; 264 unset($this->_list[$i]['price_tmp']); 265 } 266 } 267 268 protected function loadObject($opt = false) 269 { 270 $result = parent::loadObject($opt); 271 if ($result && Validate::isLoadedObject($this->object)) { 272 if (Shop::getContext() == Shop::CONTEXT_SHOP && Shop::isFeatureActive() && !$this->object->isAssociatedToShop()) { 273 $default_product = new Product((int) $this->object->id, false, null, (int) $this->object->id_shop_default); 274 $def = ObjectModel::getDefinition($this->object); 275 foreach ($def['fields'] as $field_name => $row) { 276 if (is_array($default_product->$field_name)) { 277 foreach ($default_product->$field_name as $key => $value) { 278 $this->object->{$field_name}[$key] = $value; 279 } 280 } else { 281 $this->object->$field_name = $default_product->$field_name; 282 } 283 } 284 } 285 $this->object->loadStockData(); 286 } 287 288 return $result; 289 } 290 291 public function ajaxProcessGetCategoryTree() 292 { 293 $category = Tools::getValue('category', Category::getRootCategory()->id); 294 $full_tree = Tools::getValue('fullTree', 0); 295 $use_check_box = Tools::getValue('useCheckBox', 1); 296 $selected = Tools::getValue('selected', []); 297 $id_tree = Tools::getValue('type'); 298 $input_name = str_replace(['[', ']'], '', Tools::getValue('inputName', null)); 299 300 $tree = new HelperTreeCategories('subtree_associated_categories'); 301 $tree->setTemplate('subtree_associated_categories.tpl') 302 ->setUseCheckBox($use_check_box) 303 ->setUseSearch(true) 304 ->setIdTree($id_tree) 305 ->setSelectedCategories($selected) 306 ->setFullTree($full_tree) 307 ->setChildrenOnly(true) 308 ->setNoJS(true) 309 ->setRootCategory($category); 310 311 if ($input_name) { 312 $tree->setInputName($input_name); 313 } 314 315 die($tree->render()); 316 } 317 318 public function ajaxProcessGetCountriesOptions() 319 { 320 if (!$res = Country::getCountriesByIdShop((int) Tools::getValue('id_shop'), (int) $this->context->language->id)) { 321 return; 322 } 323 324 $tpl = $this->createTemplate('specific_prices_shop_update.tpl'); 325 $tpl->assign( 326 [ 327 'option_list' => $res, 328 'key_id' => 'id_country', 329 'key_value' => 'name', 330 ] 331 ); 332 333 $this->content = $tpl->fetch(); 334 } 335 336 public function ajaxProcessGetCurrenciesOptions() 337 { 338 if (!$res = Currency::getCurrenciesByIdShop((int) Tools::getValue('id_shop'))) { 339 return; 340 } 341 342 $tpl = $this->createTemplate('specific_prices_shop_update.tpl'); 343 $tpl->assign( 344 [ 345 'option_list' => $res, 346 'key_id' => 'id_currency', 347 'key_value' => 'name', 348 ] 349 ); 350 351 $this->content = $tpl->fetch(); 352 } 353 354 public function ajaxProcessGetGroupsOptions() 355 { 356 if (!$res = Group::getGroups((int) $this->context->language->id, (int) Tools::getValue('id_shop'))) { 357 return; 358 } 359 360 $tpl = $this->createTemplate('specific_prices_shop_update.tpl'); 361 $tpl->assign( 362 [ 363 'option_list' => $res, 364 'key_id' => 'id_group', 365 'key_value' => 'name', 366 ] 367 ); 368 369 $this->content = $tpl->fetch(); 370 } 371 372 public function processDeleteVirtualProduct() 373 { 374 if (!($id_product_download = ProductDownload::getIdFromIdProduct((int) Tools::getValue('id_product')))) { 375 $this->errors[] = $this->trans('Cannot retrieve file.', [], 'Admin.Notifications.Error'); 376 } else { 377 $product_download = new ProductDownload((int) $id_product_download); 378 379 if (!$product_download->deleteFile((int) $id_product_download)) { 380 $this->errors[] = $this->trans('Cannot delete file', [], 'Admin.Notifications.Error'); 381 } else { 382 $this->redirect_after = self::$currentIndex . '&id_product=' . (int) Tools::getValue('id_product') . '&updateproduct&key_tab=VirtualProduct&conf=1&token=' . $this->token; 383 } 384 } 385 386 $this->display = 'edit'; 387 $this->tab_display = 'VirtualProduct'; 388 } 389 390 public function ajaxProcessAddAttachment() 391 { 392 if (!$this->access('edit')) { 393 return die(json_encode(['error' => 'You do not have the right permission'])); 394 } 395 if (isset($_FILES['attachment_file'])) { 396 if ((int) $_FILES['attachment_file']['error'] === 1) { 397 $_FILES['attachment_file']['error'] = []; 398 399 $max_upload = (int) ini_get('upload_max_filesize'); 400 $max_post = (int) ini_get('post_max_size'); 401 $upload_mb = min($max_upload, $max_post); 402 $_FILES['attachment_file']['error'][] = sprintf( 403 'File %1$s exceeds the size allowed by the server. The limit is set to %2$d MB.', 404 '<b>' . $_FILES['attachment_file']['name'] . '</b> ', 405 '<b>' . $upload_mb . '</b>' 406 ); 407 } 408 409 $_FILES['attachment_file']['error'] = []; 410 411 $is_attachment_name_valid = false; 412 $attachment_names = Tools::getValue('attachment_name'); 413 $attachment_descriptions = Tools::getValue('attachment_description'); 414 415 if (!isset($attachment_names) || !$attachment_names) { 416 $attachment_names = []; 417 } 418 419 if (!isset($attachment_descriptions) || !$attachment_descriptions) { 420 $attachment_descriptions = []; 421 } 422 423 foreach ($attachment_names as $lang => $name) { 424 $language = Language::getLanguage((int) $lang); 425 426 if (Tools::strlen($name) > 0) { 427 $is_attachment_name_valid = true; 428 } 429 430 if (!Validate::isGenericName($name)) { 431 $_FILES['attachment_file']['error'][] = $this->trans('Invalid name for %s language', [$language['name']], 'Admin.Notifications.Error'); 432 } elseif (Tools::strlen($name) > 32) { 433 $_FILES['attachment_file']['error'][] = $this->trans('The name for %1s language is too long (%2d chars max).', [$language['name'], 32], 'Admin.Notifications.Error'); 434 } 435 } 436 437 foreach ($attachment_descriptions as $lang => $description) { 438 $language = Language::getLanguage((int) $lang); 439 440 if (!Validate::isCleanHtml($description)) { 441 $_FILES['attachment_file']['error'][] = $this->trans('Invalid description for %s language', [$language['name']], 'Admin.Catalog.Notification'); 442 } 443 } 444 445 if (!$is_attachment_name_valid) { 446 $_FILES['attachment_file']['error'][] = $this->trans('An attachment name is required.', [], 'Admin.Catalog.Notification'); 447 } 448 449 if (empty($_FILES['attachment_file']['error'])) { 450 if (is_uploaded_file($_FILES['attachment_file']['tmp_name'])) { 451 if ($_FILES['attachment_file']['size'] > (Configuration::get('PS_ATTACHMENT_MAXIMUM_SIZE') * 1024 * 1024)) { 452 $_FILES['attachment_file']['error'][] = sprintf( 453 'The file is too large. Maximum size allowed is: %1$d kB. The file you are trying to upload is %2$d kB.', 454 (Configuration::get('PS_ATTACHMENT_MAXIMUM_SIZE') * 1024), 455 number_format(($_FILES['attachment_file']['size'] / 1024), 2, '.', '') 456 ); 457 } else { 458 do { 459 $uniqid = sha1(microtime()); 460 } while (file_exists(_PS_DOWNLOAD_DIR_ . $uniqid)); 461 if (!copy($_FILES['attachment_file']['tmp_name'], _PS_DOWNLOAD_DIR_ . $uniqid)) { 462 $_FILES['attachment_file']['error'][] = 'File copy failed'; 463 } 464 @unlink($_FILES['attachment_file']['tmp_name']); 465 } 466 } else { 467 $_FILES['attachment_file']['error'][] = $this->trans('The file is missing.', [], 'Admin.Notifications.Error'); 468 } 469 470 if (empty($_FILES['attachment_file']['error']) && isset($uniqid)) { 471 $attachment = new Attachment(); 472 473 foreach ($attachment_names as $lang => $name) { 474 $attachment->name[(int) $lang] = $name; 475 } 476 477 foreach ($attachment_descriptions as $lang => $description) { 478 $attachment->description[(int) $lang] = $description; 479 } 480 481 $attachment->file = $uniqid; 482 $attachment->mime = $_FILES['attachment_file']['type']; 483 $attachment->file_name = $_FILES['attachment_file']['name']; 484 485 if (empty($attachment->mime) || Tools::strlen($attachment->mime) > 128) { 486 $_FILES['attachment_file']['error'][] = $this->trans('Invalid file extension', [], 'Admin.Notifications.Error'); 487 } 488 if (!Validate::isGenericName($attachment->file_name)) { 489 $_FILES['attachment_file']['error'][] = $this->trans('Invalid file name', [], 'Admin.Notifications.Error'); 490 } 491 if (Tools::strlen($attachment->file_name) > 128) { 492 $_FILES['attachment_file']['error'][] = $this->trans('The file name is too long.', [], 'Admin.Notifications.Error'); 493 } 494 if (empty($this->errors)) { 495 $res = $attachment->add(); 496 if (!$res) { 497 $_FILES['attachment_file']['error'][] = $this->trans('This attachment was unable to be loaded into the database.', [], 'Admin.Catalog.Notification'); 498 } else { 499 $_FILES['attachment_file']['id_attachment'] = $attachment->id; 500 $_FILES['attachment_file']['filename'] = $attachment->name[$this->context->employee->id_lang]; 501 $id_product = (int) Tools::getValue($this->identifier); 502 $res = $attachment->attachProduct($id_product); 503 if (!$res) { 504 $_FILES['attachment_file']['error'][] = $this->trans('We were unable to associate this attachment to a product.', [], 'Admin.Catalog.Notification'); 505 } 506 } 507 } else { 508 $_FILES['attachment_file']['error'][] = $this->trans('Invalid file', [], 'Admin.Notifications.Error'); 509 } 510 } 511 } 512 513 die(json_encode($_FILES)); 514 } 515 } 516 517 /** 518 * Attach an existing attachment to the product. 519 */ 520 public function processAttachments() 521 { 522 if ($id = (int) Tools::getValue($this->identifier)) { 523 $attachments = trim(Tools::getValue('arrayAttachments'), ','); 524 $attachments = explode(',', $attachments); 525 if (!Attachment::attachToProduct($id, $attachments)) { 526 $this->errors[] = $this->trans('An error occurred while saving product attachments.', [], 'Admin.Catalog.Notification'); 527 } 528 } 529 } 530 531 public function processDuplicate() 532 { 533 if (Validate::isLoadedObject($product = new Product((int) Tools::getValue('id_product')))) { 534 $id_product_old = $product->id; 535 if (empty($product->price) && Shop::getContext() == Shop::CONTEXT_GROUP) { 536 $shops = ShopGroup::getShopsFromGroup(Shop::getContextShopGroupID()); 537 foreach ($shops as $shop) { 538 if ($product->isAssociatedToShop($shop['id_shop'])) { 539 $product_price = new Product($id_product_old, false, null, $shop['id_shop']); 540 $product->price = $product_price->price; 541 } 542 } 543 } 544 unset( 545 $product->id, 546 $product->id_product 547 ); 548 549 $product->indexed = 0; 550 $product->active = 0; 551 if ($product->add() 552 && Category::duplicateProductCategories($id_product_old, $product->id) 553 && Product::duplicateSuppliers($id_product_old, $product->id) 554 && ($combination_images = Product::duplicateAttributes($id_product_old, $product->id)) !== false 555 && GroupReduction::duplicateReduction($id_product_old, $product->id) 556 && Product::duplicateAccessories($id_product_old, $product->id) 557 && Product::duplicateFeatures($id_product_old, $product->id) 558 && Product::duplicateSpecificPrices($id_product_old, $product->id) 559 && Pack::duplicate($id_product_old, $product->id) 560 && Product::duplicateCustomizationFields($id_product_old, $product->id) 561 && Product::duplicateTags($id_product_old, $product->id) 562 && Product::duplicateDownload($id_product_old, $product->id)) { 563 if ($product->hasAttributes()) { 564 Product::updateDefaultAttribute($product->id); 565 } 566 567 if (!Tools::getValue('noimage') && !Image::duplicateProductImages($id_product_old, $product->id, $combination_images)) { 568 $this->errors[] = $this->trans('An error occurred while copying the image.', [], 'Admin.Notifications.Error'); 569 } else { 570 Hook::exec('actionProductAdd', ['id_product_old' => $id_product_old, 'id_product' => (int) $product->id, 'product' => $product]); 571 if (in_array($product->visibility, ['both', 'search']) && Configuration::get('PS_SEARCH_INDEXATION')) { 572 Search::indexation(false, $product->id); 573 } 574 $this->redirect_after = self::$currentIndex . (Tools::getIsset('id_category') ? '&id_category=' . (int) Tools::getValue('id_category') : '') . '&conf=19&token=' . $this->token; 575 } 576 } else { 577 $this->errors[] = $this->trans('An error occurred while creating an object.', [], 'Admin.Notifications.Error'); 578 } 579 } 580 } 581 582 public function processDelete() 583 { 584 if (Validate::isLoadedObject($object = $this->loadObject()) && isset($this->fieldImageSettings)) { 585 /** @var Product $object */ 586 // check if request at least one object with noZeroObject 587 if (isset($object->noZeroObject) && count($taxes = call_user_func([$this->className, $object->noZeroObject])) <= 1) { 588 $this->errors[] = $this->trans('You need at least one object.', [], 'Admin.Notifications.Error') . ' <b>' . $this->table . '</b><br />' . $this->trans('You cannot delete all of the items.', [], 'Admin.Notifications.Error'); 589 } else { 590 /* 591 * @since 1.5.0 592 * It is NOT possible to delete a product if there are currently: 593 * - physical stock for this product 594 * - supply order(s) for this product 595 */ 596 if (Configuration::get('PS_ADVANCED_STOCK_MANAGEMENT') && $object->advanced_stock_management) { 597 $stock_manager = StockManagerFactory::getManager(); 598 $physical_quantity = $stock_manager->getProductPhysicalQuantities($object->id, 0); 599 $real_quantity = $stock_manager->getProductRealQuantities($object->id, 0); 600 if ($physical_quantity > 0 || $real_quantity > $physical_quantity) { 601 $this->errors[] = $this->trans('You cannot delete this product because there is physical stock left.', [], 'Admin.Catalog.Notification'); 602 } 603 } 604 605 if (!count($this->errors)) { 606 if ($object->delete()) { 607 $id_category = (int) Tools::getValue('id_category'); 608 $category_url = empty($id_category) ? '' : '&id_category=' . (int) $id_category; 609 PrestaShopLogger::addLog(sprintf('%s deletion', $this->className), 1, null, $this->className, (int) $object->id, true, (int) $this->context->employee->id); 610 $this->redirect_after = self::$currentIndex . '&conf=1&token=' . $this->token . $category_url; 611 } else { 612 $this->errors[] = $this->trans('An error occurred during deletion.', [], 'Admin.Notifications.Error'); 613 } 614 } 615 } 616 } else { 617 $this->errors[] = $this->trans('An error occurred while deleting the object.', [], 'Admin.Notifications.Error') . ' <b>' . $this->table . '</b> ' . $this->trans('(cannot load object)', [], 'Admin.Notifications.Error'); 618 } 619 } 620 621 public function processImage() 622 { 623 $id_image = (int) Tools::getValue('id_image'); 624 $image = new Image((int) $id_image); 625 if (Validate::isLoadedObject($image)) { 626 /* Update product image/legend */ 627 // @todo : move in processEditProductImage 628 if (Tools::getIsset('editImage')) { 629 if ($image->cover) { 630 $_POST['cover'] = 1; 631 } 632 633 $_POST['id_image'] = $image->id; 634 } elseif (Tools::getIsset('coverImage')) { 635 /* Choose product cover image */ 636 Image::deleteCover($image->id_product); 637 $image->cover = 1; 638 if (!$image->update()) { 639 $this->errors[] = $this->trans('You cannot change the product\'s cover image.', [], 'Admin.Catalog.Notification'); 640 } else { 641 $productId = (int) Tools::getValue('id_product'); 642 @unlink(_PS_TMP_IMG_DIR_ . 'product_' . $productId . '.jpg'); 643 @unlink(_PS_TMP_IMG_DIR_ . 'product_mini_' . $productId . '_' . $this->context->shop->id . '.jpg'); 644 $this->redirect_after = self::$currentIndex . '&id_product=' . $image->id_product . '&id_category=' . (Tools::getIsset('id_category') ? '&id_category=' . (int) Tools::getValue('id_category') : '') . '&action=Images&addproduct' . '&token=' . $this->token; 645 } 646 } elseif (Tools::getIsset('imgPosition') && Tools::getIsset('imgDirection')) { 647 /* Choose product image position */ 648 $image->updatePosition(Tools::getValue('imgDirection'), Tools::getValue('imgPosition')); 649 $this->redirect_after = self::$currentIndex . '&id_product=' . $image->id_product . '&id_category=' . (Tools::getIsset('id_category') ? '&id_category=' . (int) Tools::getValue('id_category') : '') . '&add' . $this->table . '&action=Images&token=' . $this->token; 650 } 651 } else { 652 $this->errors[] = $this->trans('The image could not be found. ', [], 'Admin.Catalog.Notification'); 653 } 654 } 655 656 protected function processBulkDelete() 657 { 658 if ($this->access('delete')) { 659 if (is_array($this->boxes) && !empty($this->boxes)) { 660 $object = new $this->className(); 661 662 if (isset($object->noZeroObject) && 663 // Check if all object will be deleted 664 (count(call_user_func([$this->className, $object->noZeroObject])) <= 1 || count($_POST[$this->table . 'Box']) == count(call_user_func([$this->className, $object->noZeroObject])))) { 665 $this->errors[] = $this->trans('You need at least one object.', [], 'Admin.Notifications.Error') . ' <b>' . $this->table . '</b><br />' . $this->trans('You cannot delete all of the items.', [], 'Admin.Notifications.Error'); 666 } else { 667 $success = 1; 668 $products = Tools::getValue($this->table . 'Box'); 669 if (is_array($products) && ($count = count($products))) { 670 // Deleting products can be quite long on a cheap server. Let's say 1.5 seconds by product (I've seen it!). 671 if ((int) (ini_get('max_execution_time')) < round($count * 1.5)) { 672 ini_set('max_execution_time', round($count * 1.5)); 673 } 674 675 if (Configuration::get('PS_ADVANCED_STOCK_MANAGEMENT')) { 676 $stock_manager = StockManagerFactory::getManager(); 677 } 678 679 foreach ($products as $id_product) { 680 $product = new Product((int) $id_product); 681 /* 682 * @since 1.5.0 683 * It is NOT possible to delete a product if there are currently: 684 * - physical stock for this product 685 * - supply order(s) for this product 686 */ 687 if (Configuration::get('PS_ADVANCED_STOCK_MANAGEMENT') && $product->advanced_stock_management) { 688 $physical_quantity = $stock_manager->getProductPhysicalQuantities($product->id, 0); 689 $real_quantity = $stock_manager->getProductRealQuantities($product->id, 0); 690 if ($physical_quantity > 0 || $real_quantity > $physical_quantity) { 691 $this->errors[] = $this->trans('You cannot delete the product #%d because there is physical stock left.', [$product->id], 'Admin.Catalog.Notification'); 692 } 693 } 694 if (!count($this->errors)) { 695 if ($product->delete()) { 696 PrestaShopLogger::addLog(sprintf('%s deletion', $this->className), 1, null, $this->className, (int) $product->id, true, (int) $this->context->employee->id); 697 } else { 698 $success = false; 699 } 700 } else { 701 $success = 0; 702 } 703 } 704 } 705 706 if ($success) { 707 $id_category = (int) Tools::getValue('id_category'); 708 $category_url = empty($id_category) ? '' : '&id_category=' . (int) $id_category; 709 $this->redirect_after = self::$currentIndex . '&conf=2&token=' . $this->token . $category_url; 710 } else { 711 $this->errors[] = $this->trans('An error occurred while deleting this selection.', [], 'Admin.Notifications.Error'); 712 } 713 } 714 } else { 715 $this->errors[] = $this->trans('You must select at least one element to delete.', [], 'Admin.Notifications.Error'); 716 } 717 } else { 718 $this->errors[] = $this->trans('You do not have permission to delete this.', [], 'Admin.Notifications.Error'); 719 } 720 } 721 722 public function processProductAttribute() 723 { 724 // Don't process if the combination fields have not been submitted 725 if (!Combination::isFeatureActive() || !Tools::getValue('attribute_combination_list')) { 726 return; 727 } 728 729 if (Validate::isLoadedObject($product = $this->object)) { 730 if ($this->isProductFieldUpdated('attribute_price') && (!Tools::getIsset('attribute_price') || Tools::getIsset('attribute_price') == null)) { 731 $this->errors[] = $this->trans('The price attribute is required.', [], 'Admin.Catalog.Notification'); 732 } 733 if (!Tools::getIsset('attribute_combination_list') || Tools::isEmpty(Tools::getValue('attribute_combination_list'))) { 734 $this->errors[] = $this->trans('You must add at least one attribute.', [], 'Admin.Catalog.Notification'); 735 } 736 737 $array_checks = [ 738 'reference' => 'isReference', 739 'supplier_reference' => 'isReference', 740 'location' => 'isReference', 741 'ean13' => 'isEan13', 742 'isbn' => 'isIsbn', 743 'upc' => 'isUpc', 744 'mpn' => 'isMpn', 745 'wholesale_price' => 'isPrice', 746 'price' => 'isPrice', 747 'ecotax' => 'isPrice', 748 'quantity' => 'isInt', 749 'weight' => 'isUnsignedFloat', 750 'unit_price_impact' => 'isPrice', 751 'default_on' => 'isBool', 752 'minimal_quantity' => 'isUnsignedInt', 753 'available_date' => 'isDateFormat', 754 ]; 755 foreach ($array_checks as $property => $check) { 756 if (Tools::getValue('attribute_' . $property) !== false && !call_user_func(['Validate', $check], Tools::getValue('attribute_' . $property))) { 757 $this->errors[] = $this->trans('The %s field is not valid', [$property], 'Admin.Notifications.Error'); 758 } 759 } 760 761 if (!count($this->errors)) { 762 if (!isset($_POST['attribute_wholesale_price'])) { 763 $_POST['attribute_wholesale_price'] = 0; 764 } 765 if (!isset($_POST['attribute_price_impact'])) { 766 $_POST['attribute_price_impact'] = 0; 767 } 768 if (!isset($_POST['attribute_weight_impact'])) { 769 $_POST['attribute_weight_impact'] = 0; 770 } 771 if (!isset($_POST['attribute_ecotax'])) { 772 $_POST['attribute_ecotax'] = 0; 773 } 774 if (Tools::getValue('attribute_default')) { 775 $product->deleteDefaultAttributes(); 776 } 777 778 // Change existing one 779 if (($id_product_attribute = (int) Tools::getValue('id_product_attribute')) || ($id_product_attribute = $product->productAttributeExists(Tools::getValue('attribute_combination_list'), false, null, true, true))) { 780 if ($this->access('edit')) { 781 if ($this->isProductFieldUpdated('available_date_attribute') && (Tools::getValue('available_date_attribute') != '' && !Validate::isDateFormat(Tools::getValue('available_date_attribute')))) { 782 $this->errors[] = $this->trans('Invalid date format.', [], 'Admin.Notifications.Error'); 783 } else { 784 $product->updateAttribute( 785 (int) $id_product_attribute, 786 $this->isProductFieldUpdated('attribute_wholesale_price') ? Tools::getValue('attribute_wholesale_price') : null, 787 $this->isProductFieldUpdated('attribute_price_impact') ? Tools::getValue('attribute_price') * Tools::getValue('attribute_price_impact') : null, 788 $this->isProductFieldUpdated('attribute_weight_impact') ? Tools::getValue('attribute_weight') * Tools::getValue('attribute_weight_impact') : null, 789 $this->isProductFieldUpdated('attribute_unit_impact') ? Tools::getValue('attribute_unity') * Tools::getValue('attribute_unit_impact') : null, 790 $this->isProductFieldUpdated('attribute_ecotax') ? Tools::getValue('attribute_ecotax') : null, 791 Tools::getValue('id_image_attr'), 792 Tools::getValue('attribute_reference'), 793 Tools::getValue('attribute_ean13'), 794 $this->isProductFieldUpdated('attribute_default') ? Tools::getValue('attribute_default') : null, 795 Tools::getValue('attribute_location'), 796 Tools::getValue('attribute_upc'), 797 $this->isProductFieldUpdated('attribute_minimal_quantity') ? Tools::getValue('attribute_minimal_quantity') : null, 798 $this->isProductFieldUpdated('available_date_attribute') ? Tools::getValue('available_date_attribute') : null, 799 false, 800 [], 801 Tools::getValue('attribute_isbn'), 802 Tools::getValue('attribute_low_stock_threshold'), 803 Tools::getValue('attribute_low_stock_alert'), 804 Tools::getValue('attribute_mpn') 805 ); 806 StockAvailable::setProductDependsOnStock((int) $product->id, $product->depends_on_stock, null, (int) $id_product_attribute); 807 StockAvailable::setProductOutOfStock((int) $product->id, $product->out_of_stock, null, (int) $id_product_attribute); 808 } 809 } else { 810 $this->errors[] = $this->trans('You do not have permission to add this.', [], 'Admin.Notifications.Error'); 811 } 812 } else { 813 // Add new 814 if ($this->access('add')) { 815 if ($product->productAttributeExists(Tools::getValue('attribute_combination_list'))) { 816 $this->errors[] = $this->trans('This combination already exists.', [], 'Admin.Catalog.Notification'); 817 } else { 818 $id_product_attribute = $product->addCombinationEntity( 819 Tools::getValue('attribute_wholesale_price'), 820 Tools::getValue('attribute_price') * Tools::getValue('attribute_price_impact'), 821 Tools::getValue('attribute_weight') * Tools::getValue('attribute_weight_impact'), 822 Tools::getValue('attribute_unity') * Tools::getValue('attribute_unit_impact'), 823 Tools::getValue('attribute_ecotax'), 824 0, 825 Tools::getValue('id_image_attr'), 826 Tools::getValue('attribute_reference'), 827 null, 828 Tools::getValue('attribute_ean13'), 829 Tools::getValue('attribute_default'), 830 Tools::getValue('attribute_location'), 831 Tools::getValue('attribute_upc'), 832 Tools::getValue('attribute_minimal_quantity'), 833 [], 834 Tools::getValue('available_date_attribute'), 835 Tools::getValue('attribute_isbn'), 836 Tools::getValue('attribute_low_stock_threshold'), 837 Tools::getValue('attribute_low_stock_alert'), 838 Tools::getValue('attribute_mpn') 839 ); 840 StockAvailable::setProductDependsOnStock((int) $product->id, $product->depends_on_stock, null, (int) $id_product_attribute); 841 StockAvailable::setProductOutOfStock((int) $product->id, $product->out_of_stock, null, (int) $id_product_attribute); 842 } 843 } else { 844 $this->errors[] = $this->trans('You do not have permission to edit this.', [], 'Admin.Notifications.Error'); 845 } 846 } 847 if (!count($this->errors)) { 848 $combination = new Combination((int) $id_product_attribute); 849 $combination->setAttributes(Tools::getValue('attribute_combination_list')); 850 851 // images could be deleted before 852 $id_images = Tools::getValue('id_image_attr'); 853 if (!empty($id_images)) { 854 $combination->setImages($id_images); 855 } 856 857 $product->checkDefaultAttributes(); 858 if (Tools::getValue('attribute_default')) { 859 Product::updateDefaultAttribute((int) $product->id); 860 if (isset($id_product_attribute)) { 861 $product->cache_default_attribute = (int) $id_product_attribute; 862 } 863 864 if ($available_date = Tools::getValue('available_date_attribute')) { 865 $product->setAvailableDate($available_date); 866 } else { 867 $product->setAvailableDate(); 868 } 869 } 870 } 871 } 872 } 873 } 874 875 public function processFeatures($id_product = null) 876 { 877 if (!Feature::isFeatureActive()) { 878 return; 879 } 880 881 $id_product = (int) $id_product ? $id_product : (int) Tools::getValue('id_product'); 882 883 if (Validate::isLoadedObject($product = new Product($id_product))) { 884 // delete all objects 885 $product->deleteFeatures(); 886 887 // add new objects 888 $languages = Language::getLanguages(false); 889 $form = Tools::getValue('form', false); 890 if (false !== $form) { 891 $features = isset($form['step1']['features']) ? $form['step1']['features'] : []; 892 if (is_array($features)) { 893 foreach ($features as $feature) { 894 if (!empty($feature['value'])) { 895 $product->addFeaturesToDB($feature['feature'], $feature['value']); 896 } elseif ($defaultValue = $this->checkFeatures($languages, $feature)) { 897 $idValue = $product->addFeaturesToDB($feature['feature'], 0, 1); 898 foreach ($languages as $language) { 899 $valueToAdd = (isset($feature['custom_value'][$language['id_lang']])) 900 ? $feature['custom_value'][$language['id_lang']] 901 : $defaultValue; 902 903 $product->addFeaturesCustomToDB($idValue, (int) $language['id_lang'], $valueToAdd); 904 } 905 } 906 } 907 } 908 } 909 } else { 910 $this->errors[] = $this->trans('A product must be created before adding features.', [], 'Admin.Catalog.Notification'); 911 } 912 } 913 914 /** 915 * This function is never called at the moment (specific prices cannot be edited). 916 */ 917 public function processPricesModification() 918 { 919 $id_specific_prices = Tools::getValue('spm_id_specific_price'); 920 $id_combinations = Tools::getValue('spm_id_product_attribute'); 921 $id_shops = Tools::getValue('spm_id_shop'); 922 $id_currencies = Tools::getValue('spm_id_currency'); 923 $id_countries = Tools::getValue('spm_id_country'); 924 $id_groups = Tools::getValue('spm_id_group'); 925 $id_customers = Tools::getValue('spm_id_customer'); 926 $prices = Tools::getValue('spm_price'); 927 $from_quantities = Tools::getValue('spm_from_quantity'); 928 $reductions = Tools::getValue('spm_reduction'); 929 $reduction_types = Tools::getValue('spm_reduction_type'); 930 $froms = Tools::getValue('spm_from'); 931 $tos = Tools::getValue('spm_to'); 932 933 foreach ($id_specific_prices as $key => $id_specific_price) { 934 if ($reduction_types[$key] == 'percentage' && ((float) $reductions[$key] <= 0 || (float) $reductions[$key] > 100)) { 935 $this->errors[] = $this->trans('Submitted reduction value (0-100) is out-of-range', [], 'Admin.Catalog.Notification'); 936 } elseif ($this->_validateSpecificPrice($id_shops[$key], $id_currencies[$key], $id_countries[$key], $id_groups[$key], $id_customers[$key], $prices[$key], $from_quantities[$key], $reductions[$key], $reduction_types[$key], $froms[$key], $tos[$key], $id_combinations[$key])) { 937 $specific_price = new SpecificPrice((int) ($id_specific_price)); 938 $specific_price->id_shop = (int) $id_shops[$key]; 939 $specific_price->id_product_attribute = (int) $id_combinations[$key]; 940 $specific_price->id_currency = (int) ($id_currencies[$key]); 941 $specific_price->id_country = (int) ($id_countries[$key]); 942 $specific_price->id_group = (int) ($id_groups[$key]); 943 $specific_price->id_customer = (int) $id_customers[$key]; 944 $specific_price->price = (float) ($prices[$key]); 945 $specific_price->from_quantity = (int) ($from_quantities[$key]); 946 $specific_price->reduction = (float) ($reduction_types[$key] == 'percentage' ? ($reductions[$key] / 100) : $reductions[$key]); 947 $specific_price->reduction_type = !$reductions[$key] ? 'amount' : $reduction_types[$key]; 948 $specific_price->from = !$froms[$key] ? '0000-00-00 00:00:00' : $froms[$key]; 949 $specific_price->to = !$tos[$key] ? '0000-00-00 00:00:00' : $tos[$key]; 950 if (!$specific_price->update()) { 951 $this->errors[] = $this->trans('An error occurred while updating the specific price.', [], 'Admin.Catalog.Notification'); 952 } 953 } 954 } 955 if (!count($this->errors)) { 956 $this->redirect_after = self::$currentIndex . '&id_product=' . (int) (Tools::getValue('id_product')) . (Tools::getIsset('id_category') ? '&id_category=' . (int) Tools::getValue('id_category') : '') . '&update' . $this->table . '&action=Prices&token=' . $this->token; 957 } 958 } 959 960 public function processPriceAddition() 961 { 962 // Check if a specific price has been submitted 963 if (!Tools::getIsset('submitPriceAddition')) { 964 return; 965 } 966 967 $id_product = Tools::getValue('id_product'); 968 $id_product_attribute = Tools::getValue('sp_id_product_attribute'); 969 $id_shop = Tools::getValue('sp_id_shop'); 970 $id_currency = Tools::getValue('sp_id_currency'); 971 $id_country = Tools::getValue('sp_id_country'); 972 $id_group = Tools::getValue('sp_id_group'); 973 $id_customer = Tools::getValue('sp_id_customer'); 974 $price = Tools::getValue('leave_bprice') ? '-1' : Tools::getValue('sp_price'); 975 $from_quantity = Tools::getValue('sp_from_quantity'); 976 $reduction = (float) (Tools::getValue('sp_reduction')); 977 $reduction_tax = Tools::getValue('sp_reduction_tax'); 978 $reduction_type = !$reduction ? 'amount' : Tools::getValue('sp_reduction_type'); 979 $reduction_type = $reduction_type == '-' ? 'amount' : $reduction_type; 980 $from = Tools::getValue('sp_from'); 981 if (!$from) { 982 $from = '0000-00-00 00:00:00'; 983 } 984 $to = Tools::getValue('sp_to'); 985 if (!$to) { 986 $to = '0000-00-00 00:00:00'; 987 } 988 989 if (($price == '-1') && ((float) $reduction == '0')) { 990 $this->errors[] = $this->trans('No reduction value has been submitted', [], 'Admin.Catalog.Notification'); 991 } elseif ($to != '0000-00-00 00:00:00' && strtotime($to) < strtotime($from)) { 992 $this->errors[] = $this->trans('Invalid date range', [], 'Admin.Notifications.Error'); 993 } elseif ($reduction_type == 'percentage' && ((float) $reduction <= 0 || (float) $reduction > 100)) { 994 $this->errors[] = $this->trans('Submitted reduction value (0-100) is out-of-range', [], 'Admin.Catalog.Notification'); 995 } elseif ($this->_validateSpecificPrice($id_shop, $id_currency, $id_country, $id_group, $id_customer, $price, $from_quantity, $reduction, $reduction_type, $from, $to, $id_product_attribute)) { 996 $specificPrice = new SpecificPrice(); 997 $specificPrice->id_product = (int) $id_product; 998 $specificPrice->id_product_attribute = (int) $id_product_attribute; 999 $specificPrice->id_shop = (int) $id_shop; 1000 $specificPrice->id_currency = (int) ($id_currency); 1001 $specificPrice->id_country = (int) ($id_country); 1002 $specificPrice->id_group = (int) ($id_group); 1003 $specificPrice->id_customer = (int) $id_customer; 1004 $specificPrice->price = (float) ($price); 1005 $specificPrice->from_quantity = (int) ($from_quantity); 1006 $specificPrice->reduction = (float) ($reduction_type == 'percentage' ? $reduction / 100 : $reduction); 1007 $specificPrice->reduction_tax = $reduction_tax; 1008 $specificPrice->reduction_type = $reduction_type; 1009 $specificPrice->from = $from; 1010 $specificPrice->to = $to; 1011 if (!$specificPrice->add()) { 1012 $this->errors[] = $this->trans('An error occurred while updating the specific price.', [], 'Admin.Catalog.Notification'); 1013 } 1014 } 1015 } 1016 1017 public function ajaxProcessDeleteSpecificPrice() 1018 { 1019 if ($this->access('delete')) { 1020 $id_specific_price = (int) Tools::getValue('id_specific_price'); 1021 if (!$id_specific_price || !Validate::isUnsignedId($id_specific_price)) { 1022 $error = $this->trans('The specific price ID is invalid.', [], 'Admin.Catalog.Notification'); 1023 } else { 1024 $specificPrice = new SpecificPrice((int) $id_specific_price); 1025 if (!$specificPrice->delete()) { 1026 $error = $this->trans('An error occurred while attempting to delete the specific price.', [], 'Admin.Catalog.Notification'); 1027 } 1028 } 1029 } else { 1030 $error = $this->trans('You do not have permission to delete this.', [], 'Admin.Notifications.Error'); 1031 } 1032 1033 if (isset($error)) { 1034 $json = [ 1035 'status' => 'error', 1036 'message' => $error, 1037 ]; 1038 } else { 1039 $json = [ 1040 'status' => 'ok', 1041 'message' => $this->_conf[1], 1042 ]; 1043 } 1044 1045 die(json_encode($json)); 1046 } 1047 1048 public function processSpecificPricePriorities() 1049 { 1050 if (!($obj = $this->loadObject())) { 1051 return; 1052 } 1053 if (!$priorities = Tools::getValue('specificPricePriority')) { 1054 $this->errors[] = $this->trans('Please specify priorities.', [], 'Admin.Catalog.Notification'); 1055 } elseif (Tools::isSubmit('specificPricePriorityToAll') && Tools::getValue('specificPricePriorityToAll')) { 1056 if (!SpecificPrice::setPriorities($priorities)) { 1057 $this->errors[] = $this->trans('An error occurred while updating priorities.', [], 'Admin.Catalog.Notification'); 1058 } else { 1059 $this->confirmations[] = 'The price rule has successfully updated'; 1060 } 1061 } elseif (!SpecificPrice::setSpecificPriority((int) $obj->id, $priorities)) { 1062 $this->errors[] = $this->trans('An error occurred while setting priorities.', [], 'Admin.Catalog.Notification'); 1063 } 1064 } 1065 1066 public function processCustomizationConfiguration() 1067 { 1068 $product = $this->object; 1069 // Get the number of existing customization fields ($product->text_fields is the updated value, not the existing value) 1070 $current_customization = $product->getCustomizationFieldIds(); 1071 $files_count = 0; 1072 $text_count = 0; 1073 if (is_array($current_customization)) { 1074 foreach ($current_customization as $field) { 1075 if ($field['type'] == Product::CUSTOMIZE_TEXTFIELD) { 1076 ++$text_count; 1077 } else { 1078 ++$files_count; 1079 } 1080 } 1081 } 1082 1083 if (!$product->createLabels((int) $product->uploadable_files - $files_count, (int) $product->text_fields - $text_count)) { 1084 $this->errors[] = $this->trans('An error occurred while creating customization fields.', [], 'Admin.Catalog.Notification'); 1085 } 1086 if (!count($this->errors) && !$product->updateLabels()) { 1087 $this->errors[] = $this->trans('An error occurred while updating customization fields.', [], 'Admin.Catalog.Notification'); 1088 } 1089 $product->customizable = ($product->uploadable_files > 0 || $product->text_fields > 0) ? 1 : 0; 1090 if (($product->uploadable_files != $files_count || $product->text_fields != $text_count) && !count($this->errors) && !$product->update()) { 1091 $this->errors[] = $this->trans('An error occurred while updating the custom configuration.', [], 'Admin.Catalog.Notification'); 1092 } 1093 } 1094 1095 public function processProductCustomization() 1096 { 1097 if (Validate::isLoadedObject($product = new Product((int) Tools::getValue('id_product')))) { 1098 foreach ($_POST as $field => $value) { 1099 if (strncmp($field, 'label_', 6) == 0 && !Validate::isLabel($value)) { 1100 $this->errors[] = $this->trans('The label fields defined are invalid.', [], 'Admin.Catalog.Notification'); 1101 } 1102 } 1103 if (empty($this->errors) && !$product->updateLabels()) { 1104 $this->errors[] = $this->trans('An error occurred while updating customization fields.', [], 'Admin.Catalog.Notification'); 1105 } 1106 if (empty($this->errors)) { 1107 $this->confirmations[] = 'Update successful'; 1108 } 1109 } else { 1110 $this->errors[] = $this->trans('A product must be created before adding customization.', [], 'Admin.Catalog.Notification'); 1111 } 1112 } 1113 1114 /** 1115 * Overrides parent for custom redirect link. 1116 */ 1117 public function processPosition() 1118 { 1119 /** @var Product $object */ 1120 if (!Validate::isLoadedObject($object = $this->loadObject())) { 1121 $this->errors[] = $this->trans('An error occurred while updating the status for an object.', [], 'Admin.Notifications.Error') . 1122 ' <b>' . $this->table . '</b> ' . $this->trans('(cannot load object)', [], 'Admin.Notifications.Error'); 1123 } elseif (!$object->updatePosition((int) Tools::getValue('way'), (int) Tools::getValue('position'))) { 1124 $this->errors[] = $this->trans('Failed to update the position.', [], 'Admin.Notifications.Error'); 1125 } else { 1126 $category = new Category((int) Tools::getValue('id_category')); 1127 if (Validate::isLoadedObject($category)) { 1128 Hook::exec('actionCategoryUpdate', ['category' => $category]); 1129 } 1130 $this->redirect_after = self::$currentIndex . '&' . $this->table . 'Orderby=position&' . $this->table . 'Orderway=asc&action=Customization&conf=5' . (($id_category = (Tools::getIsset('id_category') ? (int) Tools::getValue('id_category') : '')) ? ('&id_category=' . $id_category) : '') . '&token=' . Tools::getAdminTokenLite('AdminProducts'); 1131 } 1132 } 1133 1134 public function initProcess() 1135 { 1136 if (Tools::isSubmit('submitAddproductAndStay') || Tools::isSubmit('submitAddproduct')) { 1137 $this->id_object = (int) Tools::getValue('id_product'); 1138 $this->object = new Product($this->id_object); 1139 1140 if ($this->isTabSubmitted('Informations') && $this->object->is_virtual && (int) Tools::getValue('type_product') != 2) { 1141 if ($id_product_download = (int) ProductDownload::getIdFromIdProduct($this->id_object)) { 1142 $product_download = new ProductDownload($id_product_download); 1143 if (!$product_download->deleteFile($id_product_download)) { 1144 $this->errors[] = $this->trans('Cannot delete file', [], 'Admin.Notifications.Error'); 1145 } 1146 } 1147 } 1148 } 1149 1150 // Delete a product in the download folder 1151 if (Tools::getValue('deleteVirtualProduct')) { 1152 if ($this->access('delete')) { 1153 $this->action = 'deleteVirtualProduct'; 1154 } else { 1155 $this->errors[] = $this->trans('You do not have permission to delete this.', [], 'Admin.Notifications.Error'); 1156 } 1157 } elseif (Tools::isSubmit('submitAddProductAndPreview')) { 1158 // Product preview 1159 $this->display = 'edit'; 1160 $this->action = 'save'; 1161 if (Tools::getValue('id_product')) { 1162 $this->id_object = Tools::getValue('id_product'); 1163 $this->object = new Product((int) Tools::getValue('id_product')); 1164 } 1165 } elseif (Tools::isSubmit('submitAttachments')) { 1166 if ($this->access('edit')) { 1167 $this->action = 'attachments'; 1168 $this->tab_display = 'attachments'; 1169 } else { 1170 $this->errors[] = $this->trans('You do not have permission to edit this.', [], 'Admin.Notifications.Error'); 1171 } 1172 } elseif (Tools::getIsset('duplicate' . $this->table)) { 1173 // Product duplication 1174 if ($this->access('add')) { 1175 $this->action = 'duplicate'; 1176 } else { 1177 $this->errors[] = $this->trans('You do not have permission to add this.', [], 'Admin.Notifications.Error'); 1178 } 1179 } elseif (Tools::getValue('id_image') && Tools::getValue('ajax')) { 1180 // Product images management 1181 if ($this->access('edit')) { 1182 $this->action = 'image'; 1183 } else { 1184 $this->errors[] = $this->trans('You do not have permission to edit this.', [], 'Admin.Notifications.Error'); 1185 } 1186 } elseif (Tools::isSubmit('submitProductAttribute')) { 1187 // Product attributes management 1188 if ($this->access('edit')) { 1189 $this->action = 'productAttribute'; 1190 } else { 1191 $this->errors[] = $this->trans('You do not have permission to edit this.', [], 'Admin.Notifications.Error'); 1192 } 1193 } elseif (Tools::isSubmit('submitFeatures') || Tools::isSubmit('submitFeaturesAndStay')) { 1194 // Product features management 1195 if ($this->access('edit')) { 1196 $this->action = 'features'; 1197 } else { 1198 $this->errors[] = $this->trans('You do not have permission to edit this.', [], 'Admin.Notifications.Error'); 1199 } 1200 } elseif (Tools::isSubmit('submitPricesModification')) { 1201 // Product specific prices management NEVER USED 1202 if ($this->access('add')) { 1203 $this->action = 'pricesModification'; 1204 } else { 1205 $this->errors[] = $this->trans('You do not have permission to add this.', [], 'Admin.Notifications.Error'); 1206 } 1207 } elseif (Tools::isSubmit('deleteSpecificPrice')) { 1208 if ($this->access('delete')) { 1209 $this->action = 'deleteSpecificPrice'; 1210 } else { 1211 $this->errors[] = $this->trans('You do not have permission to delete this.', [], 'Admin.Notifications.Error'); 1212 } 1213 } elseif (Tools::isSubmit('submitSpecificPricePriorities')) { 1214 if ($this->access('edit')) { 1215 $this->action = 'specificPricePriorities'; 1216 $this->tab_display = 'prices'; 1217 } else { 1218 $this->errors[] = $this->trans('You do not have permission to edit this.', [], 'Admin.Notifications.Error'); 1219 } 1220 } elseif (Tools::isSubmit('submitCustomizationConfiguration')) { 1221 // Customization management 1222 if ($this->access('edit')) { 1223 $this->action = 'customizationConfiguration'; 1224 $this->tab_display = 'customization'; 1225 $this->display = 'edit'; 1226 } else { 1227 $this->errors[] = $this->trans('You do not have permission to edit this.', [], 'Admin.Notifications.Error'); 1228 } 1229 } elseif (Tools::isSubmit('submitProductCustomization')) { 1230 if ($this->access('edit')) { 1231 $this->action = 'productCustomization'; 1232 $this->tab_display = 'customization'; 1233 $this->display = 'edit'; 1234 } else { 1235 $this->errors[] = $this->trans('You do not have permission to edit this.', [], 'Admin.Notifications.Error'); 1236 } 1237 } elseif (Tools::isSubmit('id_product')) { 1238 $post_max_size = Tools::getMaxUploadSize(Configuration::get('PS_LIMIT_UPLOAD_FILE_VALUE') * 1024 * 1024); 1239 if ($post_max_size && isset($_SERVER['CONTENT_LENGTH']) && $_SERVER['CONTENT_LENGTH'] && $_SERVER['CONTENT_LENGTH'] > $post_max_size) { 1240 $this->errors[] = $this->trans( 1241 '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).', 1242 [ 1243 number_format((Configuration::get('PS_LIMIT_UPLOAD_FILE_VALUE'))), 1244 ($post_max_size / 1024 / 1024), 1245 ], 1246 'Admin.Catalog.Notification' 1247 ); 1248 } 1249 } 1250 1251 if (!$this->action) { 1252 parent::initProcess(); 1253 } else { 1254 $this->id_object = (int) Tools::getValue($this->identifier); 1255 } 1256 1257 if (isset($this->available_tabs[Tools::getValue('key_tab')])) { 1258 $this->tab_display = Tools::getValue('key_tab'); 1259 } 1260 1261 // Set tab to display if not decided already 1262 if (!$this->tab_display && $this->action) { 1263 if (in_array($this->action, array_keys($this->available_tabs))) { 1264 $this->tab_display = $this->action; 1265 } 1266 } 1267 1268 // And if still not set, use default 1269 if (!$this->tab_display) { 1270 if (in_array($this->default_tab, $this->available_tabs)) { 1271 $this->tab_display = $this->default_tab; 1272 } else { 1273 $this->tab_display = key($this->available_tabs); 1274 } 1275 } 1276 } 1277 1278 /** 1279 * postProcess for new form archi (need object return). 1280 * 1281 * @return ObjectModel|false 1282 */ 1283 public function postCoreProcess() 1284 { 1285 return parent::postProcess(); 1286 } 1287 1288 /** 1289 * postProcess handle every checks before saving products information. 1290 */ 1291 public function postProcess() 1292 { 1293 if (!$this->redirect_after) { 1294 parent::postProcess(); 1295 } 1296 1297 if ($this->display == 'edit' || $this->display == 'add') { 1298 $this->addJqueryUI([ 1299 'ui.core', 1300 'ui.widget', 1301 ]); 1302 1303 $this->addjQueryPlugin([ 1304 'autocomplete', 1305 'tablednd', 1306 'thickbox', 1307 'ajaxfileupload', 1308 'date', 1309 'tagify', 1310 'select2', 1311 'validate', 1312 ]); 1313 1314 $this->addJS([ 1315 _PS_JS_DIR_ . 'admin/products.js', 1316 _PS_JS_DIR_ . 'admin/attributes.js', 1317 _PS_JS_DIR_ . 'admin/price.js', 1318 _PS_JS_DIR_ . 'tiny_mce/tiny_mce.js', 1319 _PS_JS_DIR_ . 'admin/tinymce.inc.js', 1320 _PS_JS_DIR_ . 'admin/dnd.js', 1321 _PS_JS_DIR_ . 'jquery/ui/jquery.ui.progressbar.min.js', 1322 _PS_JS_DIR_ . 'vendor/spin.js', 1323 _PS_JS_DIR_ . 'vendor/ladda.js', 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 _PS_JS_DIR_ . 'jquery/plugins/timepicker/jquery-ui-timepicker-addon.css', 1331 ]); 1332 } 1333 } 1334 1335 public function ajaxProcessDeleteProductAttribute() 1336 { 1337 if (!Combination::isFeatureActive()) { 1338 return; 1339 } 1340 1341 if ($this->access('delete')) { 1342 $id_product = (int) Tools::getValue('id_product'); 1343 $id_product_attribute = (int) Tools::getValue('id_product_attribute'); 1344 1345 if ($id_product && Validate::isUnsignedId($id_product) && Validate::isLoadedObject($product = new Product($id_product))) { 1346 if (($depends_on_stock = StockAvailable::dependsOnStock($id_product)) && StockAvailable::getQuantityAvailableByProduct($id_product, $id_product_attribute)) { 1347 $json = [ 1348 'status' => 'error', 1349 'message' => '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.', 1350 ]; 1351 } else { 1352 $product->deleteAttributeCombination((int) $id_product_attribute); 1353 $product->checkDefaultAttributes(); 1354 Tools::clearColorListCache((int) $product->id); 1355 if (!$product->hasAttributes()) { 1356 $product->cache_default_attribute = 0; 1357 $product->update(); 1358 } else { 1359 Product::updateDefaultAttribute($id_product); 1360 } 1361 1362 if ($depends_on_stock && !Stock::deleteStockByIds($id_product, $id_product_attribute)) { 1363 $json = [ 1364 'status' => 'error', 1365 'message' => 'Error while deleting the stock', 1366 ]; 1367 } else { 1368 $json = [ 1369 'status' => 'ok', 1370 'message' => $this->_conf[1], 1371 'id_product_attribute' => (int) $id_product_attribute, 1372 ]; 1373 } 1374 } 1375 } else { 1376 $json = [ 1377 'status' => 'error', 1378 'message' => 'You cannot delete this attribute.', 1379 ]; 1380 } 1381 } else { 1382 $json = [ 1383 'status' => 'error', 1384 'message' => 'You do not have permission to delete this.', 1385 ]; 1386 } 1387 1388 die(json_encode($json)); 1389 } 1390 1391 public function ajaxProcessDefaultProductAttribute() 1392 { 1393 if ($this->access('edit')) { 1394 if (!Combination::isFeatureActive()) { 1395 return; 1396 } 1397 1398 if (Validate::isLoadedObject($product = new Product((int) Tools::getValue('id_product')))) { 1399 $product->deleteDefaultAttributes(); 1400 $product->setDefaultAttribute((int) Tools::getValue('id_product_attribute')); 1401 $json = [ 1402 'status' => 'ok', 1403 'message' => $this->_conf[4], 1404 ]; 1405 } else { 1406 $json = [ 1407 'status' => 'error', 1408 'message' => 'You cannot make this the default attribute.', 1409 ]; 1410 } 1411 1412 die(json_encode($json)); 1413 } 1414 } 1415 1416 public function ajaxProcessEditProductAttribute() 1417 { 1418 if ($this->access('edit')) { 1419 $id_product = (int) Tools::getValue('id_product'); 1420 $id_product_attribute = (int) Tools::getValue('id_product_attribute'); 1421 if ($id_product && Validate::isUnsignedId($id_product) && Validate::isLoadedObject($product = new Product((int) $id_product))) { 1422 $combinations = $product->getAttributeCombinationsById($id_product_attribute, $this->context->language->id); 1423 foreach ($combinations as $key => $combination) { 1424 $combinations[$key]['attributes'][] = [$combination['group_name'], $combination['attribute_name'], $combination['id_attribute']]; 1425 } 1426 1427 die(json_encode($combinations)); 1428 } 1429 } 1430 } 1431 1432 public function ajaxPreProcess() 1433 { 1434 if (Tools::getIsset('update' . $this->table) && Tools::getIsset('id_' . $this->table)) { 1435 $this->display = 'edit'; 1436 $this->action = Tools::getValue('action'); 1437 } 1438 } 1439 1440 public function ajaxProcessUpdateProductImageShopAsso() 1441 { 1442 $id_product = Tools::getValue('id_product'); 1443 if (($id_image = Tools::getValue('id_image')) && ($id_shop = (int) Tools::getValue('id_shop'))) { 1444 if (Tools::getValue('active') == 'true') { 1445 $res = Db::getInstance()->execute('INSERT INTO ' . _DB_PREFIX_ . 'image_shop (`id_product`, `id_image`, `id_shop`, `cover`) VALUES(' . (int) $id_product . ', ' . (int) $id_image . ', ' . (int) $id_shop . ', NULL)'); 1446 } else { 1447 $res = Db::getInstance()->execute('DELETE FROM ' . _DB_PREFIX_ . 'image_shop WHERE `id_image` = ' . (int) $id_image . ' AND `id_shop` = ' . (int) $id_shop); 1448 } 1449 } 1450 1451 // Clean covers in image table 1452 $count_cover_image = Db::getInstance()->getValue(' 1453 SELECT COUNT(*) FROM ' . _DB_PREFIX_ . 'image i 1454 INNER JOIN ' . _DB_PREFIX_ . 'image_shop ish ON (i.id_image = ish.id_image AND ish.id_shop = ' . (int) $id_shop . ') 1455 WHERE i.cover = 1 AND i.`id_product` = ' . (int) $id_product); 1456 1457 if (!$id_image) { 1458 $id_image = Db::getInstance()->getValue(' 1459 SELECT i.`id_image` FROM ' . _DB_PREFIX_ . 'image i 1460 INNER JOIN ' . _DB_PREFIX_ . 'image_shop ish ON (i.id_image = ish.id_image AND ish.id_shop = ' . (int) $id_shop . ') 1461 WHERE i.`id_product` = ' . (int) $id_product); 1462 } 1463 1464 if ($count_cover_image < 1) { 1465 Db::getInstance()->execute('UPDATE ' . _DB_PREFIX_ . 'image i SET i.cover = 1 WHERE i.id_image = ' . (int) $id_image . ' AND i.`id_product` = ' . (int) $id_product . ' LIMIT 1'); 1466 } 1467 1468 // Clean covers in image_shop table 1469 $count_cover_image_shop = Db::getInstance()->getValue(' 1470 SELECT COUNT(*) 1471 FROM ' . _DB_PREFIX_ . 'image_shop ish 1472 WHERE ish.`id_product` = ' . (int) $id_product . ' AND ish.id_shop = ' . (int) $id_shop . ' AND ish.cover = 1'); 1473 1474 if ($count_cover_image_shop < 1) { 1475 Db::getInstance()->execute('UPDATE ' . _DB_PREFIX_ . 'image_shop ish SET ish.cover = 1 WHERE ish.id_image = ' . (int) $id_image . ' AND ish.`id_product` = ' . (int) $id_product . ' AND ish.id_shop = ' . (int) $id_shop . ' LIMIT 1'); 1476 } 1477 1478 if ($res) { 1479 $this->jsonConfirmation($this->_conf[27]); 1480 } else { 1481 $this->jsonError($this->trans('An error occurred while attempting to associate this image with your shop. ', [], 'Admin.Catalog.Notification')); 1482 } 1483 } 1484 1485 public function ajaxProcessUpdateImagePosition() 1486 { 1487 if (!$this->access('edit')) { 1488 return die(json_encode(['error' => 'You do not have the right permission'])); 1489 } 1490 $res = false; 1491 if ($json = Tools::getValue('json')) { 1492 $res = true; 1493 $json = stripslashes($json); 1494 $images = json_decode($json, true); 1495 foreach ($images as $id => $position) { 1496 $img = new Image((int) $id); 1497 $img->position = (int) $position; 1498 $res &= $img->update(); 1499 } 1500 } 1501 if ($res) { 1502 $this->jsonConfirmation($this->_conf[25]); 1503 } else { 1504 $this->jsonError($this->trans('An error occurred while attempting to move this picture.', [], 'Admin.Catalog.Notification')); 1505 } 1506 } 1507 1508 public function ajaxProcessUpdateCover() 1509 { 1510 if (!$this->access('edit')) { 1511 return die(json_encode(['error' => 'You do not have the right permission'])); 1512 } 1513 Image::deleteCover((int) Tools::getValue('id_product')); 1514 $img = new Image((int) Tools::getValue('id_image')); 1515 $img->cover = 1; 1516 1517 @unlink(_PS_TMP_IMG_DIR_ . 'product_' . (int) $img->id_product . '.jpg'); 1518 @unlink(_PS_TMP_IMG_DIR_ . 'product_mini_' . (int) $img->id_product . '_' . $this->context->shop->id . '.jpg'); 1519 1520 if ($img->update()) { 1521 $this->jsonConfirmation($this->_conf[26]); 1522 } else { 1523 $this->jsonError($this->trans('An error occurred while attempting to update the cover picture.', [], 'Admin.Catalog.Notification')); 1524 } 1525 } 1526 1527 public function ajaxProcessDeleteProductImage($id_image = null) 1528 { 1529 $this->display = 'content'; 1530 $res = true; 1531 /* Delete product image */ 1532 $id_image = $id_image ? $id_image : (int) Tools::getValue('id_image'); 1533 1534 $image = new Image($id_image); 1535 $this->content['id'] = $image->id; 1536 $res &= $image->delete(); 1537 // if deleted image was the cover, change it to the first one 1538 if (!Image::getCover($image->id_product)) { 1539 $res &= Db::getInstance()->execute(' 1540 UPDATE `' . _DB_PREFIX_ . 'image_shop` image_shop 1541 SET image_shop.`cover` = 1 1542 WHERE image_shop.`id_product` = ' . (int) $image->id_product . ' 1543 AND id_shop=' . (int) $this->context->shop->id . ' LIMIT 1'); 1544 } 1545 1546 if (!Image::getGlobalCover($image->id_product)) { 1547 $res &= Db::getInstance()->execute(' 1548 UPDATE `' . _DB_PREFIX_ . 'image` i 1549 SET i.`cover` = 1 1550 WHERE i.`id_product` = ' . (int) $image->id_product . ' LIMIT 1'); 1551 } 1552 1553 if (file_exists(_PS_TMP_IMG_DIR_ . 'product_' . $image->id_product . '.jpg')) { 1554 $res &= @unlink(_PS_TMP_IMG_DIR_ . 'product_' . $image->id_product . '.jpg'); 1555 } 1556 if (file_exists(_PS_TMP_IMG_DIR_ . 'product_mini_' . $image->id_product . '_' . $this->context->shop->id . '.jpg')) { 1557 $res &= @unlink(_PS_TMP_IMG_DIR_ . 'product_mini_' . $image->id_product . '_' . $this->context->shop->id . '.jpg'); 1558 } 1559 1560 if ($res) { 1561 $this->jsonConfirmation($this->_conf[7]); 1562 } else { 1563 $this->jsonError($this->trans('An error occurred while attempting to delete the product image.', [], 'Admin.Catalog.Notification')); 1564 } 1565 } 1566 1567 protected function _validateSpecificPrice($id_shop, $id_currency, $id_country, $id_group, $id_customer, $price, $from_quantity, $reduction, $reduction_type, $from, $to, $id_combination = 0) 1568 { 1569 if (!Validate::isUnsignedId($id_shop) || !Validate::isUnsignedId($id_currency) || !Validate::isUnsignedId($id_country) || !Validate::isUnsignedId($id_group) || !Validate::isUnsignedId($id_customer)) { 1570 $this->errors[] = $this->trans('Wrong IDs', [], 'Admin.Catalog.Notification'); 1571 } elseif ((!isset($price) && !isset($reduction)) || (isset($price) && !Validate::isNegativePrice($price)) || (isset($reduction) && !Validate::isPrice($reduction))) { 1572 $this->errors[] = $this->trans('Invalid price/discount amount', [], 'Admin.Catalog.Notification'); 1573 } elseif (!Validate::isUnsignedInt($from_quantity)) { 1574 $this->errors[] = $this->trans('Invalid quantity', [], 'Admin.Catalog.Notification'); 1575 } elseif ($reduction && !Validate::isReductionType($reduction_type)) { 1576 $this->errors[] = $this->trans('Please select a discount type (amount or percentage).', [], 'Admin.Catalog.Notification'); 1577 } elseif ($from && $to && (!Validate::isDateFormat($from) || !Validate::isDateFormat($to))) { 1578 $this->errors[] = $this->trans('The from/to date is invalid.', [], 'Admin.Catalog.Notification'); 1579 } elseif (SpecificPrice::exists((int) $this->object->id, $id_combination, $id_shop, $id_group, $id_country, $id_currency, $id_customer, $from_quantity, $from, $to, false)) { 1580 $this->errors[] = $this->trans('A specific price already exists for these parameters.', [], 'Admin.Catalog.Notification'); 1581 } else { 1582 return true; 1583 } 1584 1585 return false; 1586 } 1587 1588 /** 1589 * Checking customs feature. 1590 * 1591 * @param array $languages 1592 * @param array $featureInfo 1593 * 1594 * @return int|string 1595 */ 1596 protected function checkFeatures($languages, $featureInfo) 1597 { 1598 $rules = call_user_func(['FeatureValue', 'getValidationRules'], 'FeatureValue'); 1599 $feature = Feature::getFeature((int) Configuration::get('PS_LANG_DEFAULT'), $featureInfo['feature']); 1600 1601 foreach ($languages as $language) { 1602 if (isset($featureInfo['custom_value'][$language['id_lang']])) { 1603 $val = $featureInfo['custom_value'][$language['id_lang']]; 1604 $current_language = new Language($language['id_lang']); 1605 if (Tools::strlen($val) > $rules['sizeLang']['value']) { 1606 $this->errors[] = $this->trans( 1607 'The name for feature %1$s is too long in %2$s.', 1608 [ 1609 ' <b>' . $feature['name'] . '</b>', 1610 $current_language->name, 1611 ], 1612 'Admin.Catalog.Notification' 1613 ); 1614 } elseif (!call_user_func(['Validate', $rules['validateLang']['value']], $val)) { 1615 $this->errors[] = $this->trans( 1616 'A valid name required for feature. %1$s in %2$s.', 1617 [ 1618 ' <b>' . $feature['name'] . '</b>', 1619 $current_language->name, 1620 ], 1621 'Admin.Catalog.Notification' 1622 ); 1623 } 1624 if (count($this->errors)) { 1625 return 0; 1626 } 1627 // Getting default language 1628 if ($language['id_lang'] == Configuration::get('PS_LANG_DEFAULT')) { 1629 return $val; 1630 } 1631 } 1632 } 1633 1634 return 0; 1635 } 1636 1637 /** 1638 * Add or update a product image. 1639 * 1640 * @param Product $product Product object to add image 1641 * @param string $method 1642 * 1643 * @return int|false 1644 */ 1645 public function addProductImage($product, $method = 'auto') 1646 { 1647 /* Updating an existing product image */ 1648 if ($id_image = (int) Tools::getValue('id_image')) { 1649 $image = new Image((int) $id_image); 1650 if (!Validate::isLoadedObject($image)) { 1651 $this->errors[] = $this->trans('An error occurred while uploading the image.', [], 'Admin.Notifications.Error'); 1652 } else { 1653 if (($cover = Tools::getValue('cover')) == 1) { 1654 Image::deleteCover($product->id); 1655 } 1656 $image->cover = $cover; 1657 $this->validateRules('Image'); 1658 $this->copyFromPost($image, 'image'); 1659 if (count($this->errors) || !$image->update()) { 1660 $this->errors[] = $this->trans('An error occurred while updating the image.', [], 'Admin.Notifications.Error'); 1661 } elseif (isset($_FILES['image_product']['tmp_name']) && $_FILES['image_product']['tmp_name'] != null) { 1662 $this->copyImage($product->id, $image->id, $method); 1663 } 1664 } 1665 } 1666 if (isset($image) && Validate::isLoadedObject($image) && !file_exists(_PS_PROD_IMG_DIR_ . $image->getExistingImgPath() . '.' . $image->image_format)) { 1667 $image->delete(); 1668 } 1669 if (count($this->errors)) { 1670 return false; 1671 } 1672 @unlink(_PS_TMP_IMG_DIR_ . 'product_' . $product->id . '.jpg'); 1673 @unlink(_PS_TMP_IMG_DIR_ . 'product_mini_' . $product->id . '_' . $this->context->shop->id . '.jpg'); 1674 1675 return (isset($id_image) && is_int($id_image) && $id_image) ? $id_image : false; 1676 } 1677 1678 /** 1679 * Copy a product image. 1680 * 1681 * @param int $id_product Product Id for product image filename 1682 * @param int $id_image Image Id for product image filename 1683 * @param string $method 1684 * 1685 * @return void|false 1686 * 1687 * @throws PrestaShopException 1688 */ 1689 public function copyImage($id_product, $id_image, $method = 'auto') 1690 { 1691 if (!isset($_FILES['image_product']['tmp_name'])) { 1692 return false; 1693 } 1694 if ($error = ImageManager::validateUpload($_FILES['image_product'])) { 1695 $this->errors[] = $error; 1696 } else { 1697 $image = new Image($id_image); 1698 1699 if (!$new_path = $image->getPathForCreation()) { 1700 $this->errors[] = $this->trans('An error occurred while attempting to create a new folder.', [], 'Admin.Notifications.Error'); 1701 } 1702 if (!($tmpName = tempnam(_PS_TMP_IMG_DIR_, 'PS')) || !move_uploaded_file($_FILES['image_product']['tmp_name'], $tmpName)) { 1703 $this->errors[] = $this->trans('An error occurred while uploading the image.', [], 'Admin.Notifications.Error'); 1704 } elseif (!ImageManager::resize($tmpName, $new_path . '.' . $image->image_format)) { 1705 $this->errors[] = $this->trans('An error occurred while copying the image.', [], 'Admin.Notifications.Error'); 1706 } elseif ($method == 'auto') { 1707 $imagesTypes = ImageType::getImagesTypes('products'); 1708 foreach ($imagesTypes as $k => $image_type) { 1709 if (!ImageManager::resize($tmpName, $new_path . '-' . stripslashes($image_type['name']) . '.' . $image->image_format, $image_type['width'], $image_type['height'], $image->image_format)) { 1710 $this->errors[] = $this->trans('An error occurred while copying this image: %s', [stripslashes($image_type['name'])], 'Admin.Notifications.Error'); 1711 } 1712 } 1713 } 1714 1715 @unlink($tmpName); 1716 Hook::exec('actionWatermark', ['id_image' => $id_image, 'id_product' => $id_product]); 1717 } 1718 } 1719 1720 protected function updateAssoShop($id_object) 1721 { 1722 //override AdminController::updateAssoShop() specifically for products because shop association is set with the context in ObjectModel 1723 } 1724 1725 public function processAdd() 1726 { 1727 $this->checkProduct(); 1728 1729 if (!empty($this->errors)) { 1730 $this->display = 'add'; 1731 1732 return false; 1733 } 1734 1735 $this->object = new $this->className(); 1736 $this->_removeTaxFromEcotax(); 1737 $this->copyFromPost($this->object, $this->table); 1738 if ($this->object->add()) { 1739 PrestaShopLogger::addLog(sprintf('%s addition', $this->className), 1, null, $this->className, (int) $this->object->id, true, (int) $this->context->employee->id); 1740 $this->addCarriers($this->object); 1741 $this->updateAccessories($this->object); 1742 $this->updatePackItems($this->object); 1743 $this->updateDownloadProduct($this->object); 1744 1745 if (Configuration::get('PS_FORCE_ASM_NEW_PRODUCT') && Configuration::get('PS_ADVANCED_STOCK_MANAGEMENT') && $this->object->getType() != Product::PTYPE_VIRTUAL) { 1746 $this->object->advanced_stock_management = 1; 1747 $this->object->save(); 1748 $id_shops = Shop::getContextListShopID(); 1749 foreach ($id_shops as $id_shop) { 1750 StockAvailable::setProductDependsOnStock($this->object->id, true, (int) $id_shop, 0); 1751 } 1752 } 1753 1754 if (empty($this->errors)) { 1755 $languages = Language::getLanguages(false); 1756 if ($this->isProductFieldUpdated('category_box') && !$this->object->updateCategories(Tools::getValue('categoryBox'))) { 1757 $this->errors[] = $this->trans( 1758 'An error occurred while linking the object %table_name% to categories.', 1759 [ 1760 '%table_name%' => ' <b>' . $this->table . '</b> ', 1761 ], 1762 'Admin.Notifications.Error' 1763 ); 1764 } elseif (!$this->updateTags($languages, $this->object)) { 1765 $this->errors[] = $this->trans('An error occurred while adding tags.', [], 'Admin.Catalog.Notification'); 1766 } else { 1767 Hook::exec('actionProductAdd', ['id_product_old' => null, 'id_product' => (int) $this->object->id, 'product' => $this->object]); 1768 if (in_array($this->object->visibility, ['both', 'search']) && Configuration::get('PS_SEARCH_INDEXATION')) { 1769 Search::indexation(false, $this->object->id); 1770 } 1771 } 1772 1773 if (Configuration::get('PS_DEFAULT_WAREHOUSE_NEW_PRODUCT') != 0 && Configuration::get('PS_ADVANCED_STOCK_MANAGEMENT')) { 1774 $warehouse_location_entity = new WarehouseProductLocation(); 1775 $warehouse_location_entity->id_product = $this->object->id; 1776 $warehouse_location_entity->id_product_attribute = 0; 1777 $warehouse_location_entity->id_warehouse = Configuration::get('PS_DEFAULT_WAREHOUSE_NEW_PRODUCT'); 1778 $warehouse_location_entity->location = pSQL(''); 1779 $warehouse_location_entity->save(); 1780 } 1781 1782 // Apply groups reductions 1783 $this->object->setGroupReduction(); 1784 1785 // Save and preview 1786 if (Tools::isSubmit('submitAddProductAndPreview')) { 1787 $this->redirect_after = $this->getPreviewUrl($this->object); 1788 } 1789 1790 // Save and stay on same form 1791 if ($this->display == 'edit') { 1792 $this->redirect_after = self::$currentIndex . '&id_product=' . (int) $this->object->id 1793 . (Tools::getIsset('id_category') ? '&id_category=' . (int) Tools::getValue('id_category') : '') 1794 . '&updateproduct&conf=3&key_tab=' . Tools::safeOutput(Tools::getValue('key_tab')) . '&token=' . $this->token; 1795 } else { 1796 // Default behavior (save and back) 1797 $this->redirect_after = self::$currentIndex 1798 . (Tools::getIsset('id_category') ? '&id_category=' . (int) Tools::getValue('id_category') : '') 1799 . '&conf=3&token=' . $this->token; 1800 } 1801 } else { 1802 $this->object->delete(); 1803 // if errors : stay on edit page 1804 $this->display = 'edit'; 1805 } 1806 } else { 1807 $this->errors[] = $this->trans('An error occurred while creating an object.', [], 'Admin.Notifications.Error') . ' <b>' . $this->table . '</b>'; 1808 } 1809 1810 return $this->object; 1811 } 1812 1813 protected function isTabSubmitted($tab_name) 1814 { 1815 if (!is_array($this->submitted_tabs)) { 1816 $this->submitted_tabs = Tools::getValue('submitted_tabs'); 1817 } 1818 1819 if (is_array($this->submitted_tabs) && in_array($tab_name, $this->submitted_tabs)) { 1820 return true; 1821 } 1822 1823 return false; 1824 } 1825 1826 public function processStatus() 1827 { 1828 $this->loadObject(true); 1829 if (!Validate::isLoadedObject($this->object)) { 1830 return false; 1831 } 1832 if (($error = $this->object->validateFields(false, true)) !== true) { 1833 $this->errors[] = $error; 1834 } 1835 if (($error = $this->object->validateFieldsLang(false, true)) !== true) { 1836 $this->errors[] = $error; 1837 } 1838 1839 if (count($this->errors)) { 1840 return false; 1841 } 1842 1843 $res = parent::processStatus(); 1844 1845 $query = trim(Tools::getValue('bo_query')); 1846 $searchType = (int) Tools::getValue('bo_search_type'); 1847 1848 if ($query) { 1849 $this->redirect_after = preg_replace('/[\?|&](bo_query|bo_search_type)=([^&]*)/i', '', $this->redirect_after); 1850 $this->redirect_after .= '&bo_query=' . $query . '&bo_search_type=' . $searchType; 1851 } 1852 1853 return $res; 1854 } 1855 1856 public function processUpdate() 1857 { 1858 $existing_product = $this->object; 1859 1860 $this->checkProduct(); 1861 1862 if (!empty($this->errors)) { 1863 $this->display = 'edit'; 1864 1865 return false; 1866 } 1867 1868 $id = (int) Tools::getValue('id_' . $this->table); 1869 /* Update an existing product */ 1870 if (isset($id) && !empty($id)) { 1871 /** @var Product $object */ 1872 $object = new $this->className((int) $id); 1873 $this->object = $object; 1874 1875 if (Validate::isLoadedObject($object)) { 1876 $this->_removeTaxFromEcotax(); 1877 $product_type_before = $object->getType(); 1878 $this->copyFromPost($object, $this->table); 1879 $object->indexed = 0; 1880 1881 if (Shop::isFeatureActive() && Shop::getContext() != Shop::CONTEXT_SHOP) { 1882 $object->setFieldsToUpdate((array) Tools::getValue('multishop_check', [])); 1883 } 1884 1885 // Duplicate combinations if not associated to shop 1886 if ($this->context->shop->getContext() == Shop::CONTEXT_SHOP && !$object->isAssociatedToShop()) { 1887 $is_associated_to_shop = false; 1888 $combinations = Product::getProductAttributesIds($object->id); 1889 if ($combinations) { 1890 foreach ($combinations as $id_combination) { 1891 $combination = new Combination((int) $id_combination['id_product_attribute']); 1892 $default_combination = new Combination((int) $id_combination['id_product_attribute'], null, (int) $this->object->id_shop_default); 1893 1894 $def = ObjectModel::getDefinition($default_combination); 1895 foreach ($def['fields'] as $field_name => $row) { 1896 $combination->$field_name = ObjectModel::formatValue($default_combination->$field_name, $def['fields'][$field_name]['type']); 1897 } 1898 1899 $combination->save(); 1900 } 1901 } 1902 } else { 1903 $is_associated_to_shop = true; 1904 } 1905 1906 if ($object->update()) { 1907 // If the product doesn't exist in the current shop but exists in another shop 1908 if (Shop::getContext() == Shop::CONTEXT_SHOP && !$existing_product->isAssociatedToShop($this->context->shop->id)) { 1909 $out_of_stock = StockAvailable::outOfStock($existing_product->id, $existing_product->id_shop_default); 1910 $depends_on_stock = StockAvailable::dependsOnStock($existing_product->id, $existing_product->id_shop_default); 1911 StockAvailable::setProductOutOfStock((int) $this->object->id, $out_of_stock, $this->context->shop->id); 1912 StockAvailable::setProductDependsOnStock((int) $this->object->id, $depends_on_stock, $this->context->shop->id); 1913 } 1914 1915 PrestaShopLogger::addLog(sprintf('%s modification', $this->className), 1, null, $this->className, (int) $this->object->id, true, (int) $this->context->employee->id); 1916 if (in_array($this->context->shop->getContext(), [Shop::CONTEXT_SHOP, Shop::CONTEXT_ALL])) { 1917 if ($this->isTabSubmitted('Shipping')) { 1918 $this->addCarriers(); 1919 } 1920 if ($this->isTabSubmitted('Associations')) { 1921 $this->updateAccessories($object); 1922 } 1923 if ($this->isTabSubmitted('Suppliers')) { 1924 $this->processSuppliers(); 1925 } 1926 if ($this->isTabSubmitted('Features')) { 1927 $this->processFeatures(); 1928 } 1929 if ($this->isTabSubmitted('Combinations')) { 1930 $this->processProductAttribute(); 1931 } 1932 if ($this->isTabSubmitted('Prices')) { 1933 $this->processPriceAddition(); 1934 $this->processSpecificPricePriorities(); 1935 } 1936 if ($this->isTabSubmitted('Customization')) { 1937 $this->processCustomizationConfiguration(); 1938 } 1939 if ($this->isTabSubmitted('Attachments')) { 1940 $this->processAttachments(); 1941 } 1942 if ($this->isTabSubmitted('Images')) { 1943 $this->processImageLegends(); 1944 } 1945 1946 $this->updatePackItems($object); 1947 // Disallow avanced stock management if the product become a pack 1948 if ($product_type_before == Product::PTYPE_SIMPLE && $object->getType() == Product::PTYPE_PACK) { 1949 StockAvailable::setProductDependsOnStock((int) $object->id, false); 1950 } 1951 $this->updateDownloadProduct($object, 1); 1952 $this->updateTags(Language::getLanguages(false), $object); 1953 1954 if ($this->isProductFieldUpdated('category_box') && !$object->updateCategories(Tools::getValue('categoryBox'))) { 1955 $this->errors[] = $this->trans( 1956 'An error occurred while linking the object %table_name% to categories.', 1957 [ 1958 '%table_name%' => ' <b>' . $this->table . '</b> ', 1959 ], 1960 'Admin.Notifications.Error' 1961 ); 1962 } 1963 } 1964 1965 if ($this->isTabSubmitted('Warehouses')) { 1966 $this->processWarehouses(); 1967 } 1968 if (empty($this->errors)) { 1969 if (in_array($object->visibility, ['both', 'search']) && Configuration::get('PS_SEARCH_INDEXATION')) { 1970 Search::indexation(false, $object->id); 1971 } 1972 1973 // Save and preview 1974 if (Tools::isSubmit('submitAddProductAndPreview')) { 1975 $this->redirect_after = $this->getPreviewUrl($object); 1976 } else { 1977 $page = (int) Tools::getValue('page'); 1978 // Save and stay on same form 1979 if ($this->display == 'edit') { 1980 $this->confirmations[] = 'Update successful'; 1981 $this->redirect_after = self::$currentIndex . '&id_product=' . (int) $this->object->id 1982 . (Tools::getIsset('id_category') ? '&id_category=' . (int) Tools::getValue('id_category') : '') 1983 . '&updateproduct&conf=4&key_tab=' . Tools::safeOutput(Tools::getValue('key_tab')) . ($page > 1 ? '&page=' . (int) $page : '') . '&token=' . $this->token; 1984 } else { 1985 // Default behavior (save and back) 1986 $this->redirect_after = self::$currentIndex . (Tools::getIsset('id_category') ? '&id_category=' . (int) Tools::getValue('id_category') : '') . '&conf=4' . ($page > 1 ? '&submitFilterproduct=' . (int) $page : '') . '&token=' . $this->token; 1987 } 1988 } 1989 } else { 1990 // if errors: stay on edit page 1991 $this->display = 'edit'; 1992 } 1993 } else { 1994 if (!$is_associated_to_shop && $combinations) { 1995 foreach ($combinations as $id_combination) { 1996 $combination = new Combination((int) $id_combination['id_product_attribute']); 1997 $combination->delete(); 1998 } 1999 } 2000 $this->errors[] = $this->trans('An error occurred while updating an object.', [], 'Admin.Notifications.Error') . ' <b>' . $this->table . '</b> (' . Db::getInstance()->getMsgError() . ')'; 2001 } 2002 } else { 2003 $this->errors[] = $this->trans('An error occurred while updating an object.', [], 'Admin.Notifications.Error') . ' <b>' . $this->table . '</b> (' . $this->trans('The object cannot be loaded. ', [], 'Admin.Notifications.Error') . ')'; 2004 } 2005 2006 return $object; 2007 } 2008 } 2009 2010 /** 2011 * Check that a saved product is valid. 2012 */ 2013 public function checkProduct() 2014 { 2015 /** @todo : the call_user_func seems to contains only statics values (className = 'Product') */ 2016 $rules = call_user_func([$this->className, 'getValidationRules'], $this->className); 2017 $default_language = new Language((int) Configuration::get('PS_LANG_DEFAULT')); 2018 $languages = Language::getLanguages(false); 2019 2020 // Check required fields 2021 foreach ($rules['required'] as $field) { 2022 if (!$this->isProductFieldUpdated($field)) { 2023 continue; 2024 } 2025 2026 if (($value = Tools::getValue($field)) == false && $value != '0') { 2027 if (Tools::getValue('id_' . $this->table) && $field == 'passwd') { 2028 continue; 2029 } 2030 $this->errors[] = $this->trans('The %name% field is required.', ['%name%' => call_user_func([$this->className, 'displayFieldName'], $field, $this->className)], 'Admin.Notifications.Error'); 2031 } 2032 } 2033 2034 // Check multilingual required fields 2035 foreach ($rules['requiredLang'] as $fieldLang) { 2036 if ($this->isProductFieldUpdated($fieldLang, $default_language->id) && !Tools::getValue($fieldLang . '_' . $default_language->id)) { 2037 $this->errors[] = $this->trans( 2038 'This %1$s field is required at least in %2$s', 2039 [ 2040 call_user_func([$this->className, 'displayFieldName'], $fieldLang, $this->className), 2041 $default_language->name, 2042 ], 2043 'Admin.Catalog.Notification' 2044 ); 2045 } 2046 } 2047 2048 // Check fields sizes 2049 foreach ($rules['size'] as $field => $maxLength) { 2050 if ($this->isProductFieldUpdated($field) && ($value = Tools::getValue($field)) && Tools::strlen($value) > $maxLength) { 2051 $this->errors[] = $this->trans( 2052 'The %1$s field is too long (%2$d chars max).', 2053 [ 2054 call_user_func([$this->className, 'displayFieldName'], $field, $this->className), 2055 $maxLength, 2056 ], 2057 'Admin.Catalog.Notification' 2058 ); 2059 } 2060 } 2061 2062 if (Tools::getIsset('description_short') && $this->isProductFieldUpdated('description_short')) { 2063 $saveShort = Tools::getValue('description_short'); 2064 $_POST['description_short'] = strip_tags(Tools::getValue('description_short')); 2065 } 2066 2067 // Check description short size without html 2068 $limit = (int) Configuration::get('PS_PRODUCT_SHORT_DESC_LIMIT'); 2069 if ($limit <= 0) { 2070 $limit = 400; 2071 } 2072 foreach ($languages as $language) { 2073 if ($this->isProductFieldUpdated('description_short', $language['id_lang']) && ($value = Tools::getValue('description_short_' . $language['id_lang']))) { 2074 // This validation computation actually comes from TinyMceMaxLengthValidator if you modify it here you 2075 // should keep the validator in sync (along with other parts of the code, more info in the 2076 // TinyMceMaxLengthValidator comments). 2077 $replaceArray = [ 2078 "\n", 2079 "\r", 2080 "\n\r", 2081 "\r\n", 2082 ]; 2083 $str = str_replace($replaceArray, [''], strip_tags($value)); 2084 $shortDescriptionLength = iconv_strlen($str); 2085 if ($shortDescriptionLength > $limit) { 2086 $this->errors[] = $this->trans( 2087 'This %1$s field (%2$s) is too long: %3$d chars max (current count %4$d).', 2088 [ 2089 call_user_func([$this->className, 'displayFieldName'], 'description_short'), 2090 $language['name'], 2091 $limit, 2092 Tools::strlen(strip_tags($value)), 2093 ], 2094 'Admin.Catalog.Notification' 2095 ); 2096 } 2097 } 2098 } 2099 2100 // Check multilingual fields sizes 2101 foreach ($rules['sizeLang'] as $fieldLang => $maxLength) { 2102 foreach ($languages as $language) { 2103 $value = Tools::getValue($fieldLang . '_' . $language['id_lang']); 2104 if ($value && Tools::strlen($value) > $maxLength) { 2105 $this->errors[] = $this->trans( 2106 'The %1$s field is too long (%2$d chars max).', 2107 [ 2108 call_user_func([$this->className, 'displayFieldName'], $fieldLang, $this->className), 2109 $maxLength, 2110 ], 2111 'Admin.Catalog.Notification' 2112 ); 2113 } 2114 } 2115 } 2116 2117 if ($this->isProductFieldUpdated('description_short') && isset($_POST['description_short'])) { 2118 $_POST['description_short'] = $saveShort; 2119 } 2120 2121 // Check fields validity 2122 foreach ($rules['validate'] as $field => $function) { 2123 if ($this->isProductFieldUpdated($field) && ($value = Tools::getValue($field))) { 2124 $res = true; 2125 if (Tools::strtolower($function) == 'iscleanhtml') { 2126 if (!Validate::$function($value, (int) Configuration::get('PS_ALLOW_HTML_IFRAME'))) { 2127 $res = false; 2128 } 2129 } elseif (!Validate::$function($value)) { 2130 $res = false; 2131 } 2132 2133 if (!$res) { 2134 $this->errors[] = $this->trans( 2135 'The %s field is invalid.', 2136 [ 2137 call_user_func([$this->className, 'displayFieldName'], $field, $this->className), 2138 ], 2139 'Admin.Notifications.Error' 2140 ); 2141 } 2142 } 2143 } 2144 // Check multilingual fields validity 2145 foreach ($rules['validateLang'] as $fieldLang => $function) { 2146 foreach ($languages as $language) { 2147 if ($this->isProductFieldUpdated($fieldLang, $language['id_lang']) && ($value = Tools::getValue($fieldLang . '_' . $language['id_lang']))) { 2148 if (!Validate::$function($value, (int) Configuration::get('PS_ALLOW_HTML_IFRAME'))) { 2149 $this->errors[] = $this->trans( 2150 'The %1$s field (%2$s) is invalid.', 2151 [ 2152 call_user_func([$this->className, 'displayFieldName'], $fieldLang, $this->className), 2153 $language['name'], 2154 ], 2155 'Admin.Notifications.Error' 2156 ); 2157 } 2158 } 2159 } 2160 } 2161 2162 // Categories 2163 if ($this->isProductFieldUpdated('id_category_default') && (!Tools::isSubmit('categoryBox') || !count(Tools::getValue('categoryBox')))) { 2164 $this->errors[] = 'Products must be in at least one category.'; 2165 } 2166 2167 if ($this->isProductFieldUpdated('id_category_default') && (!is_array(Tools::getValue('categoryBox')) || !in_array(Tools::getValue('id_category_default'), Tools::getValue('categoryBox')))) { 2168 $this->errors[] = 'This product must be in the default category.'; 2169 } 2170 2171 // Tags 2172 foreach ($languages as $language) { 2173 if ($value = Tools::getValue('tags_' . $language['id_lang'])) { 2174 if (!Validate::isTagsList($value)) { 2175 $this->errors[] = $this->trans( 2176 'The tags list (%s) is invalid.', 2177 [ 2178 $language['name'], 2179 ], 2180 'Admin.Notifications.Error' 2181 ); 2182 } 2183 } 2184 } 2185 } 2186 2187 /** 2188 * Check if a field is edited (if the checkbox is checked) 2189 * This method will do something only for multishop with a context all / group. 2190 * 2191 * @param string $field Name of field 2192 * @param int $id_lang 2193 * 2194 * @return bool 2195 */ 2196 protected function isProductFieldUpdated($field, $id_lang = null) 2197 { 2198 // Cache this condition to improve performances 2199 static $is_activated = null; 2200 if (null === $is_activated) { 2201 $is_activated = Shop::isFeatureActive() && Shop::getContext() != Shop::CONTEXT_SHOP && $this->id_object; 2202 } 2203 2204 if (!$is_activated) { 2205 return true; 2206 } 2207 2208 $def = ObjectModel::getDefinition($this->object); 2209 if (!$this->object->isMultiShopField($field) && null === $id_lang && isset($def['fields'][$field])) { 2210 return true; 2211 } 2212 2213 if (null === $id_lang) { 2214 return !empty($_POST['multishop_check'][$field]); 2215 } else { 2216 return !empty($_POST['multishop_check'][$field][$id_lang]); 2217 } 2218 } 2219 2220 protected function _removeTaxFromEcotax() 2221 { 2222 if ($ecotax = Tools::getValue('ecotax')) { 2223 $_POST['ecotax'] = Tools::ps_round($ecotax / (1 + Tax::getProductEcotaxRate() / 100), 6); 2224 } 2225 } 2226 2227 protected function _applyTaxToEcotax($product) 2228 { 2229 if ($product->ecotax) { 2230 $product->ecotax = Tools::ps_round($product->ecotax * (1 + Tax::getProductEcotaxRate() / 100), 2); 2231 } 2232 } 2233 2234 /** 2235 * Update product download. 2236 * 2237 * @param Product $product 2238 * @param int $edit 2239 * 2240 * @return bool 2241 */ 2242 public function updateDownloadProduct($product, $edit = 0) 2243 { 2244 //legacy/sf2 form workaround 2245 //if is_virtual_file parameter was not send (SF2 form case), don't process virtual file 2246 if (Tools::getValue('is_virtual_file') === false) { 2247 return false; 2248 } 2249 2250 if ((int) Tools::getValue('is_virtual_file') == 1) { 2251 if (isset($_FILES['virtual_product_file_uploader']) && $_FILES['virtual_product_file_uploader']['size'] > 0) { 2252 $virtual_product_filename = ProductDownload::getNewFilename(); 2253 $helper = new HelperUploader('virtual_product_file_uploader'); 2254 $helper->setPostMaxSize(Tools::getOctets(ini_get('upload_max_filesize'))) 2255 ->setSavePath(_PS_DOWNLOAD_DIR_)->upload($_FILES['virtual_product_file_uploader'], $virtual_product_filename); 2256 } else { 2257 $virtual_product_filename = Tools::getValue('virtual_product_filename', ProductDownload::getNewFilename()); 2258 } 2259 2260 $product->setDefaultAttribute(0); //reset cache_default_attribute 2261 if (Tools::getValue('virtual_product_expiration_date') && !Validate::isDate(Tools::getValue('virtual_product_expiration_date'))) { 2262 if (!Tools::getValue('virtual_product_expiration_date')) { 2263 $this->errors[] = $this->trans('The expiration-date attribute is required.', [], 'Admin.Catalog.Notification'); 2264 2265 return false; 2266 } 2267 } 2268 2269 // Trick's 2270 if ($edit == 1) { 2271 $id_product_download = (int) ProductDownload::getIdFromIdProduct((int) $product->id, false); 2272 if (!$id_product_download) { 2273 $id_product_download = (int) Tools::getValue('virtual_product_id'); 2274 } 2275 } else { 2276 $id_product_download = Tools::getValue('virtual_product_id'); 2277 } 2278 2279 $is_shareable = Tools::getValue('virtual_product_is_shareable'); 2280 $virtual_product_name = Tools::getValue('virtual_product_name'); 2281 $virtual_product_nb_days = Tools::getValue('virtual_product_nb_days'); 2282 $virtual_product_nb_downloable = Tools::getValue('virtual_product_nb_downloable'); 2283 $virtual_product_expiration_date = Tools::getValue('virtual_product_expiration_date'); 2284 2285 $download = new ProductDownload((int) $id_product_download); 2286 $download->id_product = (int) $product->id; 2287 $download->display_filename = $virtual_product_name; 2288 $download->filename = $virtual_product_filename; 2289 $download->date_add = date('Y-m-d H:i:s'); 2290 $download->date_expiration = $virtual_product_expiration_date ? $virtual_product_expiration_date . ' 23:59:59' : ''; 2291 $download->nb_days_accessible = (int) $virtual_product_nb_days; 2292 $download->nb_downloadable = (int) $virtual_product_nb_downloable; 2293 $download->active = 1; 2294 $download->is_shareable = (int) $is_shareable; 2295 if ($download->save()) { 2296 return true; 2297 } 2298 } else { 2299 /* unactive download product if checkbox not checked */ 2300 if ($edit == 1) { 2301 $id_product_download = (int) ProductDownload::getIdFromIdProduct((int) $product->id); 2302 if (!$id_product_download) { 2303 $id_product_download = (int) Tools::getValue('virtual_product_id'); 2304 } 2305 } else { 2306 $id_product_download = ProductDownload::getIdFromIdProduct($product->id); 2307 } 2308 2309 if (!empty($id_product_download)) { 2310 $product_download = new ProductDownload((int) $id_product_download); 2311 $product_download->date_expiration = date('Y-m-d H:i:s', time() - 1); 2312 $product_download->active = 0; 2313 2314 return $product_download->save(); 2315 } 2316 } 2317 2318 return false; 2319 } 2320 2321 /** 2322 * Update product accessories. 2323 * 2324 * @param object $product Product 2325 */ 2326 public function updateAccessories($product) 2327 { 2328 $product->deleteAccessories(); 2329 if ($accessories = Tools::getValue('inputAccessories')) { 2330 $accessories_id = array_unique(explode('-', $accessories)); 2331 if (count($accessories_id)) { 2332 array_pop($accessories_id); 2333 $product->changeAccessories($accessories_id); 2334 } 2335 } 2336 } 2337 2338 /** 2339 * Update product tags. 2340 * 2341 * @param array $languages Array languages 2342 * @param object $product Product 2343 * 2344 * @return bool Update result 2345 */ 2346 public function updateTags($languages, $product) 2347 { 2348 $tag_success = true; 2349 /* Reset all tags for THIS product */ 2350 if (!Tag::deleteTagsForProduct((int) $product->id)) { 2351 $this->errors[] = $this->trans('An error occurred while attempting to delete previous tags.', [], 'Admin.Catalog.Notification'); 2352 } 2353 /* Assign tags to this product */ 2354 foreach ($languages as $language) { 2355 if ($value = Tools::getValue('tags_' . $language['id_lang'])) { 2356 $tag_success &= Tag::addTags($language['id_lang'], (int) $product->id, $value); 2357 } 2358 } 2359 2360 if (!$tag_success) { 2361 $this->errors[] = $this->trans('An error occurred while adding tags.', [], 'Admin.Catalog.Notification'); 2362 } 2363 2364 return $tag_success; 2365 } 2366 2367 public function ajaxProcessProductManufacturers() 2368 { 2369 $manufacturers = Manufacturer::getManufacturers(false, 0, true, false, false, false, true); 2370 $jsonArray = []; 2371 2372 if ($manufacturers) { 2373 foreach ($manufacturers as $manufacturer) { 2374 $tmp = ['optionValue' => $manufacturer['id_manufacturer'], 'optionDisplay' => htmlspecialchars(trim($manufacturer['name']))]; 2375 $jsonArray[] = json_encode($tmp); 2376 } 2377 } 2378 2379 die('[' . implode(',', $jsonArray) . ']'); 2380 } 2381 2382 /** 2383 * Build a categories tree. 2384 * 2385 * @param $id_obj 2386 * @param array $indexedCategories Array with categories where product is indexed (in order to check checkbox) 2387 * @param array $categories Categories to list 2388 * @param $current 2389 * @param null $id_category Current category ID 2390 * @param null $id_category_default 2391 * @param array $has_suite 2392 * 2393 * @return string 2394 */ 2395 public static function recurseCategoryForInclude($id_obj, $indexedCategories, $categories, $current, $id_category = null, $id_category_default = null, $has_suite = []) 2396 { 2397 @trigger_error('This function is deprecated since 1.7.0.', E_USER_DEPRECATED); 2398 } 2399 2400 public function getPreviewUrl(Product $product) 2401 { 2402 $id_lang = Configuration::get('PS_LANG_DEFAULT', null, null, Context::getContext()->shop->id); 2403 2404 if (!ShopUrl::getMainShopDomain()) { 2405 return false; 2406 } 2407 2408 $is_rewrite_active = (bool) Configuration::get('PS_REWRITING_SETTINGS'); 2409 $preview_url = $this->context->link->getProductLink( 2410 $product, 2411 $this->getFieldValue($product, 'link_rewrite', $this->context->language->id), 2412 Category::getLinkRewrite($this->getFieldValue($product, 'id_category_default'), $this->context->language->id), 2413 null, 2414 $id_lang, 2415 (int) Context::getContext()->shop->id, 2416 0, 2417 $is_rewrite_active 2418 ); 2419 2420 if (!$product->active) { 2421 $admin_dir = dirname($_SERVER['PHP_SELF']); 2422 $admin_dir = substr($admin_dir, strrpos($admin_dir, '/') + 1); 2423 $preview_url .= ((strpos($preview_url, '?') === false) ? '?' : '&') . 'adtoken=' . $this->token . '&ad=' . $admin_dir . '&id_employee=' . (int) $this->context->employee->id; 2424 } 2425 2426 return $preview_url; 2427 } 2428 2429 /** 2430 * Post treatment for suppliers. 2431 * 2432 * @param int|null $id_product 2433 */ 2434 public function processSuppliers($id_product = null) 2435 { 2436 $id_product = (int) $id_product ? $id_product : (int) Tools::getValue('id_product'); 2437 2438 if ((int) Tools::getValue('supplier_loaded') === 1 && Validate::isLoadedObject($product = new Product($id_product))) { 2439 // Get all id_product_attribute 2440 $attributes = $product->getAttributesResume($this->context->language->id); 2441 if (empty($attributes)) { 2442 $attributes[] = [ 2443 'id_product_attribute' => 0, 2444 'attribute_designation' => '', 2445 ]; 2446 } 2447 2448 // Get all available suppliers 2449 $suppliers = Supplier::getSuppliers(); 2450 2451 // Get already associated suppliers 2452 $associated_suppliers = ProductSupplier::getSupplierCollection($product->id); 2453 2454 $suppliers_to_associate = []; 2455 $new_default_supplier = 0; 2456 $defaultWholeslePrice = (float) 0; 2457 $defaultReference = ''; 2458 2459 if (Tools::isSubmit('default_supplier')) { 2460 $new_default_supplier = (int) Tools::getValue('default_supplier'); 2461 } 2462 2463 // Get new associations 2464 foreach ($suppliers as $supplier) { 2465 if (Tools::isSubmit('check_supplier_' . $supplier['id_supplier'])) { 2466 $suppliers_to_associate[] = $supplier['id_supplier']; 2467 } 2468 } 2469 2470 // Delete already associated suppliers if needed 2471 foreach ($associated_suppliers as $key => $associated_supplier) { 2472 /** @var ProductSupplier $associated_supplier */ 2473 if (!in_array($associated_supplier->id_supplier, $suppliers_to_associate)) { 2474 $associated_supplier->delete(); 2475 unset($associated_suppliers[$key]); 2476 } 2477 } 2478 2479 // Associate suppliers 2480 foreach ($suppliers_to_associate as $id) { 2481 $to_add = true; 2482 foreach ($associated_suppliers as $as) { 2483 /** @var ProductSupplier $as */ 2484 if ($id == $as->id_supplier) { 2485 $to_add = false; 2486 } 2487 } 2488 2489 if ($to_add) { 2490 $product_supplier = new ProductSupplier(); 2491 $product_supplier->id_product = $product->id; 2492 $product_supplier->id_product_attribute = 0; 2493 $product_supplier->id_supplier = $id; 2494 if ($this->context->currency->id) { 2495 $product_supplier->id_currency = (int) $this->context->currency->id; 2496 } else { 2497 $product_supplier->id_currency = (int) Configuration::get('PS_CURRENCY_DEFAULT'); 2498 } 2499 $product_supplier->save(); 2500 2501 $associated_suppliers[] = $product_supplier; 2502 foreach ($attributes as $attribute) { 2503 if ((int) $attribute['id_product_attribute'] > 0) { 2504 $product_supplier = new ProductSupplier(); 2505 $product_supplier->id_product = $product->id; 2506 $product_supplier->id_product_attribute = (int) $attribute['id_product_attribute']; 2507 $product_supplier->id_supplier = $id; 2508 $product_supplier->save(); 2509 } 2510 } 2511 } 2512 } 2513 2514 // Manage references and prices 2515 foreach ($attributes as $attribute) { 2516 foreach ($associated_suppliers as $supplier) { 2517 /** @var ProductSupplier $supplier */ 2518 if (Tools::isSubmit('supplier_reference_' . $product->id . '_' . $attribute['id_product_attribute'] . '_' . $supplier->id_supplier) || 2519 (Tools::isSubmit('product_price_' . $product->id . '_' . $attribute['id_product_attribute'] . '_' . $supplier->id_supplier) && 2520 Tools::isSubmit('product_price_currency_' . $product->id . '_' . $attribute['id_product_attribute'] . '_' . $supplier->id_supplier))) { 2521 $reference = pSQL( 2522 Tools::getValue( 2523 'supplier_reference_' . $product->id . '_' . $attribute['id_product_attribute'] . '_' . $supplier->id_supplier, 2524 '' 2525 ) 2526 ); 2527 2528 $price = (float) str_replace( 2529 [' ', ','], 2530 ['', '.'], 2531 Tools::getValue( 2532 'product_price_' . $product->id . '_' . $attribute['id_product_attribute'] . '_' . $supplier->id_supplier, 2533 0 2534 ) 2535 ); 2536 2537 $price = Tools::ps_round($price, 6); 2538 2539 $id_currency = (int) Tools::getValue( 2540 'product_price_currency_' . $product->id . '_' . $attribute['id_product_attribute'] . '_' . $supplier->id_supplier, 2541 0 2542 ); 2543 2544 if ($id_currency <= 0 || (!($result = Currency::getCurrency($id_currency)) || empty($result))) { 2545 $this->errors[] = $this->trans('The selected currency is not valid', [], 'Admin.Catalog.Notification'); 2546 } 2547 2548 // Save product-supplier data 2549 $product_supplier_id = (int) ProductSupplier::getIdByProductAndSupplier($product->id, $attribute['id_product_attribute'], $supplier->id_supplier); 2550 2551 if (!$product_supplier_id) { 2552 $product->addSupplierReference($supplier->id_supplier, (int) $attribute['id_product_attribute'], $reference, (float) $price, (int) $id_currency); 2553 } else { 2554 $product_supplier = new ProductSupplier($product_supplier_id); 2555 $product_supplier->id_currency = (int) $id_currency; 2556 $product_supplier->product_supplier_price_te = (float) $price; 2557 $product_supplier->product_supplier_reference = pSQL($reference); 2558 $product_supplier->update(); 2559 } 2560 2561 if ($new_default_supplier == $supplier->id_supplier) { 2562 if ((int) $attribute['id_product_attribute'] > 0) { 2563 $data = [ 2564 'supplier_reference' => pSQL($reference), 2565 'wholesale_price' => (float) Tools::convertPrice($price, $id_currency), 2566 ]; 2567 $where = ' 2568 a.id_product = ' . (int) $product->id . ' 2569 AND a.id_product_attribute = ' . (int) $attribute['id_product_attribute']; 2570 ObjectModel::updateMultishopTable('Combination', $data, $where); 2571 } else { 2572 // @deprecated 1.7.7.0 This condition block will be remove in the next major, use ProductSupplier instead 2573 $defaultWholeslePrice = (float) Tools::convertPrice($price, $id_currency); 2574 $defaultReference = $reference; 2575 } 2576 } 2577 } elseif (Tools::isSubmit('supplier_reference_' . $product->id . '_' . $attribute['id_product_attribute'] . '_' . $supplier->id_supplier)) { 2578 //int attribute with default values if possible 2579 if ((int) $attribute['id_product_attribute'] > 0) { 2580 $product_supplier = new ProductSupplier(); 2581 $product_supplier->id_product = $product->id; 2582 $product_supplier->id_product_attribute = (int) $attribute['id_product_attribute']; 2583 $product_supplier->id_supplier = $supplier->id_supplier; 2584 $product_supplier->save(); 2585 } 2586 } 2587 } 2588 } 2589 2590 if ($this->object) { 2591 $product->updateDefaultSupplierData( 2592 $new_default_supplier, 2593 $defaultReference, 2594 $defaultWholeslePrice 2595 ); 2596 } 2597 } 2598 } 2599 2600 /** 2601 * Post treatment for warehouses. 2602 */ 2603 public function processWarehouses() 2604 { 2605 if ((int) Tools::getValue('warehouse_loaded') === 1 && Validate::isLoadedObject($product = new Product((int) $id_product = Tools::getValue('id_product')))) { 2606 // Get all id_product_attribute 2607 $attributes = $product->getAttributesResume($this->context->language->id); 2608 if (empty($attributes)) { 2609 $attributes[] = [ 2610 'id_product_attribute' => 0, 2611 'attribute_designation' => '', 2612 ]; 2613 } 2614 2615 // Get all available warehouses 2616 $warehouses = Warehouse::getWarehouses(true); 2617 2618 // Get already associated warehouses 2619 $associated_warehouses_collection = WarehouseProductLocation::getCollection($product->id); 2620 2621 $elements_to_manage = []; 2622 2623 // get form information 2624 foreach ($attributes as $attribute) { 2625 foreach ($warehouses as $warehouse) { 2626 $key = $warehouse['id_warehouse'] . '_' . $product->id . '_' . $attribute['id_product_attribute']; 2627 2628 // get elements to manage 2629 if (Tools::isSubmit('check_warehouse_' . $key)) { 2630 $location = Tools::getValue('location_warehouse_' . $key, ''); 2631 $elements_to_manage[$key] = $location; 2632 } 2633 } 2634 } 2635 2636 // Delete entry if necessary 2637 foreach ($associated_warehouses_collection as $awc) { 2638 /** @var WarehouseProductLocation $awc */ 2639 if (!array_key_exists($awc->id_warehouse . '_' . $awc->id_product . '_' . $awc->id_product_attribute, $elements_to_manage)) { 2640 $awc->delete(); 2641 } 2642 } 2643 2644 // Manage locations 2645 foreach ($elements_to_manage as $key => $location) { 2646 $params = explode('_', $key); 2647 2648 $wpl_id = (int) WarehouseProductLocation::getIdByProductAndWarehouse((int) $params[1], (int) $params[2], (int) $params[0]); 2649 2650 if (empty($wpl_id)) { 2651 //create new record 2652 $warehouse_location_entity = new WarehouseProductLocation(); 2653 $warehouse_location_entity->id_product = (int) $params[1]; 2654 $warehouse_location_entity->id_product_attribute = (int) $params[2]; 2655 $warehouse_location_entity->id_warehouse = (int) $params[0]; 2656 $warehouse_location_entity->location = pSQL($location); 2657 $warehouse_location_entity->save(); 2658 } else { 2659 $warehouse_location_entity = new WarehouseProductLocation((int) $wpl_id); 2660 2661 $location = pSQL($location); 2662 2663 if ($location != $warehouse_location_entity->location) { 2664 $warehouse_location_entity->location = pSQL($location); 2665 $warehouse_location_entity->update(); 2666 } 2667 } 2668 } 2669 StockAvailable::synchronize((int) $id_product); 2670 } 2671 } 2672 2673 /** 2674 * Get an array of pack items for display from the product object if specified, else from POST/GET values. 2675 * 2676 * @param Product $product 2677 * 2678 * @return array of pack items 2679 */ 2680 public function getPackItems($product = null) 2681 { 2682 $pack_items = []; 2683 2684 if (!$product) { 2685 $names_input = Tools::getValue('namePackItems'); 2686 $ids_input = Tools::getValue('inputPackItems'); 2687 if (!$names_input || !$ids_input) { 2688 return []; 2689 } 2690 // ids is an array of string with format : QTYxID 2691 $ids = array_unique(explode('-', $ids_input)); 2692 $names = array_unique(explode('¤', $names_input)); 2693 2694 if (!empty($ids)) { 2695 $length = count($ids); 2696 for ($i = 0; $i < $length; ++$i) { 2697 if (!empty($ids[$i]) && !empty($names[$i])) { 2698 list($pack_items[$i]['pack_quantity'], $pack_items[$i]['id']) = explode('x', $ids[$i]); 2699 $exploded_name = explode('x', $names[$i]); 2700 $pack_items[$i]['name'] = $exploded_name[1]; 2701 } 2702 } 2703 } 2704 } else { 2705 $i = 0; 2706 foreach ($product->packItems as $pack_item) { 2707 $pack_items[$i]['id'] = $pack_item->id; 2708 $pack_items[$i]['pack_quantity'] = $pack_item->pack_quantity; 2709 $pack_items[$i]['name'] = $pack_item->name; 2710 $pack_items[$i]['reference'] = $pack_item->reference; 2711 $pack_items[$i]['id_product_attribute'] = isset($pack_item->id_pack_product_attribute) && $pack_item->id_pack_product_attribute ? $pack_item->id_pack_product_attribute : 0; 2712 $cover = $pack_item->id_pack_product_attribute ? Product::getCombinationImageById($pack_item->id_pack_product_attribute, Context::getContext()->language->id) : Product::getCover($pack_item->id); 2713 $pack_items[$i]['image'] = Context::getContext()->link->getImageLink($pack_item->link_rewrite, $cover['id_image'], 'home_default'); 2714 // @todo: don't rely on 'home_default' 2715 //$path_to_image = _PS_IMG_DIR_.'p/'.Image::getImgFolderStatic($cover['id_image']).(int)$cover['id_image'].'.jpg'; 2716 //$pack_items[$i]['image'] = ImageManager::thumbnail($path_to_image, 'pack_mini_'.$pack_item->id.'_'.$this->context->shop->id.'.jpg', 120); 2717 ++$i; 2718 } 2719 } 2720 2721 return $pack_items; 2722 } 2723 2724 protected function _getFinalPrice($specific_price, $product_price, $tax_rate) 2725 { 2726 return $this->object->getPrice(false, $specific_price['id_product_attribute'], 2); 2727 } 2728 2729 protected function _getCustomizationFieldIds($labels, $alreadyGenerated, $obj) 2730 { 2731 $customizableFieldIds = []; 2732 if (isset($labels[Product::CUSTOMIZE_FILE])) { 2733 foreach ($labels[Product::CUSTOMIZE_FILE] as $id_customization_field => $label) { 2734 $customizableFieldIds[] = 'label_' . Product::CUSTOMIZE_FILE . '_' . (int) ($id_customization_field); 2735 } 2736 } 2737 if (isset($labels[Product::CUSTOMIZE_TEXTFIELD])) { 2738 foreach ($labels[Product::CUSTOMIZE_TEXTFIELD] as $id_customization_field => $label) { 2739 $customizableFieldIds[] = 'label_' . Product::CUSTOMIZE_TEXTFIELD . '_' . (int) ($id_customization_field); 2740 } 2741 } 2742 $j = 0; 2743 for ($i = $alreadyGenerated[Product::CUSTOMIZE_FILE]; $i < (int) ($this->getFieldValue($obj, 'uploadable_files')); ++$i) { 2744 $customizableFieldIds[] = 'newLabel_' . Product::CUSTOMIZE_FILE . '_' . $j++; 2745 } 2746 $j = 0; 2747 for ($i = $alreadyGenerated[Product::CUSTOMIZE_TEXTFIELD]; $i < (int) ($this->getFieldValue($obj, 'text_fields')); ++$i) { 2748 $customizableFieldIds[] = 'newLabel_' . Product::CUSTOMIZE_TEXTFIELD . '_' . $j++; 2749 } 2750 2751 return implode('¤', $customizableFieldIds); 2752 } 2753 2754 protected function getCarrierList() 2755 { 2756 $carrier_list = Carrier::getCarriers($this->context->language->id, false, false, false, null, Carrier::ALL_CARRIERS); 2757 2758 if ($product = $this->loadObject(true)) { 2759 /** @var Product $product */ 2760 $carrier_selected_list = $product->getCarriers(); 2761 foreach ($carrier_list as &$carrier) { 2762 foreach ($carrier_selected_list as $carrier_selected) { 2763 if ($carrier_selected['id_reference'] == $carrier['id_reference']) { 2764 $carrier['selected'] = true; 2765 2766 continue; 2767 } 2768 } 2769 } 2770 } 2771 2772 return $carrier_list; 2773 } 2774 2775 protected function addCarriers($product = null) 2776 { 2777 if (!isset($product)) { 2778 $product = new Product((int) Tools::getValue('id_product')); 2779 } 2780 2781 if (Validate::isLoadedObject($product)) { 2782 $carriers = []; 2783 2784 if (Tools::getValue('selectedCarriers')) { 2785 $carriers = Tools::getValue('selectedCarriers'); 2786 } 2787 2788 $product->setCarriers($carriers); 2789 } 2790 } 2791 2792 /** 2793 * Ajax process upload images. 2794 * 2795 * @param int|null $idProduct 2796 * @param string $inputFileName 2797 * @param bool $die If method must die or return values 2798 * 2799 * @return array 2800 */ 2801 public function ajaxProcessaddProductImage($idProduct = null, $inputFileName = 'file', $die = true) 2802 { 2803 $idProduct = $idProduct ? $idProduct : Tools::getValue('id_product'); 2804 2805 self::$currentIndex = 'index.php?tab=AdminProducts'; 2806 $product = new Product((int) $idProduct); 2807 $legends = Tools::getValue('legend'); 2808 2809 if (!is_array($legends)) { 2810 $legends = (array) $legends; 2811 } 2812 2813 if (!Validate::isLoadedObject($product)) { 2814 $files = []; 2815 $files[0]['error'] = $this->trans('Cannot add image because product creation failed.', [], 'Admin.Catalog.Notification'); 2816 } 2817 2818 $image_uploader = new HelperImageUploader($inputFileName); 2819 $image_uploader->setAcceptTypes(['jpeg', 'gif', 'png', 'jpg'])->setMaxSize($this->max_image_size); 2820 $files = $image_uploader->process(); 2821 2822 foreach ($files as &$file) { 2823 $image = new Image(); 2824 $image->id_product = (int) ($product->id); 2825 $image->position = Image::getHighestPosition($product->id) + 1; 2826 2827 foreach ($legends as $key => $legend) { 2828 if (!empty($legend)) { 2829 $image->legend[(int) $key] = $legend; 2830 } 2831 } 2832 2833 if (!Image::getCover($image->id_product)) { 2834 $image->cover = 1; 2835 } else { 2836 $image->cover = 0; 2837 } 2838 2839 if (($validate = $image->validateFieldsLang(false, true)) !== true) { 2840 $file['error'] = $validate; 2841 } 2842 2843 if (isset($file['error']) && (!is_numeric($file['error']) || $file['error'] != 0)) { 2844 continue; 2845 } 2846 2847 if (!$image->add()) { 2848 $file['error'] = $this->trans('Error while creating additional image', [], 'Admin.Catalog.Notification'); 2849 } else { 2850 if (!$new_path = $image->getPathForCreation()) { 2851 $file['error'] = $this->trans('An error occurred while attempting to create a new folder.', [], 'Admin.Notifications.Error'); 2852 2853 continue; 2854 } 2855 2856 $error = 0; 2857 2858 if (!ImageManager::resize($file['save_path'], $new_path . '.' . $image->image_format, null, null, 'jpg', false, $error)) { 2859 switch ($error) { 2860 case ImageManager::ERROR_FILE_NOT_EXIST: 2861 $file['error'] = $this->trans('An error occurred while copying image, the file does not exist anymore.', [], 'Admin.Catalog.Notification'); 2862 2863 break; 2864 2865 case ImageManager::ERROR_FILE_WIDTH: 2866 $file['error'] = $this->trans('An error occurred while copying image, the file width is 0px.', [], 'Admin.Catalog.Notification'); 2867 2868 break; 2869 2870 case ImageManager::ERROR_MEMORY_LIMIT: 2871 $file['error'] = $this->trans('An error occurred while copying image, check your memory limit.', [], 'Admin.Catalog.Notification'); 2872 2873 break; 2874 2875 default: 2876 $file['error'] = $this->trans('An error occurred while copying the image.', [], 'Admin.Catalog.Notification'); 2877 2878 break; 2879 } 2880 2881 continue; 2882 } else { 2883 $imagesTypes = ImageType::getImagesTypes('products'); 2884 $generate_hight_dpi_images = (bool) Configuration::get('PS_HIGHT_DPI'); 2885 2886 foreach ($imagesTypes as $imageType) { 2887 if (!ImageManager::resize($file['save_path'], $new_path . '-' . stripslashes($imageType['name']) . '.' . $image->image_format, $imageType['width'], $imageType['height'], $image->image_format)) { 2888 $file['error'] = $this->trans('An error occurred while copying this image:', [], 'Admin.Notifications.Error') . ' ' . stripslashes($imageType['name']); 2889 2890 continue; 2891 } 2892 2893 if ($generate_hight_dpi_images) { 2894 if (!ImageManager::resize($file['save_path'], $new_path . '-' . stripslashes($imageType['name']) . '2x.' . $image->image_format, (int) $imageType['width'] * 2, (int) $imageType['height'] * 2, $image->image_format)) { 2895 $file['error'] = $this->trans('An error occurred while copying this image:', [], 'Admin.Notifications.Error') . ' ' . stripslashes($imageType['name']); 2896 2897 continue; 2898 } 2899 } 2900 } 2901 } 2902 2903 unlink($file['save_path']); 2904 //Necesary to prevent hacking 2905 unset($file['save_path']); 2906 Hook::exec('actionWatermark', ['id_image' => $image->id, 'id_product' => $product->id]); 2907 2908 if (!$image->update()) { 2909 $file['error'] = $this->trans('Error while updating the status.', [], 'Admin.Notifications.Error'); 2910 2911 continue; 2912 } 2913 2914 // Associate image to shop from context 2915 $shops = Shop::getContextListShopID(); 2916 $image->associateTo($shops); 2917 $json_shops = []; 2918 2919 foreach ($shops as $id_shop) { 2920 $json_shops[$id_shop] = true; 2921 } 2922 2923 $file['status'] = 'ok'; 2924 $file['id'] = $image->id; 2925 $file['position'] = $image->position; 2926 $file['cover'] = $image->cover; 2927 $file['legend'] = $image->legend; 2928 $file['path'] = $image->getExistingImgPath(); 2929 $file['shops'] = $json_shops; 2930 2931 @unlink(_PS_TMP_IMG_DIR_ . 'product_' . (int) $product->id . '.jpg'); 2932 @unlink(_PS_TMP_IMG_DIR_ . 'product_mini_' . (int) $product->id . '_' . $this->context->shop->id . '.jpg'); 2933 } 2934 } 2935 2936 if ($die) { 2937 die(json_encode([$image_uploader->getName() => $files])); 2938 } else { 2939 return $files; 2940 } 2941 } 2942 2943 public function ajaxProcessProductQuantity() 2944 { 2945 if (!$this->access('edit')) { 2946 return die(json_encode(['error' => 'You do not have the right permission'])); 2947 } 2948 if (!Tools::getValue('actionQty')) { 2949 return json_encode(['error' => 'Undefined action']); 2950 } 2951 2952 $product = new Product((int) Tools::getValue('id_product'), true); 2953 switch (Tools::getValue('actionQty')) { 2954 case 'depends_on_stock': 2955 if (Tools::getValue('value') === false) { 2956 die(json_encode(['error' => 'Undefined value'])); 2957 } 2958 if ((int) Tools::getValue('value') != 0 && (int) Tools::getValue('value') != 1) { 2959 die(json_encode(['error' => 'Incorrect value'])); 2960 } 2961 if (!$product->advanced_stock_management && (int) Tools::getValue('value') == 1) { 2962 die(json_encode(['error' => 'Not possible if advanced stock management is disabled.'])); 2963 } 2964 if (Configuration::get('PS_ADVANCED_STOCK_MANAGEMENT') 2965 && (int) Tools::getValue('value') == 1 2966 && ( 2967 Pack::isPack($product->id) 2968 && !Pack::allUsesAdvancedStockManagement($product->id) 2969 && ( 2970 $product->pack_stock_type == Pack::STOCK_TYPE_PACK_BOTH 2971 || $product->pack_stock_type == Pack::STOCK_TYPE_PRODUCTS_ONLY 2972 || ( 2973 $product->pack_stock_type == Pack::STOCK_TYPE_DEFAULT 2974 && (Configuration::get('PS_PACK_STOCK_TYPE') == Pack::STOCK_TYPE_PRODUCTS_ONLY 2975 || Configuration::get('PS_PACK_STOCK_TYPE') == Pack::STOCK_TYPE_PACK_BOTH) 2976 ) 2977 ) 2978 ) 2979 ) { 2980 die(json_encode(['error' => 'You cannot use advanced stock management for this pack because' . '<br />' . 2981 '- advanced stock management is not enabled for these products' . '<br />' . 2982 '- you have chosen to decrement products quantities.', ])); 2983 } 2984 2985 StockAvailable::setProductDependsOnStock($product->id, (int) Tools::getValue('value')); 2986 2987 break; 2988 2989 case 'pack_stock_type': 2990 $value = Tools::getValue('value'); 2991 if ($value === false) { 2992 die(json_encode(['error' => 'Undefined value'])); 2993 } 2994 if ((int) $value != 0 && (int) $value != 1 2995 && (int) $value != 2 && (int) $value != 3) { 2996 die(json_encode(['error' => 'Incorrect value'])); 2997 } 2998 if ($product->depends_on_stock 2999 && !Pack::allUsesAdvancedStockManagement($product->id) 3000 && ( 3001 (int) $value == 1 3002 || (int) $value == 2 3003 || ( 3004 (int) $value == 3 3005 && (Configuration::get('PS_PACK_STOCK_TYPE') == Pack::STOCK_TYPE_PRODUCTS_ONLY 3006 || Configuration::get('PS_PACK_STOCK_TYPE') == Pack::STOCK_TYPE_PACK_BOTH) 3007 ) 3008 ) 3009 ) { 3010 die(json_encode(['error' => 'You cannot use this stock management option because:' . '<br />' . 3011 '- advanced stock management is not enabled for these products' . '<br />' . 3012 '- advanced stock management is enabled for the pack', ])); 3013 } 3014 3015 Product::setPackStockType($product->id, $value); 3016 3017 break; 3018 3019 case 'out_of_stock': 3020 if (Tools::getValue('value') === false) { 3021 die(json_encode(['error' => 'Undefined value'])); 3022 } 3023 if (!in_array((int) Tools::getValue('value'), [0, 1, 2])) { 3024 die(json_encode(['error' => 'Incorrect value'])); 3025 } 3026 3027 StockAvailable::setProductOutOfStock($product->id, (int) Tools::getValue('value')); 3028 3029 break; 3030 3031 case 'set_qty': 3032 if (Tools::getValue('value') === false || (!is_numeric(trim(Tools::getValue('value'))))) { 3033 die(json_encode(['error' => 'Undefined value'])); 3034 } 3035 if (Tools::getValue('id_product_attribute') === false) { 3036 die(json_encode(['error' => 'Undefined id product attribute'])); 3037 } 3038 3039 StockAvailable::setQuantity($product->id, (int) Tools::getValue('id_product_attribute'), (int) Tools::getValue('value')); 3040 Hook::exec('actionProductUpdate', ['id_product' => (int) $product->id, 'product' => $product]); 3041 3042 // Catch potential echo from modules 3043 // This echoed error is kept for legacy controllers, but is dropped during sf refactoring of the hook. 3044 $error = ob_get_contents(); 3045 if (!empty($error)) { 3046 ob_end_clean(); 3047 die(json_encode(['error' => $error])); 3048 } 3049 3050 break; 3051 case 'advanced_stock_management': 3052 if (Tools::getValue('value') === false) { 3053 die(json_encode(['error' => 'Undefined value'])); 3054 } 3055 if ((int) Tools::getValue('value') != 1 && (int) Tools::getValue('value') != 0) { 3056 die(json_encode(['error' => 'Incorrect value'])); 3057 } 3058 if (!Configuration::get('PS_ADVANCED_STOCK_MANAGEMENT') && (int) Tools::getValue('value') == 1) { 3059 die(json_encode(['error' => 'Not possible if advanced stock management is disabled. '])); 3060 } 3061 3062 $product->setAdvancedStockManagement((int) Tools::getValue('value')); 3063 if (StockAvailable::dependsOnStock($product->id) == 1 && (int) Tools::getValue('value') == 0) { 3064 StockAvailable::setProductDependsOnStock($product->id, 0); 3065 } 3066 3067 break; 3068 } 3069 die(json_encode(['error' => false])); 3070 } 3071 3072 public function getCombinationImagesJS() 3073 { 3074 /** @var Product $obj */ 3075 if (!($obj = $this->loadObject(true))) { 3076 return; 3077 } 3078 3079 $content = 'var combination_images = new Array();'; 3080 if (!$allCombinationImages = $obj->getCombinationImages($this->context->language->id)) { 3081 return $content; 3082 } 3083 foreach ($allCombinationImages as $id_product_attribute => $combination_images) { 3084 $i = 0; 3085 $content .= 'combination_images[' . (int) $id_product_attribute . '] = new Array();'; 3086 foreach ($combination_images as $combination_image) { 3087 $content .= 'combination_images[' . (int) $id_product_attribute . '][' . $i++ . '] = ' . (int) $combination_image['id_image'] . ';'; 3088 } 3089 } 3090 3091 return $content; 3092 } 3093 3094 public function haveThisAccessory($accessory_id, $accessories) 3095 { 3096 foreach ($accessories as $accessory) { 3097 if ((int) $accessory['id_product'] == (int) $accessory_id) { 3098 return true; 3099 } 3100 } 3101 3102 return false; 3103 } 3104 3105 protected function initPack(Product $product) 3106 { 3107 $this->tpl_form_vars['is_pack'] = ($product->id && Pack::isPack($product->id)) || Tools::getValue('type_product') == Product::PTYPE_PACK; 3108 $product->packItems = Pack::getItems($product->id, $this->context->language->id); 3109 3110 $input_pack_items = ''; 3111 if (Tools::getValue('inputPackItems')) { 3112 $input_pack_items = Tools::getValue('inputPackItems'); 3113 } else { 3114 foreach ($product->packItems as $pack_item) { 3115 $input_pack_items .= $pack_item->pack_quantity . 'x' . $pack_item->id . '-'; 3116 } 3117 } 3118 $this->tpl_form_vars['input_pack_items'] = $input_pack_items; 3119 3120 $input_namepack_items = ''; 3121 if (Tools::getValue('namePackItems')) { 3122 $input_namepack_items = Tools::getValue('namePackItems'); 3123 } else { 3124 foreach ($product->packItems as $pack_item) { 3125 $input_namepack_items .= $pack_item->pack_quantity . ' x ' . $pack_item->name . '¤'; 3126 } 3127 } 3128 $this->tpl_form_vars['input_namepack_items'] = $input_namepack_items; 3129 } 3130 3131 /** 3132 * delete all items in pack, then check if type_product value is 2. 3133 * if yes, add the pack items from input "inputPackItems". 3134 * 3135 * @param Product $product 3136 * 3137 * @return bool 3138 */ 3139 public function updatePackItems($product) 3140 { 3141 Pack::deleteItems($product->id); 3142 // lines format: QTY x ID-QTY x ID 3143 if (Tools::getValue('type_product') == Product::PTYPE_PACK) { 3144 $product->setDefaultAttribute(0); //reset cache_default_attribute 3145 $items = Tools::getValue('inputPackItems'); 3146 $lines = array_unique(explode('-', $items)); 3147 3148 // lines is an array of string with format : QTYxIDxID_PRODUCT_ATTRIBUTE 3149 if (count($lines)) { 3150 foreach ($lines as $line) { 3151 if (!empty($line)) { 3152 $item_id_attribute = 0; 3153 count($array = explode('x', $line)) == 3 ? list($qty, $item_id, $item_id_attribute) = $array : list($qty, $item_id) = $array; 3154 if ($qty > 0 && isset($item_id)) { 3155 if (Pack::isPack((int) $item_id)) { 3156 $this->errors[] = $this->trans('You can\'t add product packs into a pack', [], 'Admin.Catalog.Notification'); 3157 } elseif (!Pack::addItem((int) $product->id, (int) $item_id, (int) $qty, (int) $item_id_attribute)) { 3158 $this->errors[] = $this->trans('An error occurred while attempting to add products to the pack.', [], 'Admin.Catalog.Notification'); 3159 } 3160 } 3161 } 3162 } 3163 } 3164 } 3165 } 3166 3167 public function ajaxProcessCheckProductName() 3168 { 3169 if ($this->access('view')) { 3170 $search = Tools::getValue('q'); 3171 $id_lang = Tools::getValue('id_lang'); 3172 $limit = Tools::getValue('limit'); 3173 if (Context::getContext()->shop->getContext() != Shop::CONTEXT_SHOP) { 3174 $result = false; 3175 } else { 3176 $result = Db::getInstance()->executeS(' 3177 SELECT DISTINCT pl.`name`, p.`id_product`, pl.`id_shop` 3178 FROM `' . _DB_PREFIX_ . 'product` p 3179 LEFT JOIN `' . _DB_PREFIX_ . 'product_shop` ps ON (ps.id_product = p.id_product AND ps.id_shop =' . (int) Context::getContext()->shop->id . ') 3180 LEFT JOIN `' . _DB_PREFIX_ . 'product_lang` pl 3181 ON (pl.`id_product` = p.`id_product` AND pl.`id_lang` = ' . (int) $id_lang . ') 3182 WHERE pl.`name` LIKE "%' . pSQL($search) . '%" AND ps.id_product IS NULL 3183 GROUP BY pl.`id_product` 3184 LIMIT ' . (int) $limit); 3185 } 3186 die(json_encode($result)); 3187 } 3188 } 3189 3190 public function ajaxProcessUpdatePositions() 3191 { 3192 if ($this->access('edit')) { 3193 $way = (int) (Tools::getValue('way')); 3194 $id_product = (int) Tools::getValue('id_product'); 3195 $id_category = (int) Tools::getValue('id_category'); 3196 $positions = Tools::getValue('product'); 3197 $page = (int) Tools::getValue('page'); 3198 $selected_pagination = (int) Tools::getValue('selected_pagination'); 3199 3200 if (is_array($positions)) { 3201 foreach ($positions as $position => $value) { 3202 $pos = explode('_', $value); 3203 3204 if ((isset($pos[1], $pos[2])) && ($pos[1] == $id_category && (int) $pos[2] === $id_product)) { 3205 if ($page > 1) { 3206 $position = $position + (($page - 1) * $selected_pagination); 3207 } 3208 3209 if ($product = new Product((int) $pos[2])) { 3210 if (isset($position) && $product->updatePosition($way, $position)) { 3211 $category = new Category((int) $id_category); 3212 if (Validate::isLoadedObject($category)) { 3213 hook::Exec('categoryUpdate', ['category' => $category]); 3214 } 3215 echo 'ok position ' . (int) $position . ' for product ' . (int) $pos[2] . "\r\n"; 3216 } else { 3217 echo '{"hasError" : true, "errors" : "Can not update product ' . (int) $id_product . ' to position ' . (int) $position . ' "}'; 3218 } 3219 } else { 3220 echo '{"hasError" : true, "errors" : "This product (' . (int) $id_product . ') can t be loaded"}'; 3221 } 3222 3223 break; 3224 } 3225 } 3226 } 3227 } 3228 } 3229 3230 public function ajaxProcessPublishProduct() 3231 { 3232 if ($this->access('edit')) { 3233 if ($id_product = (int) Tools::getValue('id_product')) { 3234 $bo_product_url = dirname($_SERVER['PHP_SELF']) . '/index.php?tab=AdminProducts&id_product=' . $id_product . '&updateproduct&token=' . $this->token; 3235 3236 if (Tools::getValue('redirect')) { 3237 die($bo_product_url); 3238 } 3239 3240 $product = new Product((int) $id_product); 3241 if (!Validate::isLoadedObject($product)) { 3242 die('error: invalid id'); 3243 } 3244 3245 $product->active = 1; 3246 3247 if ($product->save()) { 3248 die($bo_product_url); 3249 } else { 3250 die('error: saving'); 3251 } 3252 } 3253 } 3254 } 3255 3256 public function processImageLegends() 3257 { 3258 if (Tools::getValue('key_tab') == 'Images' && Tools::getValue('submitAddproductAndStay') == 'update_legends' && Validate::isLoadedObject($product = new Product((int) Tools::getValue('id_product')))) { 3259 $id_image = (int) Tools::getValue('id_caption'); 3260 $language_ids = Language::getIDs(false); 3261 foreach ($_POST as $key => $val) { 3262 if (preg_match('/^legend_([0-9]+)/i', $key, $match)) { 3263 foreach ($language_ids as $id_lang) { 3264 if ($val && $id_lang == $match[1]) { 3265 Db::getInstance()->execute('UPDATE ' . _DB_PREFIX_ . 'image_lang SET legend = "' . pSQL($val) . '" WHERE ' . ($id_image ? 'id_image = ' . (int) $id_image : 'EXISTS (SELECT 1 FROM ' . _DB_PREFIX_ . 'image WHERE ' . _DB_PREFIX_ . 'image.id_image = ' . _DB_PREFIX_ . 'image_lang.id_image AND id_product = ' . (int) $product->id . ')') . ' AND id_lang = ' . (int) $id_lang); 3266 } 3267 } 3268 } 3269 } 3270 } 3271 } 3272 3273 /** 3274 * Returns in an homemade JSON with the content of a products pack. 3275 */ 3276 public function displayAjaxProductPackItems() 3277 { 3278 $jsonArray = []; 3279 $products = Db::getInstance()->executeS(' 3280 SELECT p.`id_product`, pl.`name` 3281 FROM `' . _DB_PREFIX_ . 'product` p 3282 NATURAL LEFT JOIN `' . _DB_PREFIX_ . 'product_lang` pl 3283 WHERE pl.`id_lang` = ' . (int) (Tools::getValue('id_lang')) . ' 3284 ' . Shop::addSqlRestrictionOnLang('pl') . ' 3285 AND NOT EXISTS (SELECT 1 FROM `' . _DB_PREFIX_ . 'pack` WHERE `id_product_pack` = p.`id_product`) 3286 AND p.`id_product` != ' . (int) (Tools::getValue('id_product'))); 3287 3288 foreach ($products as $packItem) { 3289 $jsonArray[] = '{"value": "' . (int) ($packItem['id_product']) . '-' . addslashes($packItem['name']) 3290 . '", "text":"' . (int) ($packItem['id_product']) . ' - ' . addslashes($packItem['name']) . '"}'; 3291 } 3292 $this->ajaxRender('[' . implode(',', $jsonArray) . ']'); 3293 } 3294 3295 /** 3296 * Displays a list of products when their name matches a given query 3297 * Optional parameters allow products to be excluded from the results. 3298 */ 3299 public function displayAjaxProductsList() 3300 { 3301 $query = Tools::getValue('q', false); 3302 if (empty($query)) { 3303 return; 3304 } 3305 3306 /* 3307 * In the SQL request the "q" param is used entirely to match result in database. 3308 * In this way if string:"(ref : #ref_pattern#)" is displayed on the return list, 3309 * they are no return values just because string:"(ref : #ref_pattern#)" 3310 * is not write in the name field of the product. 3311 * So the ref pattern will be cut for the search request. 3312 */ 3313 if ($pos = strpos($query, ' (ref:')) { 3314 $query = substr($query, 0, $pos); 3315 } 3316 3317 $excludeIds = Tools::getValue('excludeIds', false); 3318 if ($excludeIds && $excludeIds != 'NaN') { 3319 $excludeIds = implode(',', array_map('intval', explode(',', $excludeIds))); 3320 } else { 3321 $excludeIds = ''; 3322 } 3323 3324 // Excluding downloadable products from packs because download from pack is not supported 3325 $forceJson = Tools::getValue('forceJson', false); 3326 $disableCombination = Tools::getValue('disableCombination', false); 3327 $excludeVirtuals = (bool) Tools::getValue('excludeVirtuals', true); 3328 $exclude_packs = (bool) Tools::getValue('exclude_packs', true); 3329 3330 $context = Context::getContext(); 3331 3332 $sql = 'SELECT p.`id_product`, pl.`link_rewrite`, p.`reference`, pl.`name`, image_shop.`id_image` id_image, il.`legend`, p.`cache_default_attribute` 3333 FROM `' . _DB_PREFIX_ . 'product` p 3334 ' . Shop::addSqlAssociation('product', 'p') . ' 3335 LEFT JOIN `' . _DB_PREFIX_ . 'product_lang` pl ON (pl.id_product = p.id_product AND pl.id_lang = ' . (int) $context->language->id . Shop::addSqlRestrictionOnLang('pl') . ') 3336 LEFT JOIN `' . _DB_PREFIX_ . 'image_shop` image_shop 3337 ON (image_shop.`id_product` = p.`id_product` AND image_shop.cover=1 AND image_shop.id_shop=' . (int) $context->shop->id . ') 3338 LEFT JOIN `' . _DB_PREFIX_ . 'image_lang` il ON (image_shop.`id_image` = il.`id_image` AND il.`id_lang` = ' . (int) $context->language->id . ') 3339 WHERE (pl.name LIKE \'%' . pSQL($query) . '%\' OR p.reference LIKE \'%' . pSQL($query) . '%\')' . 3340 (!empty($excludeIds) ? ' AND p.id_product NOT IN (' . $excludeIds . ') ' : ' ') . 3341 ($excludeVirtuals ? 'AND NOT EXISTS (SELECT 1 FROM `' . _DB_PREFIX_ . 'product_download` pd WHERE (pd.id_product = p.id_product))' : '') . 3342 ($exclude_packs ? 'AND (p.cache_is_pack IS NULL OR p.cache_is_pack = 0)' : '') . 3343 ' GROUP BY p.id_product'; 3344 3345 $items = Db::getInstance()->executeS($sql); 3346 3347 if ($items && ($disableCombination || $excludeIds)) { 3348 $results = []; 3349 foreach ($items as $item) { 3350 if (!$forceJson) { 3351 $item['name'] = str_replace('|', '|', $item['name']); 3352 $results[] = trim($item['name']) . (!empty($item['reference']) ? ' (ref: ' . $item['reference'] . ')' : '') . '|' . (int) ($item['id_product']); 3353 } else { 3354 $results[] = [ 3355 'id' => $item['id_product'], 3356 'name' => $item['name'] . (!empty($item['reference']) ? ' (ref: ' . $item['reference'] . ')' : ''), 3357 'ref' => (!empty($item['reference']) ? $item['reference'] : ''), 3358 'image' => str_replace('http://', Tools::getShopProtocol(), $context->link->getImageLink($item['link_rewrite'], $item['id_image'], 'home_default')), 3359 ]; 3360 } 3361 } 3362 3363 if (!$forceJson) { 3364 return $this->ajaxRender(implode(PHP_EOL, $results)); 3365 } 3366 3367 return $this->ajaxRender(json_encode($results)); 3368 } 3369 if ($items) { 3370 // packs 3371 $results = []; 3372 foreach ($items as $item) { 3373 // check if product have combination 3374 if (Combination::isFeatureActive() && $item['cache_default_attribute']) { 3375 $sql = 'SELECT pa.`id_product_attribute`, pa.`reference`, ag.`id_attribute_group`, pai.`id_image`, agl.`name` AS group_name, al.`name` AS attribute_name, 3376 a.`id_attribute` 3377 FROM `' . _DB_PREFIX_ . 'product_attribute` pa 3378 ' . Shop::addSqlAssociation('product_attribute', 'pa') . ' 3379 LEFT JOIN `' . _DB_PREFIX_ . 'product_attribute_combination` pac ON pac.`id_product_attribute` = pa.`id_product_attribute` 3380 LEFT JOIN `' . _DB_PREFIX_ . 'attribute` a ON a.`id_attribute` = pac.`id_attribute` 3381 LEFT JOIN `' . _DB_PREFIX_ . 'attribute_group` ag ON ag.`id_attribute_group` = a.`id_attribute_group` 3382 LEFT JOIN `' . _DB_PREFIX_ . 'attribute_lang` al ON (a.`id_attribute` = al.`id_attribute` AND al.`id_lang` = ' . (int) $context->language->id . ') 3383 LEFT JOIN `' . _DB_PREFIX_ . 'attribute_group_lang` agl ON (ag.`id_attribute_group` = agl.`id_attribute_group` AND agl.`id_lang` = ' . (int) $context->language->id . ') 3384 LEFT JOIN `' . _DB_PREFIX_ . 'product_attribute_image` pai ON pai.`id_product_attribute` = pa.`id_product_attribute` 3385 WHERE pa.`id_product` = ' . (int) $item['id_product'] . ' 3386 GROUP BY pa.`id_product_attribute`, ag.`id_attribute_group` 3387 ORDER BY pa.`id_product_attribute`'; 3388 3389 $combinations = Db::getInstance()->executeS($sql); 3390 if (!empty($combinations)) { 3391 foreach ($combinations as $k => $combination) { 3392 $results[$combination['id_product_attribute']]['id'] = $item['id_product']; 3393 $results[$combination['id_product_attribute']]['id_product_attribute'] = $combination['id_product_attribute']; 3394 !empty($results[$combination['id_product_attribute']]['name']) ? $results[$combination['id_product_attribute']]['name'] .= ' ' . $combination['group_name'] . '-' . $combination['attribute_name'] 3395 : $results[$combination['id_product_attribute']]['name'] = $item['name'] . ' ' . $combination['group_name'] . '-' . $combination['attribute_name']; 3396 if (!empty($combination['reference'])) { 3397 $results[$combination['id_product_attribute']]['ref'] = $combination['reference']; 3398 } else { 3399 $results[$combination['id_product_attribute']]['ref'] = !empty($item['reference']) ? $item['reference'] : ''; 3400 } 3401 if (empty($results[$combination['id_product_attribute']]['image'])) { 3402 $results[$combination['id_product_attribute']]['image'] = str_replace('http://', Tools::getShopProtocol(), $context->link->getImageLink($item['link_rewrite'], $combination['id_image'], 'home_default')); 3403 } 3404 } 3405 } else { 3406 $results[] = [ 3407 'id' => $item['id_product'], 3408 'name' => $item['name'], 3409 'ref' => (!empty($item['reference']) ? $item['reference'] : ''), 3410 'image' => str_replace('http://', Tools::getShopProtocol(), $context->link->getImageLink($item['link_rewrite'], $item['id_image'], 'home_default')), 3411 ]; 3412 } 3413 } else { 3414 $results[] = [ 3415 'id' => $item['id_product'], 3416 'name' => $item['name'], 3417 'ref' => (!empty($item['reference']) ? $item['reference'] : ''), 3418 'image' => str_replace('http://', Tools::getShopProtocol(), $context->link->getImageLink($item['link_rewrite'], $item['id_image'], 'home_default')), 3419 ]; 3420 } 3421 } 3422 3423 return $this->ajaxRender(json_encode(array_values($results))); 3424 } 3425 3426 return $this->ajaxRender(json_encode([])); 3427 } 3428} 3429