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; 27use PrestaShop\PrestaShop\Core\Feature\TokenInUrls; 28use PrestaShop\PrestaShop\Core\Localization\Locale; 29use PrestaShop\PrestaShop\Core\Localization\Specification\Number as NumberSpecification; 30use PrestaShop\PrestaShop\Core\Localization\Specification\Price as PriceSpecification; 31 32class AdminControllerCore extends Controller 33{ 34 /** @var string */ 35 public $path; 36 37 /** @var string */ 38 public static $currentIndex; 39 40 /** @var string */ 41 public $content; 42 43 /** @var array */ 44 public $warnings = []; 45 46 /** @var array */ 47 public $informations = []; 48 49 /** @var array */ 50 public $confirmations = []; 51 52 /** @var string|false */ 53 public $shopShareDatas = false; 54 55 /** @var array */ 56 public $_languages = []; 57 58 /** @var int */ 59 public $default_form_language; 60 61 /** @var bool */ 62 public $allow_employee_form_lang; 63 64 /** @var string */ 65 public $layout = 'layout.tpl'; 66 67 /** @var bool */ 68 public $bootstrap = false; 69 70 /** @var string|array */ 71 protected $meta_title = []; 72 73 /** @var string */ 74 public $template = 'content.tpl'; 75 76 /** @var string Associated table name */ 77 public $table = 'configuration'; 78 79 /** @var string */ 80 public $list_id; 81 82 /** @var string|false Object identifier inside the associated table */ 83 protected $identifier = false; 84 85 /** @var string */ 86 protected $identifier_name = 'name'; 87 88 /** @var string Associated object class name */ 89 public $className; 90 91 /** @var array */ 92 public $tabAccess; 93 94 /** @var int Tab id */ 95 public $id = -1; 96 97 /** @var bool */ 98 public $required_database = false; 99 100 /** @var string Security token */ 101 public $token; 102 103 /** @var string "shop" or "group_shop" */ 104 public $shopLinkType; 105 106 /** @var string Default ORDER BY clause when `$_orderBy` is not defined */ 107 protected $_defaultOrderBy = false; 108 109 /** @var string */ 110 protected $_defaultOrderWay = 'ASC'; 111 112 /** @var array */ 113 public $tpl_form_vars = []; 114 115 /** @var array */ 116 public $tpl_list_vars = []; 117 118 /** @var array */ 119 public $tpl_delete_link_vars = []; 120 121 /** @var array */ 122 public $tpl_option_vars = []; 123 124 /** @var array */ 125 public $tpl_view_vars = []; 126 127 /** @var array */ 128 public $tpl_required_fields_vars = []; 129 130 /** @var string|null */ 131 public $base_tpl_view = null; 132 133 /** @var string|null */ 134 public $base_tpl_form = null; 135 136 /** @var bool If you want more fieldsets in the form */ 137 public $multiple_fieldsets = false; 138 139 /** @var array|false */ 140 public $fields_value = false; 141 142 /** @var array Errors displayed after post processing */ 143 public $errors = []; 144 145 /** @var bool Define if the header of the list contains filter and sorting links or not */ 146 protected $list_simple_header; 147 148 /** @var array List to be generated */ 149 protected $fields_list; 150 151 /** @var array Modules list filters */ 152 protected $filter_modules_list = null; 153 154 /** @var array Modules list filters */ 155 protected $modules_list = []; 156 157 /** @var array Edit form to be generated */ 158 protected $fields_form; 159 160 /** @var array Override of `$fields_form` */ 161 protected $fields_form_override; 162 163 /** @var string Override form action */ 164 protected $submit_action; 165 166 /** @var array List of option forms to be generated */ 167 protected $fields_options = []; 168 169 /** @var string */ 170 protected $shopLink; 171 172 /** @var string SQL query */ 173 protected $_listsql = ''; 174 175 /** @var array Cache for query results */ 176 protected $_list = []; 177 178 /** @var string MySQL error */ 179 protected $_list_error; 180 181 /** @var string|array Toolbar title */ 182 protected $toolbar_title; 183 184 /** @var array List of toolbar buttons */ 185 protected $toolbar_btn = null; 186 187 /** @var bool Scrolling toolbar */ 188 protected $toolbar_scroll = true; 189 190 /** @var bool Set to false to hide toolbar and page title */ 191 protected $show_toolbar = true; 192 193 /** @var bool Set to true to show toolbar and page title for options */ 194 protected $show_toolbar_options = false; 195 196 /** @var int Number of results in list */ 197 protected $_listTotal = 0; 198 199 /** @var bool Automatically join language table if true */ 200 public $lang = false; 201 202 /** @var array WHERE clause determined by filter fields */ 203 protected $_filter; 204 205 /** @var string */ 206 protected $_filterHaving; 207 208 /** @var array Temporary SQL table WHERE clause determined by filter fields */ 209 protected $_tmpTableFilter = ''; 210 211 /** @var array Number of results in list per page (used in select field) */ 212 protected $_pagination = [20, 50, 100, 300, 1000]; 213 214 /** @var int Default number of results in list per page */ 215 protected $_default_pagination = 50; 216 217 /** @var string ORDER BY clause determined by field/arrows in list header */ 218 protected $_orderBy; 219 220 /** @var string Order way (ASC, DESC) determined by arrows in list header */ 221 protected $_orderWay; 222 223 /** @var array List of available actions for each list row - default actions are view, edit, delete, duplicate */ 224 protected $actions_available = ['view', 'edit', 'duplicate', 'delete']; 225 226 /** @var array List of required actions for each list row */ 227 protected $actions = []; 228 229 /** @var array List of row ids associated with a given action for witch this action have to not be available */ 230 protected $list_skip_actions = []; 231 232 /** @var bool Don't show header & footer */ 233 protected $lite_display = false; 234 235 /** @var bool List content lines are clickable if true */ 236 protected $list_no_link = false; 237 238 /** @var bool */ 239 protected $allow_export = false; 240 241 /** @var array Cache for translations */ 242 public static $cache_lang = []; 243 244 /** @var array Required_fields to display in the Required Fields form */ 245 public $required_fields = []; 246 247 /** @var HelperList */ 248 protected $helper; 249 250 /** @var bool */ 251 private $allowAnonymous = false; 252 253 /** @var int DELETE access level */ 254 const LEVEL_DELETE = 4; 255 256 /** @var int ADD access level */ 257 const LEVEL_ADD = 3; 258 259 /** @var int EDIT access level */ 260 const LEVEL_EDIT = 2; 261 262 /** @var int VIEW access level */ 263 const LEVEL_VIEW = 1; 264 265 /** 266 * Actions to execute on multiple selections. 267 * 268 * Usage: 269 * 270 * array( 271 * 'actionName' => array( 272 * 'text' => $this->trans('Message displayed on the submit button (mandatory)'), 273 * 'confirm' => $this->trans('If set, this confirmation message will pop-up (optional)')), 274 * 'anotherAction' => array(...) 275 * ); 276 * 277 * If your action is named 'actionName', you need to have a method named bulkactionName() that will be executed when the button is clicked. 278 * 279 * @var array 280 */ 281 protected $bulk_actions; 282 283 /** @var array Ids of the rows selected */ 284 protected $boxes; 285 286 /** @var bool Do not automatically select * anymore but select only what is necessary */ 287 protected $explicitSelect = false; 288 289 /** @var string Add fields into data query to display list */ 290 protected $_select; 291 292 /** @var string Join tables into data query to display list */ 293 protected $_join; 294 295 /** @var string Add conditions into data query to display list */ 296 protected $_where; 297 298 /** @var string Group rows into data query to display list */ 299 protected $_group; 300 301 /** @var string Having rows into data query to display list */ 302 protected $_having; 303 304 /** @var string Use SQL_CALC_FOUND_ROWS / FOUND_ROWS to count the number of records */ 305 protected $_use_found_rows = true; 306 307 /** @var bool */ 308 protected $is_cms = false; 309 310 /** @var string Identifier to use for changing positions in lists (can be omitted if positions cannot be changed) */ 311 protected $position_identifier; 312 313 /** @var string|int */ 314 protected $position_group_identifier; 315 316 /** @var bool Table records are not deleted but marked as deleted if set to true */ 317 protected $deleted = false; 318 319 /** @var bool Is a list filter set */ 320 protected $filter; 321 322 /** @var bool */ 323 protected $noLink; 324 325 /** @var bool|null */ 326 protected $specificConfirmDelete = null; 327 328 /** @var bool */ 329 protected $colorOnBackground; 330 331 /** @var bool If true, activates color on hover */ 332 protected $row_hover = true; 333 334 /** @var string Action to perform : 'edit', 'view', 'add', ... */ 335 protected $action; 336 337 /** @var string */ 338 protected $display; 339 340 /** @var array */ 341 protected $tab_modules_list = ['default_list' => [], 'slider_list' => []]; 342 343 /** @var string */ 344 public $tpl_folder; 345 346 /** @var string */ 347 protected $bo_theme; 348 349 /** @var bool Redirect or not after a creation */ 350 protected $_redirect = true; 351 352 /** @var array Name and directory where class image are located */ 353 public $fieldImageSettings = []; 354 355 /** @var string Image type */ 356 public $imageType = 'jpg'; 357 358 /** @var ObjectModel Instantiation of the class associated with the AdminController */ 359 protected $object; 360 361 /** @var int Current object ID */ 362 protected $id_object; 363 364 /** @var string Current controller name without suffix */ 365 public $controller_name; 366 367 /** @var int */ 368 public $multishop_context = -1; 369 370 /** @var false */ 371 public $multishop_context_group = true; 372 373 /** @var array Current breadcrumb position as an array of tab names */ 374 protected $breadcrumbs; 375 376 /** @var bool Bootstrap variable */ 377 public $show_page_header_toolbar = false; 378 379 /** @var string Bootstrap variable */ 380 public $page_header_toolbar_title; 381 382 /** @var array|Traversable Bootstrap variable */ 383 public $page_header_toolbar_btn = []; 384 385 /** @var bool Bootstrap variable */ 386 public $show_form_cancel_button; 387 388 /** @var string */ 389 public $admin_webpath; 390 391 /** @var array */ 392 protected $list_natives_modules = []; 393 394 /** @var array */ 395 protected $list_partners_modules = []; 396 397 /** @var array */ 398 public $modals = []; 399 400 /** @var bool */ 401 protected $logged_on_addons = false; 402 403 /** @var bool if logged employee has access to AdminImport */ 404 protected $can_import = false; 405 406 /** @var string */ 407 protected $tabSlug; 408 409 /** @var int Auth cookie lifetime */ 410 const AUTH_COOKIE_LIFETIME = 3600; 411 412 /** @var array */ 413 public $_conf; 414 415 /** @var float @var */ 416 public $timer_start; 417 418 /** @var bool */ 419 protected static $is_prestashop_up = true; 420 421 /** @var array */ 422 protected $translationsTab = []; 423 424 public function __construct($forceControllerName = '', $default_theme_name = 'default') 425 { 426 global $timer_start; 427 $this->timer_start = $timer_start; 428 429 $this->controller_type = 'admin'; 430 $this->controller_name = !empty($forceControllerName) ? $forceControllerName : get_class($this); 431 if (strpos($this->controller_name, 'ControllerOverride')) { 432 $this->controller_name = substr($this->controller_name, 0, -18); 433 } 434 if (strpos($this->controller_name, 'Controller')) { 435 $this->controller_name = substr($this->controller_name, 0, -10); 436 } 437 parent::__construct(); 438 439 if ($this->multishop_context == -1) { 440 $this->multishop_context = Shop::CONTEXT_ALL | Shop::CONTEXT_GROUP | Shop::CONTEXT_SHOP; 441 } 442 443 if (defined('_PS_BO_ALL_THEMES_DIR_')) { 444 if (defined('_PS_BO_DEFAULT_THEME_') && _PS_BO_DEFAULT_THEME_ 445 && @filemtime(_PS_BO_ALL_THEMES_DIR_ . _PS_BO_DEFAULT_THEME_ . DIRECTORY_SEPARATOR . 'template')) { 446 $default_theme_name = _PS_BO_DEFAULT_THEME_; 447 } 448 449 $this->bo_theme = $default_theme_name; 450 if (!@filemtime(_PS_BO_ALL_THEMES_DIR_ . $this->bo_theme . DIRECTORY_SEPARATOR . 'template')) { 451 $this->bo_theme = 'default'; 452 } 453 454 $this->context->employee->bo_theme = ( 455 Validate::isLoadedObject($this->context->employee) 456 && $this->context->employee->bo_theme 457 ) ? $this->context->employee->bo_theme : $this->bo_theme; 458 459 $this->bo_css = ( 460 Validate::isLoadedObject($this->context->employee) 461 && $this->context->employee->bo_css 462 ) ? $this->context->employee->bo_css : 'theme.css'; 463 $this->context->employee->bo_css = $this->bo_css; 464 465 $adminThemeCSSFile = _PS_BO_ALL_THEMES_DIR_ . $this->bo_theme . DIRECTORY_SEPARATOR . 'public' . DIRECTORY_SEPARATOR . $this->bo_css; 466 467 if (file_exists($adminThemeCSSFile)) { 468 $this->bo_css = 'theme.css'; 469 } 470 471 $this->context->smarty->setTemplateDir([ 472 _PS_BO_ALL_THEMES_DIR_ . $this->bo_theme . DIRECTORY_SEPARATOR . 'template', 473 _PS_OVERRIDE_DIR_ . 'controllers' . DIRECTORY_SEPARATOR . 'admin' . DIRECTORY_SEPARATOR . 'templates', 474 ]); 475 } 476 477 $this->id = Tab::getIdFromClassName($this->controller_name); 478 $this->token = Tools::getAdminToken($this->controller_name . (int) $this->id . (int) $this->context->employee->id); 479 480 $this->_conf = [ 481 1 => $this->trans('Successful deletion.', [], 'Admin.Notifications.Success'), 482 2 => $this->trans('The selection has been successfully deleted.', [], 'Admin.Notifications.Success'), 483 3 => $this->trans('Successful creation.', [], 'Admin.Notifications.Success'), 484 4 => $this->trans('Successful update.', [], 'Admin.Notifications.Success'), 485 5 => $this->trans('The status has been successfully updated.', [], 'Admin.Notifications.Success'), 486 6 => $this->trans('The settings have been successfully updated.', [], 'Admin.Notifications.Success'), 487 7 => $this->trans('The image was successfully deleted.', [], 'Admin.Notifications.Success'), 488 8 => $this->trans('The module was successfully downloaded.', [], 'Admin.Modules.Notification'), 489 9 => $this->trans('The thumbnails were successfully regenerated.', [], 'Admin.Notifications.Success'), 490 10 => $this->trans('The message was successfully sent to the customer.', [], 'Admin.Orderscustomers.Notification'), 491 11 => $this->trans('Comment successfully added.', [], 'Admin.Notifications.Success'), 492 12 => $this->trans('Module(s) installed successfully.', [], 'Admin.Modules.Notification'), 493 13 => $this->trans('Module(s) uninstalled successfully.', [], 'Admin.Modules.Notification'), 494 14 => $this->trans('The translation was successfully copied.', [], 'Admin.International.Notification'), 495 15 => $this->trans('The translations have been successfully added.', [], 'Admin.International.Notification'), 496 16 => $this->trans('The module transplanted successfully to the hook.', [], 'Admin.Modules.Notification'), 497 17 => $this->trans('The module was successfully removed from the hook.', [], 'Admin.Modules.Notification'), 498 18 => $this->trans('Successful upload.', [], 'Admin.Notifications.Success'), 499 19 => $this->trans('Duplication was completed successfully.', [], 'Admin.Notifications.Success'), 500 20 => $this->trans('The translation was added successfully, but the language has not been created.', [], 'Admin.International.Notification'), 501 21 => $this->trans('Module reset successfully.', [], 'Admin.Modules.Notification'), 502 22 => $this->trans('Module deleted successfully.', [], 'Admin.Modules.Notification'), 503 23 => $this->trans('Localization pack imported successfully.', [], 'Admin.International.Notification'), 504 24 => $this->trans('Localization pack imported successfully.', [], 'Admin.International.Notification'), 505 25 => $this->trans('The selected images have successfully been moved.', [], 'Admin.Notifications.Success'), 506 26 => $this->trans('Your cover image selection has been saved.', [], 'Admin.Notifications.Success'), 507 27 => $this->trans('The image\'s shop association has been modified.', [], 'Admin.Notifications.Success'), 508 28 => $this->trans('A zone has been assigned to the selection successfully.', [], 'Admin.Notifications.Success'), 509 29 => $this->trans('Successful upgrade.', [], 'Admin.Notifications.Success'), 510 30 => $this->trans('A partial refund was successfully created.', [], 'Admin.Orderscustomers.Notification'), 511 31 => $this->trans('The discount was successfully generated.', [], 'Admin.Catalog.Notification'), 512 32 => $this->trans('Successfully signed in to PrestaShop Addons.', [], 'Admin.Modules.Notification'), 513 ]; 514 515 $this->_error = [ 516 1 => $this->trans( 517 'The root category of the shop %shop% is not associated with the current shop. You can\'t access this page. Please change the root category of the shop.', 518 [ 519 '%shop%' => $this->context->shop->name, 520 ], 521 'Admin.Catalog.Notification' 522 ), 523 ]; 524 525 if (!$this->identifier) { 526 $this->identifier = 'id_' . $this->table; 527 } 528 if (!$this->_defaultOrderBy) { 529 $this->_defaultOrderBy = $this->identifier; 530 } 531 532 // Fix for homepage 533 if ($this->controller_name == 'AdminDashboard') { 534 $_POST['token'] = $this->token; 535 } 536 537 if (!Shop::isFeatureActive()) { 538 $this->shopLinkType = ''; 539 } 540 541 $this->override_folder = Tools::toUnderscoreCase(substr($this->controller_name, 5)) . '/'; 542 // Get the name of the folder containing the custom tpl files 543 $this->tpl_folder = Tools::toUnderscoreCase(substr($this->controller_name, 5)) . '/'; 544 545 $this->initShopContext(); 546 547 if (defined('_PS_ADMIN_DIR_')) { 548 $this->admin_webpath = str_ireplace(_PS_CORE_DIR_, '', _PS_ADMIN_DIR_); 549 $this->admin_webpath = preg_replace('/^' . preg_quote(DIRECTORY_SEPARATOR, '/') . '/', '', $this->admin_webpath); 550 } 551 552 // Check if logged on Addons 553 $this->logged_on_addons = !empty($this->context->cookie->username_addons) && !empty($this->context->cookie->password_addons); 554 555 // Set context mode 556 if (defined('_PS_HOST_MODE_') && _PS_HOST_MODE_) { 557 if (isset($this->context->cookie->is_contributor) && (int) $this->context->cookie->is_contributor === 1) { 558 $this->context->mode = Context::MODE_HOST_CONTRIB; 559 } else { 560 $this->context->mode = Context::MODE_HOST; 561 } 562 } elseif (isset($this->context->cookie->is_contributor) && (int) $this->context->cookie->is_contributor === 1) { 563 $this->context->mode = Context::MODE_STD_CONTRIB; 564 } else { 565 $this->context->mode = Context::MODE_STD; 566 } 567 568 /* Check if logged employee has access to AdminImport controller */ 569 $import_access = Profile::getProfileAccess($this->context->employee->id_profile, Tab::getIdFromClassName('AdminImport')); 570 if (is_array($import_access) && isset($import_access['view']) && $import_access['view'] == 1) { 571 $this->can_import = true; 572 } 573 574 $this->context->smarty->assign([ 575 'context_mode' => $this->context->mode, 576 'logged_on_addons' => $this->logged_on_addons, 577 'can_import' => $this->can_import, 578 ]); 579 } 580 581 /** 582 * Gets the multistore header and assigns its html content to a smarty variable 583 * 584 * @see PrestaShopBundle\Controller\Admin\MultistoreController 585 * 586 * (the decision to display it or not is taken by the MultistoreController) 587 */ 588 public function initMultistoreHeader(): void 589 { 590 if (!isset($this->lockedToAllShopContext)) { 591 return; 592 } 593 594 $this->context->smarty->assign([ 595 'multistore_header' => $this->container->get('prestashop.core.admin.multistore')->header($this->lockedToAllShopContext)->getContent(), 596 ]); 597 } 598 599 /** 600 * Set breadcrumbs array for the controller page. 601 * 602 * @param int|null $tab_id 603 * @param array|null $tabs 604 */ 605 public function initBreadcrumbs($tab_id = null, $tabs = null) 606 { 607 if (!is_array($tabs)) { 608 $tabs = []; 609 } 610 611 if (null === $tab_id) { 612 $tab_id = $this->id; 613 } 614 615 $tabs = Tab::recursiveTab($tab_id, $tabs); 616 617 $dummy = ['name' => '', 'href' => '', 'icon' => '']; 618 $breadcrumbs2 = [ 619 'container' => $dummy, 620 'tab' => $dummy, 621 'action' => $dummy, 622 ]; 623 if (!empty($tabs[0])) { 624 $this->addMetaTitle($tabs[0]['name']); 625 $breadcrumbs2['tab']['name'] = $tabs[0]['name']; 626 $breadcrumbs2['tab']['href'] = $this->context->link->getTabLink($tabs[0]); 627 if (!isset($tabs[1])) { 628 $breadcrumbs2['tab']['icon'] = 'icon-' . $tabs[0]['class_name']; 629 } 630 } 631 if (!empty($tabs[1])) { 632 $breadcrumbs2['container']['name'] = $tabs[1]['name']; 633 $breadcrumbs2['container']['href'] = $this->context->link->getTabLink($tabs[1]); 634 $breadcrumbs2['container']['icon'] = 'icon-' . $tabs[1]['class_name']; 635 } 636 637 /* content, edit, list, add, details, options, view */ 638 switch ($this->display) { 639 case 'add': 640 $breadcrumbs2['action']['name'] = $this->trans('Add'); 641 $breadcrumbs2['action']['icon'] = 'icon-plus'; 642 643 break; 644 case 'edit': 645 $breadcrumbs2['action']['name'] = $this->trans('Edit'); 646 $breadcrumbs2['action']['icon'] = 'icon-pencil'; 647 648 break; 649 case '': 650 case 'list': 651 $breadcrumbs2['action']['name'] = $this->trans('List'); 652 $breadcrumbs2['action']['icon'] = 'icon-th-list'; 653 654 break; 655 case 'details': 656 case 'view': 657 $breadcrumbs2['action']['name'] = $this->trans('View details'); 658 $breadcrumbs2['action']['icon'] = 'icon-zoom-in'; 659 660 break; 661 case 'options': 662 $breadcrumbs2['action']['name'] = $this->trans('Options'); 663 $breadcrumbs2['action']['icon'] = 'icon-cogs'; 664 665 break; 666 case 'generator': 667 $breadcrumbs2['action']['name'] = $this->trans('Generator'); 668 $breadcrumbs2['action']['icon'] = 'icon-flask'; 669 670 break; 671 } 672 673 $this->context->smarty->assign([ 674 'breadcrumbs2' => $breadcrumbs2, 675 'quick_access_current_link_name' => Tools::safeOutput($breadcrumbs2['tab']['name'] . (isset($breadcrumbs2['action']) ? ' - ' . $breadcrumbs2['action']['name'] : '')), 676 'quick_access_current_link_icon' => $breadcrumbs2['container']['icon'], 677 ]); 678 679 /* BEGIN - Backward compatibility < 1.6.0.3 */ 680 $this->breadcrumbs[] = $tabs[0]['name'] ?? ''; 681 $navigation_pipe = (Configuration::get('PS_NAVIGATION_PIPE') ? Configuration::get('PS_NAVIGATION_PIPE') : '>'); 682 $this->context->smarty->assign('navigationPipe', $navigation_pipe); 683 /* END - Backward compatibility < 1.6.0.3 */ 684 } 685 686 /** 687 * Set default toolbar_title to admin breadcrumb. 688 */ 689 public function initToolbarTitle() 690 { 691 $this->toolbar_title = is_array($this->breadcrumbs) ? array_unique($this->breadcrumbs) : [$this->breadcrumbs]; 692 693 switch ($this->display) { 694 case 'edit': 695 $this->toolbar_title[] = $this->trans('Edit'); 696 $this->addMetaTitle($this->trans('Edit')); 697 698 break; 699 700 case 'add': 701 $this->toolbar_title[] = $this->trans('Add new'); 702 $this->addMetaTitle($this->trans('Add new')); 703 704 break; 705 706 case 'view': 707 $this->toolbar_title[] = $this->trans('View'); 708 $this->addMetaTitle($this->trans('View')); 709 710 break; 711 } 712 713 if ($filter = $this->addFiltersToBreadcrumbs()) { 714 $this->toolbar_title[] = $filter; 715 } 716 } 717 718 /** 719 * @return string|void 720 */ 721 public function addFiltersToBreadcrumbs() 722 { 723 if ($this->filter && is_array($this->fields_list)) { 724 $filters = []; 725 726 foreach ($this->fields_list as $field => $t) { 727 if (isset($t['filter_key'])) { 728 $field = $t['filter_key']; 729 } 730 731 if (($val = Tools::getValue($this->table . 'Filter_' . $field)) || $val = $this->context->cookie->{$this->getCookieFilterPrefix() . $this->table . 'Filter_' . $field}) { 732 if (!is_array($val)) { 733 $filter_value = ''; 734 if (isset($t['type']) && $t['type'] == 'bool') { 735 $filter_value = ((bool) $val) ? $this->trans('yes') : $this->trans('no'); 736 } elseif (isset($t['type']) && $t['type'] == 'date' || isset($t['type']) && $t['type'] == 'datetime') { 737 $date = json_decode($val, true); 738 if (isset($date[0])) { 739 $filter_value = $date[0]; 740 if (isset($date[1]) && !empty($date[1])) { 741 $filter_value .= ' - ' . $date[1]; 742 } 743 } 744 } elseif (is_string($val)) { 745 $filter_value = htmlspecialchars($val, ENT_QUOTES, 'UTF-8'); 746 } 747 if (!empty($filter_value)) { 748 $filters[] = $this->trans('%s: %s', [$t['title'], $filter_value]); 749 } 750 } else { 751 $filter_value = ''; 752 foreach ($val as $v) { 753 if (is_string($v) && !empty($v)) { 754 $filter_value .= ' - ' . htmlspecialchars($v, ENT_QUOTES, 'UTF-8'); 755 } 756 } 757 $filter_value = ltrim($filter_value, ' -'); 758 if (!empty($filter_value)) { 759 $filters[] = $this->trans('%s: %s', [$t['title'], $filter_value]); 760 } 761 } 762 } 763 } 764 765 if (count($filters)) { 766 return $this->trans('filter by %s', [implode(', ', $filters)]); 767 } 768 } 769 } 770 771 /** 772 * @param string $action 773 * @param bool $disable 774 */ 775 public function access($action, $disable = false) 776 { 777 if (empty($this->tabAccess[$action])) { 778 $slugs = []; 779 780 foreach ((array) Access::getAuthorizationFromLegacy($action) as $roleSuffix) { 781 $slugs[] = $this->getTabSlug() . $roleSuffix; 782 } 783 784 $this->tabAccess[$action] = Access::isGranted( 785 $slugs, 786 $this->context->employee->id_profile 787 ); 788 } 789 790 return $this->tabAccess[$action]; 791 } 792 793 /** 794 * Check rights to view the current tab. 795 * 796 * @param bool $disable 797 * 798 * @return bool 799 */ 800 public function viewAccess($disable = false) 801 { 802 return $this->access('view', $disable); 803 } 804 805 /** 806 * Check for security token. 807 * 808 * @return bool 809 */ 810 public function checkToken() 811 { 812 if (TokenInUrls::isDisabled() || $this->isAnonymousAllowed()) { 813 return true; 814 } 815 816 $token = Tools::getValue('token'); 817 if ($token === $this->token) { 818 return true; 819 } 820 821 if (count($_POST) || !isset($_GET['controller']) || !Validate::isControllerName($_GET['controller']) || !$token) { 822 return false; 823 } 824 825 foreach ($_GET as $key => $value) { 826 if (is_array($value) || !in_array($key, ['controller', 'controllerUri'])) { 827 return false; 828 } 829 } 830 831 $cookie = Context::getContext()->cookie; 832 $whitelist = ['date_add', 'id_lang', 'id_employee', 'email', 'profile', 'passwd', 'remote_addr', 'shopContext', 'collapse_menu', 'checksum']; 833 foreach ($cookie->getAll() as $key => $value) { 834 if (!in_array($key, $whitelist)) { 835 unset($cookie->$key); 836 } 837 } 838 839 $cookie->write(); 840 841 return true; 842 } 843 844 /** 845 * Set the filters used for the list display. 846 */ 847 protected function getCookieFilterPrefix() 848 { 849 return str_replace(['admin', 'controller'], '', Tools::strtolower(get_class($this))); 850 } 851 852 public function processFilter() 853 { 854 Hook::exec('action' . $this->controller_name . 'ListingFieldsModifier', [ 855 'fields' => &$this->fields_list, 856 ]); 857 858 if (!isset($this->list_id)) { 859 $this->list_id = $this->table; 860 } 861 862 $prefix = $this->getCookieFilterPrefix(); 863 864 if (isset($this->list_id)) { 865 foreach ($_POST as $key => $value) { 866 if ($value === '') { 867 unset($this->context->cookie->{$prefix . $key}); 868 } elseif (stripos($key, $this->list_id . 'Filter_') === 0) { 869 $this->context->cookie->{$prefix . $key} = !is_array($value) ? $value : json_encode($value); 870 } elseif (stripos($key, 'submitFilter') === 0) { 871 $this->context->cookie->$key = !is_array($value) ? $value : json_encode($value); 872 } 873 } 874 875 foreach ($_GET as $key => $value) { 876 if (stripos($key, $this->list_id . 'Filter_') === 0) { 877 $this->context->cookie->{$prefix . $key} = !is_array($value) ? $value : json_encode($value); 878 } elseif (stripos($key, 'submitFilter') === 0) { 879 $this->context->cookie->$key = !is_array($value) ? $value : json_encode($value); 880 } 881 if (stripos($key, $this->list_id . 'Orderby') === 0 && Validate::isOrderBy($value)) { 882 if ($value === '' || $value == $this->_defaultOrderBy) { 883 unset($this->context->cookie->{$prefix . $key}); 884 } else { 885 $this->context->cookie->{$prefix . $key} = $value; 886 } 887 } elseif (stripos($key, $this->list_id . 'Orderway') === 0 && Validate::isOrderWay($value)) { 888 if ($value === '' || $value == $this->_defaultOrderWay) { 889 unset($this->context->cookie->{$prefix . $key}); 890 } else { 891 $this->context->cookie->{$prefix . $key} = $value; 892 } 893 } 894 } 895 } 896 897 $filters = $this->context->cookie->getFamily($prefix . $this->list_id . 'Filter_'); 898 $definition = false; 899 if (isset($this->className) && $this->className) { 900 $definition = ObjectModel::getDefinition($this->className); 901 } 902 903 foreach ($filters as $key => $value) { 904 /* Extracting filters from $_POST on key filter_ */ 905 if ($value != null && !strncmp($key, $prefix . $this->list_id . 'Filter_', 7 + Tools::strlen($prefix . $this->list_id))) { 906 $key = Tools::substr($key, 7 + Tools::strlen($prefix . $this->list_id)); 907 /* Table alias could be specified using a ! eg. alias!field */ 908 $tmp_tab = explode('!', $key); 909 $filter = count($tmp_tab) > 1 ? $tmp_tab[1] : $tmp_tab[0]; 910 911 if ($field = $this->filterToField($key, $filter)) { 912 $type = (array_key_exists('filter_type', $field) ? $field['filter_type'] : (array_key_exists('type', $field) ? $field['type'] : false)); 913 if (($type == 'date' || $type == 'datetime') && is_string($value)) { 914 $value = json_decode($value, true); 915 } 916 $key = isset($tmp_tab[1]) ? $tmp_tab[0] . '.`' . $tmp_tab[1] . '`' : '`' . $tmp_tab[0] . '`'; 917 918 // Assignment by reference 919 if (array_key_exists('tmpTableFilter', $field)) { 920 $sql_filter = &$this->_tmpTableFilter; 921 } elseif (array_key_exists('havingFilter', $field)) { 922 $sql_filter = &$this->_filterHaving; 923 } else { 924 $sql_filter = &$this->_filter; 925 } 926 927 /* Only for date filtering (from, to) */ 928 if (is_array($value)) { 929 if (isset($value[0]) && !empty($value[0])) { 930 if (!Validate::isDate($value[0])) { 931 $this->errors[] = $this->trans('The \'From\' date format is invalid (YYYY-MM-DD)', [], 'Admin.Notifications.Error'); 932 } else { 933 $sql_filter .= ' AND ' . pSQL($key) . ' >= \'' . pSQL(Tools::dateFrom($value[0])) . '\''; 934 } 935 } 936 937 if (isset($value[1]) && !empty($value[1])) { 938 if (!Validate::isDate($value[1])) { 939 $this->errors[] = $this->trans('The \'To\' date format is invalid (YYYY-MM-DD)', [], 'Admin.Notifications.Error'); 940 } else { 941 $sql_filter .= ' AND ' . pSQL($key) . ' <= \'' . pSQL(Tools::dateTo($value[1])) . '\''; 942 } 943 } 944 } else { 945 $sql_filter .= ' AND '; 946 $check_key = ($key == $this->identifier || $key == '`' . $this->identifier . '`'); 947 $alias = ($definition && !empty($definition['fields'][$filter]['shop'])) ? 'sa' : 'a'; 948 949 if ($type == 'int' || $type == 'bool') { 950 $sql_filter .= (($check_key || $key == '`active`') ? $alias . '.' : '') . pSQL($key) . ' = ' . (int) $value . ' '; 951 } elseif ($type == 'decimal') { 952 $sql_filter .= ($check_key ? $alias . '.' : '') . pSQL($key) . ' = ' . (float) $value . ' '; 953 } elseif ($type == 'select') { 954 $sql_filter .= ($check_key ? $alias . '.' : '') . pSQL($key) . ' = \'' . pSQL($value) . '\' '; 955 } elseif ($type == 'price') { 956 $value = (float) str_replace(',', '.', $value); 957 $sql_filter .= ($check_key ? $alias . '.' : '') . pSQL($key) . ' = ' . pSQL(trim($value)) . ' '; 958 } else { 959 $sql_filter .= ($check_key ? $alias . '.' : '') . pSQL($key) . ' LIKE \'%' . pSQL(trim($value)) . '%\' '; 960 } 961 } 962 } 963 } 964 } 965 } 966 967 /** 968 * @TODO uses redirectAdmin only if !$this->ajax 969 * 970 * @return ObjectModel|bool 971 */ 972 public function postProcess() 973 { 974 try { 975 if ($this->ajax) { 976 // from ajax-tab.php 977 $action = Tools::getValue('action'); 978 // no need to use displayConf() here 979 if (!empty($action) && method_exists($this, 'ajaxProcess' . Tools::toCamelCase($action))) { 980 Hook::exec('actionAdmin' . ucfirst($this->action) . 'Before', ['controller' => $this]); 981 Hook::exec('action' . get_class($this) . ucfirst($this->action) . 'Before', ['controller' => $this]); 982 983 $return = $this->{'ajaxProcess' . Tools::toCamelCase($action)}(); 984 985 Hook::exec('actionAdmin' . ucfirst($this->action) . 'After', ['controller' => $this, 'return' => $return]); 986 Hook::exec('action' . get_class($this) . ucfirst($this->action) . 'After', ['controller' => $this, 'return' => $return]); 987 988 return $return; 989 } elseif (!empty($action) && $this->controller_name == 'AdminModules' && Tools::getIsset('configure')) { 990 $module_obj = Module::getInstanceByName(Tools::getValue('configure')); 991 if (Validate::isLoadedObject($module_obj) && method_exists($module_obj, 'ajaxProcess' . $action)) { 992 return $module_obj->{'ajaxProcess' . $action}(); 993 } 994 } elseif (method_exists($this, 'ajaxProcess')) { 995 return $this->ajaxProcess(); 996 } 997 } else { 998 // Process list filtering 999 if ($this->filter && $this->action != 'reset_filters') { 1000 $this->processFilter(); 1001 } 1002 1003 if (isset($_POST) && count($_POST) && (int) Tools::getValue('submitFilter' . $this->list_id) || Tools::isSubmit('submitReset' . $this->list_id)) { 1004 $this->setRedirectAfter(self::$currentIndex . '&token=' . $this->token . (Tools::isSubmit('submitFilter' . $this->list_id) ? '&submitFilter' . $this->list_id . '=' . (int) Tools::getValue('submitFilter' . $this->list_id) : '')); 1005 } 1006 1007 // If the method named after the action exists, call "before" hooks, then call action method, then call "after" hooks 1008 if (!empty($this->action) && method_exists($this, 'process' . ucfirst(Tools::toCamelCase($this->action)))) { 1009 // Hook before action 1010 Hook::exec('actionAdmin' . ucfirst($this->action) . 'Before', ['controller' => $this]); 1011 Hook::exec('action' . get_class($this) . ucfirst($this->action) . 'Before', ['controller' => $this]); 1012 // Call process 1013 $return = $this->{'process' . Tools::toCamelCase($this->action)}(); 1014 1015 // Hook After Action 1016 Hook::exec('actionAdmin' . ucfirst($this->action) . 'After', ['controller' => $this, 'return' => $return]); 1017 Hook::exec('action' . get_class($this) . ucfirst($this->action) . 'After', ['controller' => $this, 'return' => $return]); 1018 1019 return $return; 1020 } 1021 } 1022 } catch (PrestaShopException $e) { 1023 $this->errors[] = $e->getMessage(); 1024 } 1025 1026 return false; 1027 } 1028 1029 /** 1030 * Object Delete images. 1031 * 1032 * @return ObjectModel|false 1033 */ 1034 public function processDeleteImage() 1035 { 1036 if (Validate::isLoadedObject($object = $this->loadObject())) { 1037 if (($object->deleteImage())) { 1038 $redirect = self::$currentIndex . '&update' . $this->table . '&' . $this->identifier . '=' . (int) Tools::getValue($this->identifier) . '&conf=7&token=' . $this->token; 1039 if (!$this->ajax) { 1040 $this->redirect_after = $redirect; 1041 } else { 1042 $this->content = 'ok'; 1043 } 1044 } 1045 } 1046 $this->errors[] = $this->trans('An error occurred while attempting to delete the image. (cannot load object).', [], 'Admin.Notifications.Error'); 1047 1048 return $object; 1049 } 1050 1051 /** 1052 * @param string $text_delimiter 1053 * 1054 * @throws PrestaShopException 1055 */ 1056 public function processExport($text_delimiter = '"') 1057 { 1058 // clean buffer 1059 if (ob_get_level() && ob_get_length() > 0) { 1060 ob_clean(); 1061 } 1062 $this->getList($this->context->language->id, null, null, 0, false); 1063 if (!count($this->_list)) { 1064 return; 1065 } 1066 1067 header('Content-type: text/csv'); 1068 header('Content-Type: application/force-download; charset=UTF-8'); 1069 header('Cache-Control: no-store, no-cache'); 1070 header('Content-disposition: attachment; filename="' . $this->table . '_' . date('Y-m-d_His') . '.csv"'); 1071 1072 $fd = fopen('php://output', 'wb'); 1073 $headers = []; 1074 foreach ($this->fields_list as $key => $datas) { 1075 if ('PDF' === $datas['title']) { 1076 unset($this->fields_list[$key]); 1077 } else { 1078 if ('ID' === $datas['title']) { 1079 $headers[] = strtolower(Tools::htmlentitiesDecodeUTF8($datas['title'])); 1080 } else { 1081 $headers[] = Tools::htmlentitiesDecodeUTF8($datas['title']); 1082 } 1083 } 1084 } 1085 fputcsv($fd, $headers, ';', $text_delimiter); 1086 1087 foreach ($this->_list as $i => $row) { 1088 $content = []; 1089 $path_to_image = false; 1090 foreach ($this->fields_list as $key => $params) { 1091 $field_value = isset($row[$key]) ? Tools::htmlentitiesDecodeUTF8(Tools::nl2br($row[$key])) : ''; 1092 if ($key == 'image') { 1093 if ($params['image'] != 'p' || Configuration::get('PS_LEGACY_IMAGES')) { 1094 $path_to_image = Tools::getShopDomain(true) . _PS_IMG_ . $params['image'] . '/' . $row['id_' . $this->table] . (isset($row['id_image']) ? '-' . (int) $row['id_image'] : '') . '.' . $this->imageType; 1095 } else { 1096 $path_to_image = Tools::getShopDomain(true) . _PS_IMG_ . $params['image'] . '/' . Image::getImgFolderStatic($row['id_image']) . (int) $row['id_image'] . '.' . $this->imageType; 1097 } 1098 if ($path_to_image) { 1099 $field_value = $path_to_image; 1100 } 1101 } 1102 if (isset($params['callback'])) { 1103 $callback_obj = (isset($params['callback_object'])) ? $params['callback_object'] : $this->context->controller; 1104 if (!preg_match('/<([a-z]+)([^<]+)*(?:>(.*)<\/\1>|\s+\/>)/ism', call_user_func_array([$callback_obj, $params['callback']], [$field_value, $row]))) { 1105 $field_value = call_user_func_array([$callback_obj, $params['callback']], [$field_value, $row]); 1106 } 1107 } 1108 $content[] = $field_value; 1109 } 1110 fputcsv($fd, $content, ';', $text_delimiter); 1111 } 1112 @fclose($fd); 1113 die; 1114 } 1115 1116 /** 1117 * Object Delete. 1118 * 1119 * @return ObjectModel|false 1120 * 1121 * @throws PrestaShopException 1122 */ 1123 public function processDelete() 1124 { 1125 if (Validate::isLoadedObject($object = $this->loadObject())) { 1126 $res = true; 1127 // check if request at least one object with noZeroObject 1128 if (isset($object->noZeroObject) && count(call_user_func([$this->className, $object->noZeroObject])) <= 1) { 1129 $this->errors[] = $this->trans('You need at least one object.', [], 'Admin.Notifications.Error') . 1130 ' <b>' . $this->table . '</b><br />' . 1131 $this->trans('You cannot delete all of the items.', [], 'Admin.Notifications.Error'); 1132 } elseif (array_key_exists('delete', $this->list_skip_actions) && in_array($object->id, $this->list_skip_actions['delete'])) { //check if some ids are in list_skip_actions and forbid deletion 1133 $this->errors[] = $this->trans('You cannot delete this item.', [], 'Admin.Notifications.Error'); 1134 } else { 1135 if ($this->deleted) { 1136 if (!empty($this->fieldImageSettings)) { 1137 $res = $object->deleteImage(); 1138 } 1139 1140 if (!$res) { 1141 $this->errors[] = $this->trans('Unable to delete associated images.', [], 'Admin.Notifications.Error'); 1142 } 1143 1144 $object->deleted = true; 1145 if ($res = $object->update()) { 1146 $this->redirect_after = self::$currentIndex . '&conf=1&token=' . $this->token; 1147 } 1148 } elseif ($res = $object->delete()) { 1149 $this->redirect_after = self::$currentIndex . '&conf=1&token=' . $this->token; 1150 } 1151 $this->errors[] = $this->trans('An error occurred during deletion.', [], 'Admin.Notifications.Error'); 1152 if ($res) { 1153 PrestaShopLogger::addLog( 1154 $this->trans('%s deletion', [$this->className]), 1155 1, 1156 null, 1157 $this->className, 1158 (int) $this->object->id, 1159 true, 1160 (int) $this->context->employee->id 1161 ); 1162 } 1163 } 1164 } else { 1165 $this->errors[] = $this->trans('An error occurred while deleting the object.', [], 'Admin.Notifications.Error') . 1166 ' <b>' . $this->table . '</b> ' . 1167 $this->trans('(cannot load object)', [], 'Admin.Notifications.Error'); 1168 } 1169 1170 return $object; 1171 } 1172 1173 /** 1174 * Call the right method for creating or updating object. 1175 * 1176 * @return ObjectModel|false|void 1177 */ 1178 public function processSave() 1179 { 1180 if ($this->id_object) { 1181 $this->object = $this->loadObject(); 1182 1183 return $this->processUpdate(); 1184 } else { 1185 return $this->processAdd(); 1186 } 1187 } 1188 1189 /** 1190 * Object creation. 1191 * 1192 * @return ObjectModel|false 1193 * 1194 * @throws PrestaShopException 1195 */ 1196 public function processAdd() 1197 { 1198 if (!isset($this->className) || empty($this->className)) { 1199 return false; 1200 } 1201 1202 $this->validateRules(); 1203 if (count($this->errors) <= 0) { 1204 $this->object = new $this->className(); 1205 1206 $this->copyFromPost($this->object, $this->table); 1207 $this->beforeAdd($this->object); 1208 if (method_exists($this->object, 'add') && !$this->object->add()) { 1209 $this->errors[] = $this->trans('An error occurred while creating an object.', [], 'Admin.Notifications.Error') . 1210 ' <b>' . $this->table . ' (' . Db::getInstance()->getMsgError() . ')</b>'; 1211 } elseif (($_POST[$this->identifier] = $this->object->id /* voluntary do affectation here */) && $this->postImage($this->object->id) && !count($this->errors) && $this->_redirect) { 1212 PrestaShopLogger::addLog( 1213 $this->trans('%s addition', [$this->className]), 1214 1, 1215 null, 1216 $this->className, 1217 (int) $this->object->id, 1218 true, 1219 (int) $this->context->employee->id 1220 ); 1221 $parent_id = (int) Tools::getValue('id_parent', 1); 1222 $this->afterAdd($this->object); 1223 $this->updateAssoShop($this->object->id); 1224 // Save and stay on same form 1225 if (empty($this->redirect_after) && $this->redirect_after !== false && Tools::isSubmit('submitAdd' . $this->table . 'AndStay')) { 1226 $this->redirect_after = self::$currentIndex . '&' . $this->identifier . '=' . $this->object->id . '&conf=3&update' . $this->table . '&token=' . $this->token; 1227 } 1228 // Save and back to parent 1229 if (empty($this->redirect_after) && $this->redirect_after !== false && Tools::isSubmit('submitAdd' . $this->table . 'AndBackToParent')) { 1230 $this->redirect_after = self::$currentIndex . '&' . $this->identifier . '=' . $parent_id . '&conf=3&token=' . $this->token; 1231 } 1232 // Default behavior (save and back) 1233 if (empty($this->redirect_after) && $this->redirect_after !== false) { 1234 $this->redirect_after = self::$currentIndex . ($parent_id ? '&' . $this->identifier . '=' . $this->object->id : '') . '&conf=3&token=' . $this->token; 1235 } 1236 } 1237 } 1238 1239 $this->errors = array_unique($this->errors); 1240 if (!empty($this->errors)) { 1241 // if we have errors, we stay on the form instead of going back to the list 1242 $this->display = 'edit'; 1243 1244 return false; 1245 } 1246 1247 return $this->object; 1248 } 1249 1250 /** 1251 * Object update. 1252 * 1253 * @return ObjectModel|false|void 1254 * 1255 * @throws PrestaShopException 1256 */ 1257 public function processUpdate() 1258 { 1259 /* Checking fields validity */ 1260 $this->validateRules(); 1261 if (empty($this->errors)) { 1262 $id = (int) Tools::getValue($this->identifier); 1263 1264 /* Object update */ 1265 if (isset($id) && !empty($id)) { 1266 /** @var ObjectModel $object */ 1267 $object = new $this->className($id); 1268 if (Validate::isLoadedObject($object)) { 1269 /* Specific to objects which must not be deleted */ 1270 if ($this->deleted && $this->beforeDelete($object)) { 1271 // Create new one with old objet values 1272 /** @var ObjectModel $object_new */ 1273 $object_new = $object->duplicateObject(); 1274 if (Validate::isLoadedObject($object_new)) { 1275 // Update old object to deleted 1276 $object->deleted = true; 1277 $object->update(); 1278 1279 // Update new object with post values 1280 $this->copyFromPost($object_new, $this->table); 1281 $result = $object_new->update(); 1282 if (Validate::isLoadedObject($object_new)) { 1283 $this->afterDelete($object_new, $object->id); 1284 } 1285 } 1286 } else { 1287 $this->copyFromPost($object, $this->table); 1288 $result = $object->update(); 1289 $this->afterUpdate($object); 1290 } 1291 1292 if ($object->id) { 1293 $this->updateAssoShop($object->id); 1294 } 1295 1296 if (!$result) { 1297 $this->errors[] = $this->trans('An error occurred while updating an object.', [], 'Admin.Notifications.Error') . 1298 ' <b>' . $this->table . '</b> (' . Db::getInstance()->getMsgError() . ')'; 1299 } elseif ($this->postImage($object->id) && !count($this->errors) && $this->_redirect) { 1300 $parent_id = (int) Tools::getValue('id_parent', 1); 1301 // Specific back redirect 1302 if ($back = Tools::getValue('back')) { 1303 $this->redirect_after = rawurldecode($back) . '&conf=4'; 1304 } 1305 // Save and stay on same form 1306 // @todo on the to following if, we may prefer to avoid override redirect_after previous value 1307 if (Tools::isSubmit('submitAdd' . $this->table . 'AndStay')) { 1308 $this->redirect_after = self::$currentIndex . '&' . $this->identifier . '=' . $object->id . '&conf=4&update' . $this->table . '&token=' . $this->token; 1309 } 1310 // Save and back to parent 1311 if (Tools::isSubmit('submitAdd' . $this->table . 'AndBackToParent')) { 1312 $this->redirect_after = self::$currentIndex . '&' . $this->identifier . '=' . $parent_id . '&conf=4&token=' . $this->token; 1313 } 1314 1315 // Default behavior (save and back) 1316 if (empty($this->redirect_after) && $this->redirect_after !== false) { 1317 $this->redirect_after = self::$currentIndex . ($parent_id ? '&' . $this->identifier . '=' . $object->id : '') . '&conf=4&token=' . $this->token; 1318 } 1319 } 1320 PrestaShopLogger::addLog( 1321 $this->trans('%s modification', [$this->className]), 1322 1, 1323 null, 1324 $this->className, 1325 (int) $object->id, 1326 true, 1327 (int) $this->context->employee->id 1328 ); 1329 } else { 1330 $this->errors[] = $this->trans('An error occurred while updating an object.', [], 'Admin.Notifications.Error') . 1331 ' <b>' . $this->table . '</b> ' . $this->trans('(cannot load object)', [], 'Admin.Notifications.Error'); 1332 } 1333 } 1334 } 1335 $this->errors = array_unique($this->errors); 1336 if (!empty($this->errors)) { 1337 // if we have errors, we stay on the form instead of going back to the list 1338 $this->display = 'edit'; 1339 1340 return false; 1341 } 1342 1343 if (isset($object)) { 1344 return $object; 1345 } 1346 } 1347 1348 /** 1349 * Change object required fields. 1350 * 1351 * @return ObjectModel 1352 */ 1353 public function processUpdateFields() 1354 { 1355 if (!is_array($fields = Tools::getValue('fieldsBox'))) { 1356 $fields = []; 1357 } 1358 1359 /** @var $object ObjectModel */ 1360 $object = new $this->className(); 1361 1362 if (!$object->addFieldsRequiredDatabase($fields)) { 1363 $this->errors[] = $this->trans('An error occurred when attempting to update the required fields.', [], 'Admin.Notifications.Error'); 1364 } else { 1365 $this->redirect_after = self::$currentIndex . '&conf=4&token=' . $this->token; 1366 } 1367 1368 return $object; 1369 } 1370 1371 /** 1372 * Change object status (active, inactive). 1373 * 1374 * @return ObjectModel|false 1375 * 1376 * @throws PrestaShopException 1377 */ 1378 public function processStatus() 1379 { 1380 if (Validate::isLoadedObject($object = $this->loadObject())) { 1381 if ($object->toggleStatus()) { 1382 $matches = []; 1383 if (preg_match('/[\?|&]controller=([^&]*)/', (string) $_SERVER['HTTP_REFERER'], $matches) !== false 1384 && strtolower($matches[1]) != strtolower(preg_replace('/controller/i', '', get_class($this)))) { 1385 $this->redirect_after = preg_replace('/[\?|&]conf=([^&]*)/i', '', (string) $_SERVER['HTTP_REFERER']); 1386 } else { 1387 $this->redirect_after = self::$currentIndex . '&token=' . $this->token; 1388 } 1389 1390 $id_category = (($id_category = (int) Tools::getValue('id_category')) && Tools::getValue('id_product')) ? '&id_category=' . $id_category : ''; 1391 1392 $page = (int) Tools::getValue('page'); 1393 $page = $page > 1 ? '&submitFilter' . $this->table . '=' . (int) $page : ''; 1394 $this->redirect_after .= '&conf=5' . $id_category . $page; 1395 } else { 1396 $this->errors[] = $this->trans('An error occurred while updating the status.', [], 'Admin.Notifications.Error'); 1397 } 1398 } else { 1399 $this->errors[] = $this->trans('An error occurred while updating the status for an object.', [], 'Admin.Notifications.Error') . 1400 ' <b>' . $this->table . '</b> ' . 1401 $this->trans('(cannot load object)', [], 'Admin.Notifications.Error'); 1402 } 1403 1404 return $object; 1405 } 1406 1407 /** 1408 * Change object position. 1409 * 1410 * @return ObjectModel|false 1411 */ 1412 public function processPosition() 1413 { 1414 if (!Validate::isLoadedObject($object = $this->loadObject())) { 1415 $this->errors[] = $this->trans('An error occurred while updating the status for an object.', [], 'Admin.Notifications.Error') . 1416 ' <b>' . $this->table . '</b> ' . $this->trans('(cannot load object)', [], 'Admin.Notifications.Error'); 1417 } elseif (!$object->updatePosition((int) Tools::getValue('way'), (int) Tools::getValue('position'))) { 1418 $this->errors[] = $this->trans('Failed to update the position.', [], 'Admin.Notifications.Error'); 1419 } else { 1420 $id_identifier_str = ($id_identifier = (int) Tools::getValue($this->identifier)) ? '&' . $this->identifier . '=' . $id_identifier : ''; 1421 $redirect = self::$currentIndex . '&' . $this->table . 'Orderby=position&' . $this->table . 'Orderway=asc&conf=5' . $id_identifier_str . '&token=' . $this->token; 1422 $this->redirect_after = $redirect; 1423 } 1424 1425 return $object; 1426 } 1427 1428 /** 1429 * Cancel all filters for this tab. 1430 * 1431 * @param int|null $list_id 1432 */ 1433 public function processResetFilters($list_id = null) 1434 { 1435 if ($list_id === null) { 1436 $list_id = isset($this->list_id) ? $this->list_id : $this->table; 1437 } 1438 1439 $prefix = $this->getCookieOrderByPrefix(); 1440 $filters = $this->context->cookie->getFamily($prefix . $list_id . 'Filter_'); 1441 foreach ($filters as $cookie_key => $filter) { 1442 if (strncmp($cookie_key, $prefix . $list_id . 'Filter_', 7 + Tools::strlen($prefix . $list_id)) == 0) { 1443 $key = substr($cookie_key, 7 + Tools::strlen($prefix . $list_id)); 1444 if (is_array($this->fields_list) && array_key_exists($key, $this->fields_list)) { 1445 $this->context->cookie->$cookie_key = null; 1446 } 1447 unset($this->context->cookie->$cookie_key); 1448 } 1449 } 1450 1451 if (isset($this->context->cookie->{'submitFilter' . $list_id})) { 1452 unset($this->context->cookie->{'submitFilter' . $list_id}); 1453 } 1454 if (isset($this->context->cookie->{$prefix . $list_id . 'Orderby'})) { 1455 unset($this->context->cookie->{$prefix . $list_id . 'Orderby'}); 1456 } 1457 if (isset($this->context->cookie->{$prefix . $list_id . 'Orderway'})) { 1458 unset($this->context->cookie->{$prefix . $list_id . 'Orderway'}); 1459 } 1460 1461 $_POST = []; 1462 $this->_filter = false; 1463 unset( 1464 $this->_filterHaving, 1465 $this->_having 1466 ); 1467 } 1468 1469 /** 1470 * Update options and preferences. 1471 */ 1472 protected function processUpdateOptions() 1473 { 1474 $this->beforeUpdateOptions(); 1475 1476 $languages = Language::getLanguages(false); 1477 1478 $hide_multishop_checkbox = (Shop::getTotalShops(false, null) < 2) ? true : false; 1479 foreach ($this->fields_options as $category_data) { 1480 if (!isset($category_data['fields'])) { 1481 continue; 1482 } 1483 1484 $fields = $category_data['fields']; 1485 1486 foreach ($fields as $field => $values) { 1487 if (isset($values['type']) && $values['type'] == 'selectLang') { 1488 foreach ($languages as $lang) { 1489 if (Tools::getValue($field . '_' . strtoupper($lang['iso_code']))) { 1490 $fields[$field . '_' . strtoupper($lang['iso_code'])] = [ 1491 'type' => 'select', 1492 'cast' => 'strval', 1493 'identifier' => 'mode', 1494 'list' => $values['list'], 1495 ]; 1496 } 1497 } 1498 } 1499 } 1500 1501 // Validate fields 1502 foreach ($fields as $field => $values) { 1503 // We don't validate fields with no visibility 1504 if (!$hide_multishop_checkbox && Shop::isFeatureActive() && isset($values['visibility']) && $values['visibility'] > Shop::getContext()) { 1505 continue; 1506 } 1507 1508 // Check if field is required 1509 if ((!Shop::isFeatureActive() && !empty($values['required'])) 1510 || (Shop::isFeatureActive() && isset($_POST['multishopOverrideOption'][$field]) && !empty($values['required']))) { 1511 if (isset($values['type']) && $values['type'] == 'textLang') { 1512 foreach ($languages as $language) { 1513 if (($value = Tools::getValue($field . '_' . $language['id_lang'])) == false && (string) $value != '0') { 1514 $this->errors[] = $this->trans('field %s is required.', [$values['title']], 'Admin.Notifications.Error'); 1515 } 1516 } 1517 } elseif (($value = Tools::getValue($field)) == false && (string) $value != '0') { 1518 $this->errors[] = $this->trans('field %s is required.', [$values['title']], 'Admin.Notifications.Error'); 1519 } 1520 } 1521 1522 // Check field validator 1523 if (isset($values['type']) && $values['type'] == 'textLang') { 1524 foreach ($languages as $language) { 1525 if (Tools::getValue($field . '_' . $language['id_lang']) && isset($values['validation'])) { 1526 $values_validation = $values['validation']; 1527 if (!Validate::$values_validation(Tools::getValue($field . '_' . $language['id_lang']))) { 1528 $this->errors[] = $this->trans('The %s field is invalid.', [$values['title']], 'Admin.Notifications.Error'); 1529 } 1530 } 1531 } 1532 } elseif (Tools::getValue($field) && isset($values['validation'])) { 1533 $values_validation = $values['validation']; 1534 if (!Validate::$values_validation(Tools::getValue($field))) { 1535 $this->errors[] = $this->trans('The %s field is invalid.', [$values['title']], 'Admin.Notifications.Error'); 1536 } 1537 } 1538 1539 // Set default value 1540 if (Tools::getValue($field) === false && isset($values['default'])) { 1541 $_POST[$field] = $values['default']; 1542 } 1543 } 1544 1545 if (!count($this->errors)) { 1546 foreach ($fields as $key => $options) { 1547 if (Shop::isFeatureActive() && isset($options['visibility']) && $options['visibility'] > Shop::getContext()) { 1548 continue; 1549 } 1550 1551 if (!$hide_multishop_checkbox && Shop::isFeatureActive() && Shop::getContext() != Shop::CONTEXT_ALL && empty($options['no_multishop_checkbox']) && empty($_POST['multishopOverrideOption'][$key])) { 1552 Configuration::deleteFromContext($key); 1553 1554 continue; 1555 } 1556 1557 // check if a method updateOptionFieldName is available 1558 $method_name = 'updateOption' . Tools::toCamelCase($key, true); 1559 if (method_exists($this, $method_name)) { 1560 $this->$method_name(Tools::getValue($key)); 1561 } elseif (isset($options['type']) && in_array($options['type'], ['textLang', 'textareaLang'])) { 1562 $list = []; 1563 foreach ($languages as $language) { 1564 $key_lang = Tools::getValue($key . '_' . $language['id_lang']); 1565 $val = (isset($options['cast']) ? $options['cast']($key_lang) : $key_lang); 1566 if ($this->validateField($val, $options)) { 1567 if (Validate::isCleanHtml($val)) { 1568 $list[$language['id_lang']] = $val; 1569 } else { 1570 $this->errors[] = $this->trans('Cannot add configuration %1$s for %2$s language', [$key, Language::getIsoById((int) $language['id_lang'])], 'Admin.International.Notification'); 1571 } 1572 } 1573 } 1574 Configuration::updateValue($key, $list, isset($options['validation']) && $options['validation'] == 'isCleanHtml' ? true : false); 1575 } else { 1576 $val = (isset($options['cast']) ? $options['cast'](Tools::getValue($key)) : Tools::getValue($key)); 1577 if ($this->validateField($val, $options)) { 1578 if (Validate::isCleanHtml($val)) { 1579 Configuration::updateValue($key, $val); 1580 } else { 1581 $this->errors[] = $this->trans('Cannot add configuration %s', [$key], 'Admin.Notifications.Error'); 1582 } 1583 } 1584 } 1585 } 1586 } 1587 } 1588 1589 $this->display = 'list'; 1590 if (empty($this->errors)) { 1591 $this->confirmations[] = $this->_conf[6]; 1592 } 1593 } 1594 1595 public function initPageHeaderToolbar() 1596 { 1597 if (empty($this->toolbar_title)) { 1598 $this->initToolbarTitle(); 1599 } 1600 1601 if (!is_array($this->toolbar_title)) { 1602 $this->toolbar_title = [$this->toolbar_title]; 1603 } 1604 1605 switch ($this->display) { 1606 case 'view': 1607 // Default cancel button - like old back link 1608 $back = Tools::safeOutput(Tools::getValue('back', '')); 1609 if (empty($back)) { 1610 $back = self::$currentIndex . '&token=' . $this->token; 1611 } 1612 if (!Validate::isCleanHtml($back)) { 1613 die(Tools::displayError()); 1614 } 1615 if (!$this->lite_display) { 1616 $this->page_header_toolbar_btn['back'] = [ 1617 'href' => $back, 1618 'desc' => $this->trans('Back to list'), 1619 ]; 1620 } 1621 $obj = $this->loadObject(true); 1622 if (Validate::isLoadedObject($obj) && isset($obj->{$this->identifier_name}) && !empty($obj->{$this->identifier_name})) { 1623 array_pop($this->toolbar_title); 1624 array_pop($this->meta_title); 1625 $this->toolbar_title[] = is_array($obj->{$this->identifier_name}) ? $obj->{$this->identifier_name}[$this->context->employee->id_lang] : $obj->{$this->identifier_name}; 1626 $this->addMetaTitle($this->toolbar_title[count($this->toolbar_title) - 1]); 1627 } 1628 1629 break; 1630 case 'edit': 1631 $obj = $this->loadObject(true); 1632 if (Validate::isLoadedObject($obj) && isset($obj->{$this->identifier_name}) && !empty($obj->{$this->identifier_name})) { 1633 array_pop($this->toolbar_title); 1634 array_pop($this->meta_title); 1635 $this->toolbar_title[] = $this->trans( 1636 'Edit: %s', 1637 [ 1638 (is_array($obj->{$this->identifier_name}) 1639 && isset($obj->{$this->identifier_name}[$this->context->employee->id_lang]) 1640 ) 1641 ? $obj->{$this->identifier_name}[$this->context->employee->id_lang] 1642 : $obj->{$this->identifier_name}, 1643 ] 1644 ); 1645 $this->addMetaTitle($this->toolbar_title[count($this->toolbar_title) - 1]); 1646 } 1647 1648 break; 1649 } 1650 1651 if (is_array($this->page_header_toolbar_btn) 1652 && $this->page_header_toolbar_btn instanceof Traversable 1653 || count($this->toolbar_title)) { 1654 $this->show_page_header_toolbar = true; 1655 } 1656 1657 if (empty($this->page_header_toolbar_title)) { 1658 $this->page_header_toolbar_title = $this->toolbar_title[count($this->toolbar_title) - 1]; 1659 } 1660 1661 $this->context->smarty->assign('help_link', 'https://help.prestashop.com/' . Language::getIsoById($this->context->employee->id_lang) . '/doc/' 1662 . Tools::getValue('controller') . '?version=' . _PS_VERSION_ . '&country=' . Language::getIsoById($this->context->employee->id_lang)); 1663 } 1664 1665 /** 1666 * assign default action in toolbar_btn smarty var, if they are not set. 1667 * uses override to specifically add, modify or remove items. 1668 */ 1669 public function initToolbar() 1670 { 1671 switch ($this->display) { 1672 case 'add': 1673 case 'edit': 1674 // Default save button - action dynamically handled in javascript 1675 $this->toolbar_btn['save'] = [ 1676 'href' => '#', 1677 'desc' => $this->trans('Save'), 1678 ]; 1679 $back = Tools::safeOutput(Tools::getValue('back', '')); 1680 if (empty($back)) { 1681 $back = self::$currentIndex . '&token=' . $this->token; 1682 } 1683 if (!Validate::isCleanHtml($back)) { 1684 die(Tools::displayError()); 1685 } 1686 if (!$this->lite_display) { 1687 $this->toolbar_btn['cancel'] = [ 1688 'href' => $back, 1689 'desc' => $this->trans('Cancel'), 1690 ]; 1691 } 1692 1693 break; 1694 case 'view': 1695 // Default cancel button - like old back link 1696 $back = Tools::safeOutput(Tools::getValue('back', '')); 1697 if (empty($back)) { 1698 $back = self::$currentIndex . '&token=' . $this->token; 1699 } 1700 if (!Validate::isCleanHtml($back)) { 1701 die(Tools::displayError()); 1702 } 1703 if (!$this->lite_display) { 1704 $this->toolbar_btn['back'] = [ 1705 'href' => $back, 1706 'desc' => $this->trans('Back to list'), 1707 ]; 1708 } 1709 1710 break; 1711 case 'options': 1712 $this->toolbar_btn['save'] = [ 1713 'href' => '#', 1714 'desc' => $this->trans('Save'), 1715 ]; 1716 1717 break; 1718 default: 1719 // list 1720 $this->toolbar_btn['new'] = [ 1721 'href' => self::$currentIndex . '&add' . $this->table . '&token=' . $this->token, 1722 'desc' => $this->trans('Add new'), 1723 ]; 1724 if ($this->allow_export) { 1725 $this->toolbar_btn['export'] = [ 1726 'href' => self::$currentIndex . '&export' . $this->table . '&token=' . $this->token, 1727 'desc' => $this->trans('Export'), 1728 ]; 1729 } 1730 } 1731 } 1732 1733 /** 1734 * Load class object using identifier in $_GET (if possible) 1735 * otherwise return an empty object, or die. 1736 * 1737 * @param bool $opt Return an empty object if load fail 1738 * 1739 * @return ObjectModel|false 1740 */ 1741 protected function loadObject($opt = false) 1742 { 1743 if (!isset($this->className) || empty($this->className)) { 1744 return true; 1745 } 1746 1747 $id = (int) Tools::getValue($this->identifier); 1748 if ($id && Validate::isUnsignedId($id)) { 1749 if (!$this->object) { 1750 $this->object = new $this->className($id); 1751 } 1752 if (Validate::isLoadedObject($this->object)) { 1753 return $this->object; 1754 } 1755 // throw exception 1756 $this->errors[] = $this->trans('The object cannot be loaded (or found)', [], 'Admin.Notifications.Error'); 1757 1758 return false; 1759 } elseif ($opt) { 1760 if (!$this->object) { 1761 $this->object = new $this->className(); 1762 } 1763 1764 return $this->object; 1765 } else { 1766 $this->errors[] = $this->trans('The object cannot be loaded (the identifier is missing or invalid)', [], 'Admin.Notifications.Error'); 1767 1768 return false; 1769 } 1770 } 1771 1772 /** 1773 * Check if the token is valid, else display a warning page. 1774 * 1775 * @return bool 1776 */ 1777 public function checkAccess() 1778 { 1779 if (!$this->checkToken()) { 1780 // If this is an XSS attempt, then we should only display a simple, secure page 1781 // ${1} in the replacement string of the regexp is required, 1782 // because the token may begin with a number and mix up with it (e.g. $17) 1783 $url = preg_replace('/([&?]token=)[^&]*(&.*)?$/', '${1}' . $this->token . '$2', $_SERVER['REQUEST_URI']); 1784 if (false === strpos($url, '?token=') && false === strpos($url, '&token=')) { 1785 $url .= '&token=' . $this->token; 1786 } 1787 if (strpos($url, '?') === false) { 1788 $url = str_replace('&token', '?controller=AdminDashboard&token', $url); 1789 } 1790 1791 $this->context->smarty->assign('url', htmlentities($url)); 1792 1793 return false; 1794 } 1795 1796 return true; 1797 } 1798 1799 /** 1800 * @param string $key 1801 * @param string $filter 1802 * 1803 * @return array|false 1804 */ 1805 protected function filterToField($key, $filter) 1806 { 1807 if (!isset($this->fields_list)) { 1808 return false; 1809 } 1810 1811 foreach ($this->fields_list as $field) { 1812 if (array_key_exists('filter_key', $field) && $field['filter_key'] == $key) { 1813 return $field; 1814 } 1815 } 1816 if (array_key_exists($filter, $this->fields_list)) { 1817 return $this->fields_list[$filter]; 1818 } 1819 1820 return false; 1821 } 1822 1823 public function displayAjax() 1824 { 1825 if ($this->json) { 1826 $this->context->smarty->assign([ 1827 'json' => true, 1828 'status' => $this->status, 1829 ]); 1830 } 1831 $this->layout = 'layout-ajax.tpl'; 1832 $this->display_header = false; 1833 $this->display_header_javascript = false; 1834 $this->display_footer = false; 1835 1836 return $this->display(); 1837 } 1838 1839 protected function redirect() 1840 { 1841 Tools::redirectAdmin($this->redirect_after); 1842 } 1843 1844 /** 1845 * @throws Exception 1846 * @throws SmartyException 1847 */ 1848 public function display() 1849 { 1850 $this->context->smarty->assign([ 1851 'display_header' => $this->display_header, 1852 'display_header_javascript' => $this->display_header_javascript, 1853 'display_footer' => $this->display_footer, 1854 'js_def' => Media::getJsDef(), 1855 'toggle_navigation_url' => $this->context->link->getAdminLink('AdminEmployees', true, [], [ 1856 'action' => 'toggleMenu', 1857 ]), 1858 ]); 1859 1860 // Use page title from meta_title if it has been set else from the breadcrumbs array 1861 if (!$this->meta_title) { 1862 $this->meta_title = $this->toolbar_title; 1863 } 1864 if (is_array($this->meta_title)) { 1865 $this->meta_title = strip_tags(implode(' ' . Configuration::get('PS_NAVIGATION_PIPE') . ' ', $this->meta_title)); 1866 } 1867 $this->context->smarty->assign('meta_title', $this->meta_title); 1868 1869 $template_dirs = $this->context->smarty->getTemplateDir(); 1870 1871 // Check if header/footer have been overridden 1872 $dir = $this->context->smarty->getTemplateDir(0) . 'controllers' . DIRECTORY_SEPARATOR . trim($this->override_folder, '\\/') . DIRECTORY_SEPARATOR; 1873 $module_list_dir = $this->context->smarty->getTemplateDir(0) . 'helpers' . DIRECTORY_SEPARATOR . 'modules_list' . DIRECTORY_SEPARATOR; 1874 1875 $header_tpl = file_exists($dir . 'header.tpl') ? $dir . 'header.tpl' : 'header.tpl'; 1876 $page_header_toolbar = file_exists($dir . 'page_header_toolbar.tpl') ? $dir . 'page_header_toolbar.tpl' : 'page_header_toolbar.tpl'; 1877 $footer_tpl = file_exists($dir . 'footer.tpl') ? $dir . 'footer.tpl' : 'footer.tpl'; 1878 $modal_module_list = file_exists($module_list_dir . 'modal.tpl') ? $module_list_dir . 'modal.tpl' : ''; 1879 $tpl_action = $this->tpl_folder . $this->display . '.tpl'; 1880 1881 // Check if action template has been overridden 1882 foreach ($template_dirs as $template_dir) { 1883 if (file_exists($template_dir . DIRECTORY_SEPARATOR . $tpl_action) && $this->display != 'view' && $this->display != 'options') { 1884 if (method_exists($this, $this->display . Tools::toCamelCase($this->className))) { 1885 $this->{$this->display . Tools::toCamelCase($this->className)}(); 1886 } 1887 $this->context->smarty->assign('content', $this->context->smarty->fetch($tpl_action)); 1888 1889 break; 1890 } 1891 } 1892 1893 if (!$this->ajax) { 1894 $template = $this->createTemplate($this->template); 1895 $page = $template->fetch(); 1896 } else { 1897 $page = $this->content; 1898 } 1899 1900 if ($conf = Tools::getValue('conf')) { 1901 $this->context->smarty->assign('conf', $this->json ? json_encode($this->_conf[(int) $conf]) : $this->_conf[(int) $conf]); 1902 } 1903 1904 if ($error = Tools::getValue('error')) { 1905 $this->context->smarty->assign('error', $this->json ? json_encode($this->_error[(int) $error]) : $this->_error[(int) $error]); 1906 } 1907 1908 foreach (['errors', 'warnings', 'informations', 'confirmations'] as $type) { 1909 if (!is_array($this->$type)) { 1910 $this->$type = (array) $this->$type; 1911 } 1912 $this->context->smarty->assign($type, $this->json ? json_encode(array_unique($this->$type)) : array_unique($this->$type)); 1913 } 1914 1915 if ($this->show_page_header_toolbar && !$this->lite_display) { 1916 $this->context->smarty->assign( 1917 [ 1918 'page_header_toolbar' => $this->context->smarty->fetch($page_header_toolbar), 1919 ] 1920 ); 1921 if (!empty($modal_module_list)) { 1922 $this->context->smarty->assign( 1923 [ 1924 'modal_module_list' => $this->context->smarty->fetch($modal_module_list), 1925 ] 1926 ); 1927 } 1928 } 1929 1930 $this->context->smarty->assign('baseAdminUrl', __PS_BASE_URI__ . basename(_PS_ADMIN_DIR_) . '/'); 1931 1932 $this->context->smarty->assign( 1933 [ 1934 'page' => $this->json ? json_encode($page) : $page, 1935 'header' => $this->context->smarty->fetch($header_tpl), 1936 'footer' => $this->context->smarty->fetch($footer_tpl), 1937 ] 1938 ); 1939 1940 $this->smartyOutputContent($this->layout); 1941 } 1942 1943 /** 1944 * Add a warning message to display at the top of the page. 1945 * 1946 * @param string $msg 1947 */ 1948 protected function displayWarning($msg) 1949 { 1950 $this->warnings[] = $msg; 1951 } 1952 1953 /** 1954 * Add a info message to display at the top of the page. 1955 * 1956 * @param string $msg 1957 */ 1958 protected function displayInformation($msg) 1959 { 1960 $this->informations[] = $msg; 1961 } 1962 1963 /** 1964 * Assign smarty variables for the header. 1965 */ 1966 public function initHeader() 1967 { 1968 header('Cache-Control: no-store, no-cache'); 1969 1970 $this->context->smarty->assign([ 1971 'table' => $this->table, 1972 'current' => self::$currentIndex, 1973 'token' => $this->token, 1974 'host_mode' => (int) defined('_PS_HOST_MODE_'), 1975 'stock_management' => (int) Configuration::get('PS_STOCK_MANAGEMENT'), 1976 'no_order_tip' => $this->getNotificationTip('order'), 1977 'no_customer_tip' => $this->getNotificationTip('customer'), 1978 'no_customer_message_tip' => $this->getNotificationTip('customer_message'), 1979 ]); 1980 1981 if ($this->display_header) { 1982 $this->context->smarty->assign( 1983 'displayBackOfficeHeader', 1984 Hook::exec('displayBackOfficeHeader') 1985 ); 1986 } 1987 1988 $this->context->smarty->assign([ 1989 'displayBackOfficeTop' => Hook::exec('displayBackOfficeTop'), 1990 'submit_form_ajax' => (int) Tools::getValue('submitFormAjax'), 1991 ]); 1992 1993 // Multishop 1994 $is_multishop = Shop::isFeatureActive(); 1995 1996 // Quick access 1997 if ((int) $this->context->employee->id) { 1998 $quick_access = QuickAccess::getQuickAccessesWithToken($this->context->language->id, (int) $this->context->employee->id); 1999 } 2000 2001 $tabs = $this->getTabs(); 2002 $currentTabLevel = 0; 2003 foreach ($tabs as $tab) { 2004 $currentTabLevel = isset($tab['current_level']) ? $tab['current_level'] : $currentTabLevel; 2005 } 2006 2007 if (Validate::isLoadedObject($this->context->employee)) { 2008 $accesses = Profile::getProfileAccesses($this->context->employee->id_profile, 'class_name'); 2009 $helperShop = new HelperShop(); 2010 /* Hooks are voluntary out the initialize array (need those variables already assigned) */ 2011 $bo_color = empty($this->context->employee->bo_color) ? '#FFFFFF' : $this->context->employee->bo_color; 2012 $this->context->smarty->assign([ 2013 'help_box' => Configuration::get('PS_HELPBOX'), 2014 'round_mode' => Configuration::get('PS_PRICE_ROUND_MODE'), 2015 'brightness' => Tools::getBrightness($bo_color) < 128 ? 'white' : '#383838', 2016 'bo_width' => (int) $this->context->employee->bo_width, 2017 'bo_color' => isset($this->context->employee->bo_color) ? Tools::htmlentitiesUTF8($this->context->employee->bo_color) : null, 2018 'show_new_orders' => Configuration::get('PS_SHOW_NEW_ORDERS') && isset($accesses['AdminOrders']) && $accesses['AdminOrders']['view'], 2019 'show_new_customers' => Configuration::get('PS_SHOW_NEW_CUSTOMERS') && isset($accesses['AdminCustomers']) && $accesses['AdminCustomers']['view'], 2020 'show_new_messages' => Configuration::get('PS_SHOW_NEW_MESSAGES') && isset($accesses['AdminCustomerThreads']) && $accesses['AdminCustomerThreads']['view'], 2021 'employee' => $this->context->employee, 2022 'search_type' => Tools::getValue('bo_search_type'), 2023 'bo_query' => Tools::safeOutput(Tools::stripslashes(Tools::getValue('bo_query'))), 2024 'quick_access' => empty($quick_access) ? false : $quick_access, 2025 'multi_shop' => Shop::isFeatureActive(), 2026 'shop_list' => $helperShop->getRenderedShopList(), 2027 'current_shop_name' => $helperShop->getCurrentShopName(), 2028 'shop' => $this->context->shop, 2029 'shop_group' => new ShopGroup((int) Shop::getContextShopGroupID()), 2030 'is_multishop' => $is_multishop, 2031 'multishop_context' => $this->multishop_context, 2032 'default_tab_link' => $this->context->link->getAdminLink(Tab::getClassNameById((int) Context::getContext()->employee->default_tab)), 2033 'login_link' => $this->context->link->getAdminLink('AdminLogin'), 2034 'logout_link' => $this->context->link->getAdminLink('AdminLogin', true, [], ['logout' => 1]), 2035 'collapse_menu' => isset($this->context->cookie->collapse_menu) ? (int) $this->context->cookie->collapse_menu : 0, 2036 ]); 2037 } else { 2038 $this->context->smarty->assign('default_tab_link', $this->context->link->getAdminLink('AdminDashboard')); 2039 } 2040 2041 // Shop::initialize() in config.php may empty $this->context->shop->virtual_uri so using a new shop instance for getBaseUrl() 2042 $this->context->shop = new Shop((int) $this->context->shop->id); 2043 2044 $this->context->smarty->assign([ 2045 'img_dir' => _PS_IMG_, 2046 'iso' => $this->context->language->iso_code, 2047 'class_name' => $this->className, 2048 'iso_user' => $this->context->language->iso_code, 2049 'lang_is_rtl' => $this->context->language->is_rtl, 2050 'country_iso_code' => $this->context->country->iso_code, 2051 'version' => _PS_VERSION_, 2052 'lang_iso' => $this->context->language->iso_code, 2053 'full_language_code' => $this->context->language->language_code, 2054 'full_cldr_language_code' => $this->context->getCurrentLocale()->getCode(), 2055 'link' => $this->context->link, 2056 'shop_name' => Configuration::get('PS_SHOP_NAME'), 2057 'base_url' => $this->context->shop->getBaseURL(true), 2058 'current_parent_id' => (int) Tab::getCurrentParentId(), 2059 'tabs' => $tabs, 2060 'current_tab_level' => $currentTabLevel, 2061 'install_dir_exists' => file_exists(_PS_ADMIN_DIR_ . '/../install'), 2062 'pic_dir' => _THEME_PROD_PIC_DIR_, 2063 'controller_name' => htmlentities(Tools::getValue('controller')), 2064 'currentIndex' => self::$currentIndex, 2065 'bootstrap' => $this->bootstrap, 2066 'default_language' => (int) Configuration::get('PS_LANG_DEFAULT'), 2067 'display_addons_connection' => Tab::checkTabRights(Tab::getIdFromClassName('AdminModulesController')), 2068 ]); 2069 } 2070 2071 private function getNotificationTip($type) 2072 { 2073 $tips = [ 2074 'order' => [ 2075 $this->trans( 2076 'Have you checked your [1][2]abandoned carts[/2][/1]?[3]Your next order could be hiding there!', 2077 [ 2078 '[1]' => '<strong>', 2079 '[/1]' => '</strong>', 2080 '[2]' => '<a href="' . $this->context->link->getAdminLink('AdminCarts', true, [], ['action' => 'filterOnlyAbandonedCarts']) . '">', 2081 '[/2]' => '</a>', 2082 '[3]' => '<br>', 2083 ], 2084 'Admin.Navigation.Notification' 2085 ), 2086 ], 2087 'customer' => [ 2088 $this->trans('Are you active on social media these days?', [], 'Admin.Navigation.Notification'), 2089 ], 2090 'customer_message' => [ 2091 $this->trans('Seems like all your customers are happy :)', [], 'Admin.Navigation.Notification'), 2092 ], 2093 ]; 2094 2095 if (!isset($tips[$type])) { 2096 return ''; 2097 } 2098 2099 return $tips[$type][array_rand($tips[$type])]; 2100 } 2101 2102 private function getTabs($parentId = 0, $level = 0) 2103 { 2104 $tabs = Tab::getTabs($this->context->language->id, $parentId); 2105 $current_id = Tab::getCurrentParentId($this->controller_name ? $this->controller_name : ''); 2106 2107 foreach ($tabs as $index => $tab) { 2108 if (!Tab::checkTabRights($tab['id_tab']) 2109 || !$tab['enabled'] 2110 || ($tab['class_name'] == 'AdminStock' && Configuration::get('PS_ADVANCED_STOCK_MANAGEMENT') == 0) 2111 || $tab['class_name'] == 'AdminCarrierWizard') { 2112 unset($tabs[$index]); 2113 2114 continue; 2115 } 2116 2117 // tab[class_name] does not contains the "Controller" suffix 2118 if (($tab['class_name'] . 'Controller' == get_class($this)) || ($current_id == $tab['id_tab']) || $tab['class_name'] == $this->controller_name) { 2119 $tabs[$index]['current'] = true; 2120 $tabs[$index]['current_level'] = $level; 2121 } else { 2122 $tabs[$index]['current'] = false; 2123 } 2124 $tabs[$index]['img'] = null; 2125 $tabs[$index]['href'] = $this->context->link->getTabLink($tab); 2126 $tabs[$index]['sub_tabs'] = array_values($this->getTabs($tab['id_tab'], $level + 1)); 2127 2128 $subTabHref = $this->getTabLinkFromSubTabs($tabs[$index]['sub_tabs']); 2129 if (!empty($subTabHref)) { 2130 $tabs[$index]['href'] = $subTabHref; 2131 } elseif (0 == $tabs[$index]['id_parent'] && '' == $tabs[$index]['icon']) { 2132 unset($tabs[$index]); 2133 } elseif (empty($tabs[$index]['icon'])) { 2134 $tabs[$index]['icon'] = 'extension'; 2135 } 2136 2137 if (array_key_exists($index, $tabs) && array_key_exists('sub_tabs', $tabs[$index])) { 2138 foreach ($tabs[$index]['sub_tabs'] as $sub_tab) { 2139 if ((int) $sub_tab['current'] == true) { 2140 $tabs[$index]['current'] = true; 2141 $tabs[$index]['current_level'] = $sub_tab['current_level']; 2142 } 2143 } 2144 } 2145 } 2146 2147 return $tabs; 2148 } 2149 2150 /** 2151 * Declare an action to use for each row in the list. 2152 * 2153 * @param string $action 2154 */ 2155 public function addRowAction($action) 2156 { 2157 $action = strtolower($action); 2158 $this->actions[] = $action; 2159 } 2160 2161 /** 2162 * Add an action to use for each row in the list. 2163 * 2164 * @param string $action 2165 * @param array $list 2166 */ 2167 public function addRowActionSkipList($action, $list) 2168 { 2169 $action = strtolower($action); 2170 $list = (array) $list; 2171 2172 if (array_key_exists($action, $this->list_skip_actions)) { 2173 $this->list_skip_actions[$action] = array_merge($this->list_skip_actions[$action], $list); 2174 } else { 2175 $this->list_skip_actions[$action] = $list; 2176 } 2177 } 2178 2179 /** 2180 * Assign smarty variables for all default views, list and form, then call other init functions. 2181 */ 2182 public function initContent() 2183 { 2184 if (!$this->viewAccess()) { 2185 $this->errors[] = $this->trans('You do not have permission to view this.', [], 'Admin.Notifications.Error'); 2186 2187 return; 2188 } 2189 2190 if ($this->display == 'edit' || $this->display == 'add') { 2191 if (!$this->loadObject(true)) { 2192 return; 2193 } 2194 2195 $this->content .= $this->renderForm(); 2196 } elseif ($this->display == 'view') { 2197 // Some controllers use the view action without an object 2198 if ($this->className) { 2199 $this->loadObject(true); 2200 } 2201 $this->content .= $this->renderView(); 2202 } elseif ($this->display == 'details') { 2203 $this->content .= $this->renderDetails(); 2204 } elseif (!$this->ajax) { 2205 // FIXME: Sorry. I'm not very proud of this, but no choice... Please wait sf refactoring to solve this. 2206 if (get_class($this) != 'AdminCarriersController') { 2207 $this->content .= $this->renderModulesList(); 2208 } 2209 $this->content .= $this->renderKpis(); 2210 $this->content .= $this->renderList(); 2211 $this->content .= $this->renderOptions(); 2212 2213 // if we have to display the required fields form 2214 if ($this->required_database) { 2215 $this->content .= $this->displayRequiredFields(); 2216 } 2217 } 2218 2219 $this->context->smarty->assign([ 2220 'content' => $this->content, 2221 ]); 2222 } 2223 2224 public function initToolbarFlags() 2225 { 2226 $this->getLanguages(); 2227 2228 $this->initToolbar(); 2229 $this->initPageHeaderToolbar(); 2230 2231 $this->context->smarty->assign([ 2232 'maintenance_mode' => !(bool) Configuration::get('PS_SHOP_ENABLE'), 2233 'debug_mode' => (bool) _PS_MODE_DEV_, 2234 'lite_display' => $this->lite_display, 2235 'url_post' => self::$currentIndex . '&token=' . $this->token, 2236 'show_page_header_toolbar' => $this->show_page_header_toolbar, 2237 'page_header_toolbar_title' => $this->page_header_toolbar_title, 2238 'title' => $this->page_header_toolbar_title, 2239 'toolbar_btn' => $this->page_header_toolbar_btn, 2240 'page_header_toolbar_btn' => $this->page_header_toolbar_btn, 2241 ]); 2242 } 2243 2244 /** 2245 * Init tab modules list and add button in toolbar. 2246 * 2247 * @deprecated since 1.7.7.0 2248 */ 2249 protected function initTabModuleList() 2250 { 2251 @trigger_error(sprintf('The "%s()" method is deprecated and has no effect since 1.7.7.0', __METHOD__), E_USER_DEPRECATED); 2252 } 2253 2254 /** 2255 * @deprecated since 1.7.7.0 2256 */ 2257 protected function addPageHeaderToolBarModulesListButton() 2258 { 2259 @trigger_error(sprintf('The "%s()" method is deprecated and has no effect since 1.7.7.0', __METHOD__), E_USER_DEPRECATED); 2260 } 2261 2262 /** 2263 * @deprecated since 1.7.7.0 2264 */ 2265 protected function addToolBarModulesListButton() 2266 { 2267 @trigger_error(sprintf('The "%s()" method is deprecated and has no effect since 1.7.7.0', __METHOD__), E_USER_DEPRECATED); 2268 } 2269 2270 protected function getAdminModulesUrl() 2271 { 2272 return $this->context->link->getAdminLink('AdminModulesCatalog'); 2273 } 2274 2275 /** 2276 * @deprecated since 1.7.7.0 2277 */ 2278 protected function filterTabModuleList() 2279 { 2280 @trigger_error(sprintf('The "%s()" method is deprecated and has no effect since 1.7.7.0', __METHOD__), E_USER_DEPRECATED); 2281 } 2282 2283 /** 2284 * Initialize the invalid doom page of death. 2285 */ 2286 public function initCursedPage() 2287 { 2288 $this->layout = 'invalid_token.tpl'; 2289 } 2290 2291 /** 2292 * Assign smarty variables for the footer. 2293 */ 2294 public function initFooter() 2295 { 2296 //RTL Support 2297 //rtl.js overrides inline styles 2298 //iso_code.css overrides default fonts for every language (optional) 2299 if ($this->context->language->is_rtl) { 2300 $this->addJS(_PS_JS_DIR_ . 'rtl.js'); 2301 $this->addCSS(__PS_BASE_URI__ . $this->admin_webpath . '/themes/' . $this->bo_theme . '/css/' . $this->context->language->iso_code . '.css', 'all', false); 2302 } 2303 2304 // We assign js and css files on the last step before display template, because controller can add many js and css files 2305 $this->context->smarty->assign('css_files', $this->css_files); 2306 $this->context->smarty->assign('js_files', array_unique($this->js_files)); 2307 2308 $this->context->smarty->assign([ 2309 'ps_version' => _PS_VERSION_, 2310 'timer_start' => $this->timer_start, 2311 'iso_is_fr' => strtoupper($this->context->language->iso_code) == 'FR', 2312 'modals' => $this->renderModal(), 2313 ]); 2314 } 2315 2316 /** 2317 * @throws Exception 2318 * @throws SmartyException 2319 */ 2320 public function initModal() 2321 { 2322 if ($this->logged_on_addons) { 2323 $this->context->smarty->assign([ 2324 'logged_on_addons' => 1, 2325 'username_addons' => $this->context->cookie->username_addons, 2326 ]); 2327 } 2328 2329 $this->context->smarty->assign([ 2330 'img_base_path' => __PS_BASE_URI__ . basename(_PS_ADMIN_DIR_) . '/', 2331 'check_url_fopen' => (ini_get('allow_url_fopen') ? 'ok' : 'ko'), 2332 'check_openssl' => (extension_loaded('openssl') ? 'ok' : 'ko'), 2333 'add_permission' => 1, 2334 'addons_register_link' => 'https://addons.prestashop.com/' . $this->context->language->iso_code . '/login?' 2335 . 'email=' . urlencode($this->context->employee->email) 2336 . '&firstname=' . urlencode($this->context->employee->firstname) 2337 . '&lastname=' . urlencode($this->context->employee->lastname) 2338 . '&website=' . urlencode($this->context->shop->getBaseURL()) 2339 . '&utm_source=back-office&utm_medium=connect-to-addons' 2340 . '&utm_campaign=back-office-' . Tools::strtoupper($this->context->language->iso_code) 2341 . '&utm_content=' . (defined('_PS_HOST_MODE_') ? 'cloud' : 'download') . '#createnow', 2342 'addons_forgot_password_link' => '//addons.prestashop.com/' . $this->context->language->iso_code . '/forgot-your-password', 2343 ]); 2344 2345 $this->modals[] = [ 2346 'modal_id' => 'modal_addons_connect', 2347 'modal_class' => 'modal-md', 2348 'modal_title' => '<i class="icon-puzzle-piece"></i> <a target="_blank" href="https://addons.prestashop.com/' 2349 . '?utm_source=back-office&utm_medium=modules' 2350 . '&utm_campaign=back-office-' . Tools::strtoupper($this->context->language->iso_code) 2351 . '&utm_content=' . (defined('_PS_HOST_MODE_') ? 'cloud' : 'download') . '">PrestaShop Addons</a>', 2352 'modal_content' => $this->context->smarty->fetch('controllers/modules/login_addons.tpl'), 2353 ]; 2354 } 2355 2356 /** 2357 * @return string 2358 * 2359 * @throws Exception 2360 * @throws SmartyException 2361 */ 2362 public function renderModal() 2363 { 2364 $modal_render = ''; 2365 if (is_array($this->modals) && count($this->modals)) { 2366 foreach ($this->modals as $modal) { 2367 $this->context->smarty->assign($modal); 2368 $modal_render .= $this->context->smarty->fetch('modal.tpl'); 2369 } 2370 } 2371 2372 return $modal_render; 2373 } 2374 2375 /** 2376 * Was used to display a list of recommended modules. 2377 * 2378 * @param string|bool $tracking_source Source information for URL used by "Install" button 2379 * 2380 * @return string Empty 2381 * 2382 * @deprecated since 1.7.4.0 2383 */ 2384 public function renderModulesList($tracking_source = false) 2385 { 2386 return ''; 2387 } 2388 2389 /** 2390 * Function used to render the list to display for this controller. 2391 * 2392 * @return string|false 2393 * 2394 * @throws PrestaShopException 2395 */ 2396 public function renderList() 2397 { 2398 if (!($this->fields_list && is_array($this->fields_list))) { 2399 return false; 2400 } 2401 $this->getList($this->context->language->id); 2402 2403 // If list has 'active' field, we automatically create bulk action 2404 if (isset($this->fields_list) && is_array($this->fields_list) && array_key_exists('active', $this->fields_list) 2405 && !empty($this->fields_list['active'])) { 2406 if (!is_array($this->bulk_actions)) { 2407 $this->bulk_actions = []; 2408 } 2409 2410 $this->bulk_actions = array_merge([ 2411 'enableSelection' => [ 2412 'text' => $this->trans('Enable selection'), 2413 'icon' => 'icon-power-off text-success', 2414 ], 2415 'disableSelection' => [ 2416 'text' => $this->trans('Disable selection'), 2417 'icon' => 'icon-power-off text-danger', 2418 ], 2419 'divider' => [ 2420 'text' => 'divider', 2421 ], 2422 ], $this->bulk_actions); 2423 } 2424 2425 $helper = new HelperList(); 2426 2427 // Empty list is ok 2428 if (!is_array($this->_list)) { 2429 $this->displayWarning($this->trans('Bad SQL query') . '<br />' . htmlspecialchars($this->_list_error)); 2430 2431 return false; 2432 } 2433 2434 $this->setHelperDisplay($helper); 2435 $helper->_default_pagination = $this->_default_pagination; 2436 $helper->_pagination = $this->_pagination; 2437 $helper->tpl_vars = $this->getTemplateListVars(); 2438 $helper->tpl_delete_link_vars = $this->tpl_delete_link_vars; 2439 2440 // For compatibility reasons, we have to check standard actions in class attributes 2441 foreach ($this->actions_available as $action) { 2442 if (!in_array($action, $this->actions) && isset($this->$action) && $this->$action) { 2443 $this->actions[] = $action; 2444 } 2445 } 2446 2447 $helper->is_cms = $this->is_cms; 2448 $helper->sql = $this->_listsql; 2449 $list = $helper->generateList($this->_list, $this->fields_list); 2450 2451 return $list; 2452 } 2453 2454 public function getTemplateListVars() 2455 { 2456 return $this->tpl_list_vars; 2457 } 2458 2459 /** 2460 * Override to render the view page. 2461 * 2462 * @return string 2463 */ 2464 public function renderView() 2465 { 2466 $helper = new HelperView($this); 2467 $this->setHelperDisplay($helper); 2468 $helper->tpl_vars = $this->getTemplateViewVars(); 2469 if (null !== $this->base_tpl_view) { 2470 $helper->base_tpl = $this->base_tpl_view; 2471 } 2472 $view = $helper->generateView(); 2473 2474 return $view; 2475 } 2476 2477 public function getTemplateViewVars() 2478 { 2479 return $this->tpl_view_vars; 2480 } 2481 2482 /** 2483 * Override to render the view page. 2484 * 2485 * @return string|false 2486 */ 2487 public function renderDetails() 2488 { 2489 return $this->renderList(); 2490 } 2491 2492 /** 2493 * Function used to render the form for this controller. 2494 * 2495 * @return string 2496 * 2497 * @throws Exception 2498 * @throws SmartyException 2499 */ 2500 public function renderForm() 2501 { 2502 if (!$this->default_form_language) { 2503 $this->getLanguages(); 2504 } 2505 2506 if (Tools::getValue('submitFormAjax')) { 2507 $this->content .= $this->context->smarty->fetch('form_submit_ajax.tpl'); 2508 } 2509 2510 if ($this->fields_form && is_array($this->fields_form)) { 2511 if (!$this->multiple_fieldsets) { 2512 $this->fields_form = [['form' => $this->fields_form]]; 2513 } 2514 2515 // For add a fields via an override of $fields_form, use $fields_form_override 2516 if (is_array($this->fields_form_override) && !empty($this->fields_form_override)) { 2517 $this->fields_form[0]['form']['input'] = array_merge($this->fields_form[0]['form']['input'], $this->fields_form_override); 2518 } 2519 2520 $fields_value = $this->getFieldsValue($this->object); 2521 2522 Hook::exec('action' . $this->controller_name . 'FormModifier', [ 2523 'object' => &$this->object, 2524 'fields' => &$this->fields_form, 2525 'fields_value' => &$fields_value, 2526 'form_vars' => &$this->tpl_form_vars, 2527 ]); 2528 2529 $helper = new HelperForm($this); 2530 $this->setHelperDisplay($helper); 2531 $helper->fields_value = $fields_value; 2532 $helper->submit_action = $this->submit_action; 2533 $helper->tpl_vars = $this->getTemplateFormVars(); 2534 $helper->show_cancel_button = (isset($this->show_form_cancel_button)) ? $this->show_form_cancel_button : ($this->display == 'add' || $this->display == 'edit'); 2535 2536 $back = rawurldecode(Tools::getValue('back', '')); 2537 if (empty($back)) { 2538 $back = self::$currentIndex . '&token=' . $this->token; 2539 } 2540 if (!Validate::isCleanHtml($back)) { 2541 die(Tools::displayError()); 2542 } 2543 2544 $helper->back_url = $back; 2545 null !== $this->base_tpl_form ? $helper->base_tpl = $this->base_tpl_form : ''; 2546 if ($this->access('view')) { 2547 if (Tools::getValue('back')) { 2548 $helper->tpl_vars['back'] = Tools::safeOutput(Tools::getValue('back')); 2549 } else { 2550 $helper->tpl_vars['back'] = Tools::safeOutput(self::$currentIndex . '&token=' . $this->token); 2551 } 2552 } 2553 $form = $helper->generateForm($this->fields_form); 2554 2555 return $form; 2556 } 2557 } 2558 2559 public function getTemplateFormVars() 2560 { 2561 return $this->tpl_form_vars; 2562 } 2563 2564 public function renderKpis() 2565 { 2566 } 2567 2568 /** 2569 * Function used to render the options for this controller. 2570 * 2571 * @return string 2572 */ 2573 public function renderOptions() 2574 { 2575 Hook::exec('action' . $this->controller_name . 'OptionsModifier', [ 2576 'options' => &$this->fields_options, 2577 'option_vars' => &$this->tpl_option_vars, 2578 ]); 2579 2580 if ($this->fields_options && is_array($this->fields_options)) { 2581 if (isset($this->display) && $this->display != 'options' && $this->display != 'list') { 2582 $this->show_toolbar = false; 2583 } else { 2584 $this->display = 'options'; 2585 } 2586 2587 unset($this->toolbar_btn); 2588 $this->initToolbar(); 2589 $helper = new HelperOptions($this); 2590 $this->setHelperDisplay($helper); 2591 $helper->id = $this->id; 2592 $helper->tpl_vars = $this->tpl_option_vars; 2593 $options = $helper->generateOptions($this->fields_options); 2594 2595 return $options; 2596 } 2597 } 2598 2599 /** 2600 * This function sets various display options for helper list. 2601 * 2602 * @param Helper $helper 2603 */ 2604 public function setHelperDisplay(Helper $helper) 2605 { 2606 if (empty($this->toolbar_title)) { 2607 $this->initToolbarTitle(); 2608 } 2609 // tocheck 2610 if ($this->object && $this->object->id) { 2611 $helper->id = $this->object->id; 2612 } 2613 2614 // @todo : move that in Helper 2615 $helper->title = is_array($this->toolbar_title) ? implode(' ' . Configuration::get('PS_NAVIGATION_PIPE') . ' ', $this->toolbar_title) : $this->toolbar_title; 2616 $helper->toolbar_btn = $this->toolbar_btn; 2617 $helper->show_toolbar = $this->show_toolbar; 2618 $helper->toolbar_scroll = $this->toolbar_scroll; 2619 $helper->override_folder = $this->tpl_folder; 2620 $helper->actions = $this->actions; 2621 $helper->simple_header = $this->list_simple_header; 2622 $helper->bulk_actions = $this->bulk_actions; 2623 $helper->currentIndex = self::$currentIndex; 2624 $helper->className = $this->className; 2625 $helper->table = $this->table; 2626 $helper->name_controller = Tools::getValue('controller'); 2627 $helper->orderBy = $this->_orderBy; 2628 $helper->orderWay = $this->_orderWay; 2629 $helper->listTotal = $this->_listTotal; 2630 $helper->shopLink = $this->shopLink; 2631 $helper->shopLinkType = $this->shopLinkType; 2632 $helper->identifier = $this->identifier; 2633 $helper->token = $this->token; 2634 $helper->languages = $this->_languages; 2635 $helper->specificConfirmDelete = $this->specificConfirmDelete; 2636 $helper->imageType = $this->imageType; 2637 $helper->no_link = $this->list_no_link; 2638 $helper->colorOnBackground = $this->colorOnBackground; 2639 $helper->ajax_params = (isset($this->ajax_params) ? $this->ajax_params : null); 2640 $helper->default_form_language = $this->default_form_language; 2641 $helper->allow_employee_form_lang = $this->allow_employee_form_lang; 2642 $helper->multiple_fieldsets = $this->multiple_fieldsets; 2643 $helper->row_hover = $this->row_hover; 2644 $helper->position_identifier = $this->position_identifier; 2645 $helper->position_group_identifier = $this->position_group_identifier; 2646 $helper->controller_name = $this->controller_name; 2647 $helper->list_id = isset($this->list_id) ? $this->list_id : $this->table; 2648 $helper->bootstrap = $this->bootstrap; 2649 2650 // For each action, try to add the corresponding skip elements list 2651 $helper->list_skip_actions = $this->list_skip_actions; 2652 2653 $this->helper = $helper; 2654 } 2655 2656 /** 2657 * @deprecated 1.6.0 2658 */ 2659 public function setDeprecatedMedia() 2660 { 2661 } 2662 2663 public function setMedia($isNewTheme = false) 2664 { 2665 if ($isNewTheme) { 2666 $this->addCSS(__PS_BASE_URI__ . $this->admin_webpath . '/themes/new-theme/public/theme.css', 'all', 1); 2667 $this->addJS(__PS_BASE_URI__ . $this->admin_webpath . '/themes/new-theme/public/main.bundle.js'); 2668 2669 // the multistore dropdown should be called only once, and only if multistore is used 2670 if ($this->container->get('prestashop.adapter.multistore_feature')->isUsed()) { 2671 $this->addJs(__PS_BASE_URI__ . $this->admin_webpath . '/themes/new-theme/public/multistore_dropdown.bundle.js'); 2672 } 2673 $this->addJqueryPlugin(['chosen', 'fancybox']); 2674 } else { 2675 //Bootstrap 2676 $this->addCSS(__PS_BASE_URI__ . $this->admin_webpath . '/themes/' . $this->bo_theme . '/css/' . $this->bo_css, 'all', 0); 2677 $this->addCSS(__PS_BASE_URI__ . $this->admin_webpath . '/themes/' . $this->bo_theme . '/css/vendor/titatoggle-min.css', 'all', 0); 2678 $this->addCSS(__PS_BASE_URI__ . $this->admin_webpath . '/themes/' . $this->bo_theme . '/public/theme.css', 'all', 0); 2679 2680 // add Jquery 3 and its migration script 2681 $this->addJs(_PS_JS_DIR_ . 'jquery/jquery-3.5.1.min.js'); 2682 $this->addJs(_PS_JS_DIR_ . 'jquery/bo-migrate-mute.min.js'); 2683 $this->addJs(_PS_JS_DIR_ . 'jquery/jquery-migrate-3.1.0.min.js'); 2684 // implement $.browser object and live method, that has been removed since jquery 1.9 2685 $this->addJs(_PS_JS_DIR_ . 'jquery/jquery.browser-0.1.0.min.js'); 2686 $this->addJs(_PS_JS_DIR_ . 'jquery/jquery.live-polyfill-1.1.2.min.js'); 2687 2688 $this->addJqueryPlugin(['scrollTo', 'alerts', 'chosen', 'autosize', 'fancybox']); 2689 $this->addJqueryPlugin('growl', null, false); 2690 $this->addJqueryUI(['ui.slider', 'ui.datepicker']); 2691 2692 $this->addJS(__PS_BASE_URI__ . $this->admin_webpath . '/themes/' . $this->bo_theme . '/js/vendor/bootstrap.min.js'); 2693 $this->addJS(__PS_BASE_URI__ . $this->admin_webpath . '/themes/' . $this->bo_theme . '/js/vendor/modernizr.min.js'); 2694 $this->addJS(__PS_BASE_URI__ . $this->admin_webpath . '/themes/' . $this->bo_theme . '/js/modernizr-loads.js'); 2695 $this->addJS(__PS_BASE_URI__ . $this->admin_webpath . '/themes/' . $this->bo_theme . '/js/vendor/moment-with-langs.min.js'); 2696 $this->addJS(__PS_BASE_URI__ . $this->admin_webpath . '/themes/' . $this->bo_theme . '/public/bundle.js'); 2697 2698 $this->addJS(_PS_JS_DIR_ . 'jquery/plugins/timepicker/jquery-ui-timepicker-addon.js'); 2699 2700 if (!$this->lite_display) { 2701 $this->addJS(__PS_BASE_URI__ . $this->admin_webpath . '/themes/' . $this->bo_theme . '/js/help.js'); 2702 } 2703 2704 if (!Tools::getValue('submitFormAjax')) { 2705 $this->addJS(_PS_JS_DIR_ . 'admin/notifications.js'); 2706 } 2707 2708 if (defined('_PS_HOST_MODE_') && _PS_HOST_MODE_) { 2709 $this->addJS('https://cdn.statuspage.io/se-v2.js'); 2710 2711 Media::addJsDefL('status_operational', $this->trans('Operational')); 2712 Media::addJsDefL('status_degraded_performance', $this->trans('Degraded Performance')); 2713 Media::addJsDefL('status_partial_outage', $this->trans('Partial Outage')); 2714 Media::addJsDefL('status_major_outage', $this->trans('Major Outage')); 2715 Media::addJsDef(['host_cluster' => defined('_PS_HOST_CLUSTER_') ? _PS_HOST_CLUSTER_ : 'fr1']); 2716 } 2717 2718 // Specific Admin Theme 2719 $this->addCSS(__PS_BASE_URI__ . $this->admin_webpath . '/themes/' . $this->bo_theme . '/css/overrides.css', 'all', PHP_INT_MAX); 2720 } 2721 2722 $this->addJS([ 2723 _PS_JS_DIR_ . 'admin.js?v=' . _PS_VERSION_, // TODO: SEE IF REMOVABLE 2724 __PS_BASE_URI__ . $this->admin_webpath . '/themes/new-theme/public/cldr.bundle.js', 2725 _PS_JS_DIR_ . 'tools.js?v=' . _PS_VERSION_, 2726 __PS_BASE_URI__ . $this->admin_webpath . '/public/bundle.js', 2727 ]); 2728 2729 Media::addJsDef([ 2730 'changeFormLanguageUrl' => $this->context->link->getAdminLink( 2731 'AdminEmployees', 2732 true, 2733 [], 2734 ['action' => 'formLanguage'] 2735 ), 2736 ]); 2737 Media::addJsDef(['host_mode' => (defined('_PS_HOST_MODE_') && _PS_HOST_MODE_)]); 2738 Media::addJsDef(['baseDir' => __PS_BASE_URI__]); 2739 Media::addJsDef(['baseAdminDir' => __PS_BASE_URI__ . basename(_PS_ADMIN_DIR_) . '/']); 2740 Media::addJsDef(['currency' => [ 2741 'iso_code' => Context::getContext()->currency->iso_code, 2742 'sign' => Context::getContext()->currency->sign, 2743 'name' => Context::getContext()->currency->name, 2744 'format' => Context::getContext()->currency->format, 2745 ]]); 2746 Media::addJsDef( 2747 [ 2748 'currency_specifications' => $this->preparePriceSpecifications($this->context), 2749 'number_specifications' => $this->prepareNumberSpecifications($this->context), 2750 ] 2751 ); 2752 2753 Media::addJsDef([ 2754 'prestashop' => [ 2755 'debug' => _PS_MODE_DEV_, 2756 ], 2757 ]); 2758 2759 // Execute Hook AdminController SetMedia 2760 Hook::exec('actionAdminControllerSetMedia'); 2761 } 2762 2763 /** 2764 * Non-static method which uses AdminController::translate(). 2765 * 2766 * @deprecated use Context::getContext()->getTranslator()->trans($id, $parameters, $domain, $locale); instead 2767 * 2768 * @param string $string Term or expression in english 2769 * @param string|null $class Name of the class 2770 * @param bool $addslashes If set to true, the return value will pass through addslashes(). Otherwise, stripslashes(). 2771 * @param bool $htmlentities If set to true(default), the return value will pass through htmlentities($string, ENT_QUOTES, 'utf-8') 2772 * 2773 * @return string the translation if available, or the english default text 2774 */ 2775 protected function l($string, $class = null, $addslashes = false, $htmlentities = true) 2776 { 2777 $translated = $this->translator->trans($string); 2778 if ($translated !== $string) { 2779 return $translated; 2780 } 2781 2782 if ($class === null || $class == 'AdminTab') { 2783 $class = substr(get_class($this), 0, -10); 2784 } elseif (strtolower(substr($class, -10)) == 'controller') { 2785 /* classname has changed, from AdminXXX to AdminXXXController, so we remove 10 characters and we keep same keys */ 2786 $class = substr($class, 0, -10); 2787 } 2788 2789 return Translate::getAdminTranslation($string, $class, $addslashes, $htmlentities); 2790 } 2791 2792 /** 2793 * Init context and dependencies, handles POST and GET. 2794 */ 2795 public function init() 2796 { 2797 Hook::exec( 2798 'actionAdminControllerInitBefore', 2799 [ 2800 'controller' => $this, 2801 ] 2802 ); 2803 parent::init(); 2804 2805 if (Tools::getValue('ajax')) { 2806 $this->ajax = '1'; 2807 } 2808 2809 if (null === $this->context->link) { 2810 $protocol_link = (Tools::usingSecureMode() && Configuration::get('PS_SSL_ENABLED')) ? 'https://' : 'http://'; 2811 $protocol_content = (Tools::usingSecureMode() && Configuration::get('PS_SSL_ENABLED')) ? 'https://' : 'http://'; 2812 $this->context->link = new Link($protocol_link, $protocol_content); 2813 } 2814 2815 if (isset($_GET['logout'])) { 2816 $this->context->employee->logout(); 2817 } 2818 if (isset(Context::getContext()->cookie->last_activity)) { 2819 if ($this->context->cookie->last_activity + self::AUTH_COOKIE_LIFETIME < time()) { 2820 $this->context->employee->logout(); 2821 } else { 2822 $this->context->cookie->last_activity = time(); 2823 } 2824 } 2825 2826 if ( 2827 !$this->isAnonymousAllowed() 2828 && ( 2829 $this->controller_name != 'AdminLogin' 2830 && ( 2831 !isset($this->context->employee) 2832 || !$this->context->employee->isLoggedBack() 2833 ) 2834 ) 2835 ) { 2836 if (isset($this->context->employee)) { 2837 $this->context->employee->logout(); 2838 } 2839 $email = false; 2840 if (Tools::getValue('email') && Validate::isEmail(Tools::getValue('email'))) { 2841 $email = Tools::getValue('email'); 2842 } 2843 Tools::redirectAdmin($this->context->link->getAdminLink('AdminLogin') . ((!isset($_GET['logout']) && $this->controller_name != 'AdminNotFound' && Tools::getValue('controller')) ? '&redirect=' . $this->controller_name : '') . ($email ? '&email=' . $email : '')); 2844 } 2845 2846 // Set current index 2847 $current_index = 'index.php' . (($controller = Tools::getValue('controller')) ? '?controller=' . $controller : ''); 2848 if ($back = Tools::getValue('back')) { 2849 $current_index .= '&back=' . urlencode($back); 2850 } 2851 self::$currentIndex = $current_index; 2852 2853 if ((int) Tools::getValue('liteDisplaying')) { 2854 $this->display_header = false; 2855 $this->display_header_javascript = true; 2856 $this->display_footer = false; 2857 $this->content_only = false; 2858 $this->lite_display = true; 2859 } 2860 2861 if ($this->ajax && method_exists($this, 'ajaxPreprocess')) { 2862 $this->ajaxPreProcess(); 2863 } 2864 2865 Employee::setLastConnectionDate($this->context->employee->id); 2866 2867 $this->initProcess(); 2868 $this->initMultistoreHeader(); 2869 $this->initBreadcrumbs(); 2870 $this->initModal(); 2871 $this->initToolbarFlags(); 2872 $this->initNotifications(); 2873 Hook::exec( 2874 'actionAdminControllerInitAfter', 2875 [ 2876 'controller' => $this, 2877 ] 2878 ); 2879 } 2880 2881 /** 2882 * Sets the smarty variables and js defs used to show / hide some notifications. 2883 */ 2884 public function initNotifications() 2885 { 2886 $notificationsSettings = [ 2887 'show_new_orders' => Configuration::get('PS_SHOW_NEW_ORDERS'), 2888 'show_new_customers' => Configuration::get('PS_SHOW_NEW_CUSTOMERS'), 2889 'show_new_messages' => Configuration::get('PS_SHOW_NEW_MESSAGES'), 2890 ]; 2891 $this->context->smarty->assign($notificationsSettings); 2892 2893 Media::addJsDef($notificationsSettings); 2894 } 2895 2896 /** 2897 * @throws PrestaShopException 2898 */ 2899 public function initShopContext() 2900 { 2901 // Do not initialize context when the shop is not installed 2902 if (defined('PS_INSTALLATION_IN_PROGRESS')) { 2903 return; 2904 } 2905 2906 // Change shop context ? 2907 if (Shop::isFeatureActive() && Tools::getValue('setShopContext') !== false) { 2908 $this->context->cookie->shopContext = Tools::getValue('setShopContext'); 2909 $url = parse_url($_SERVER['REQUEST_URI']); 2910 $query = (isset($url['query'])) ? $url['query'] : ''; 2911 parse_str($query, $parse_query); 2912 unset($parse_query['setShopContext'], $parse_query['conf']); 2913 $http_build_query = http_build_query($parse_query, '', '&'); 2914 $this->redirect_after = $url['path'] . ($http_build_query ? '?' . $http_build_query : ''); 2915 } elseif (!Shop::isFeatureActive()) { 2916 $this->context->cookie->shopContext = 's-' . (int) Configuration::get('PS_SHOP_DEFAULT'); 2917 } elseif (Shop::getTotalShops(false, null) < 2 && $this->context->employee->isLoggedBack()) { 2918 $this->context->cookie->shopContext = 's-' . (int) $this->context->employee->getDefaultShopID(); 2919 } 2920 2921 $shop_id = null; 2922 Shop::setContext(Shop::CONTEXT_ALL); 2923 if ($this->context->cookie->shopContext && $this->context->employee->isLoggedBack()) { 2924 $split = explode('-', $this->context->cookie->shopContext); 2925 if (count($split) == 2) { 2926 if ($split[0] == 'g') { 2927 if ($this->context->employee->hasAuthOnShopGroup((int) $split[1])) { 2928 Shop::setContext(Shop::CONTEXT_GROUP, (int) $split[1]); 2929 } else { 2930 $shop_id = (int) $this->context->employee->getDefaultShopID(); 2931 Shop::setContext(Shop::CONTEXT_SHOP, $shop_id); 2932 } 2933 } elseif (Shop::getShop($split[1]) && $this->context->employee->hasAuthOnShop($split[1])) { 2934 $shop_id = (int) $split[1]; 2935 Shop::setContext(Shop::CONTEXT_SHOP, $shop_id); 2936 } else { 2937 $shop_id = (int) $this->context->employee->getDefaultShopID(); 2938 Shop::setContext(Shop::CONTEXT_SHOP, $shop_id); 2939 } 2940 } 2941 } 2942 2943 // Check multishop context and set right context if need 2944 if (!($this->multishop_context & Shop::getContext())) { 2945 if (Shop::getContext() == Shop::CONTEXT_SHOP && !($this->multishop_context & Shop::CONTEXT_SHOP)) { 2946 Shop::setContext(Shop::CONTEXT_GROUP, Shop::getContextShopGroupID()); 2947 } 2948 if (Shop::getContext() == Shop::CONTEXT_GROUP && !($this->multishop_context & Shop::CONTEXT_GROUP)) { 2949 Shop::setContext(Shop::CONTEXT_ALL); 2950 } 2951 } 2952 2953 // Replace existing shop if necessary 2954 if (!$shop_id) { 2955 $this->context->shop = new Shop((int) Configuration::get('PS_SHOP_DEFAULT')); 2956 } elseif ($this->context->shop->id != $shop_id) { 2957 $this->context->shop = new Shop((int) $shop_id); 2958 } 2959 2960 // Replace current default country 2961 $this->context->country = new Country((int) Configuration::get('PS_COUNTRY_DEFAULT')); 2962 $this->context->currency = new Currency(Configuration::get('PS_CURRENCY_DEFAULT')); 2963 } 2964 2965 /** 2966 * Retrieve GET and POST value and translate them to actions. 2967 */ 2968 public function initProcess() 2969 { 2970 if (!isset($this->list_id)) { 2971 $this->list_id = $this->table; 2972 } 2973 2974 // Manage list filtering 2975 if (Tools::isSubmit('submitFilter' . $this->list_id) 2976 || $this->context->cookie->{'submitFilter' . $this->list_id} !== false 2977 || Tools::getValue($this->list_id . 'Orderby') 2978 || Tools::getValue($this->list_id . 'Orderway')) { 2979 $this->filter = true; 2980 } 2981 2982 $this->id_object = (int) Tools::getValue($this->identifier); 2983 2984 /* Delete object image */ 2985 if (isset($_GET['deleteImage'])) { 2986 if ($this->access('delete')) { 2987 $this->action = 'delete_image'; 2988 } else { 2989 $this->errors[] = $this->trans('You do not have permission to delete this.', [], 'Admin.Notifications.Error'); 2990 } 2991 } elseif (isset($_GET['delete' . $this->table])) { 2992 /* Delete object */ 2993 if ($this->access('delete')) { 2994 $this->action = 'delete'; 2995 } else { 2996 $this->errors[] = $this->trans('You do not have permission to delete this.', [], 'Admin.Notifications.Error'); 2997 } 2998 } elseif ((isset($_GET['status' . $this->table]) || isset($_GET['status'])) && Tools::getValue($this->identifier)) { 2999 /* Change object status (active, inactive) */ 3000 if ($this->access('edit')) { 3001 $this->action = 'status'; 3002 } else { 3003 $this->errors[] = $this->trans('You do not have permission to edit this.', [], 'Admin.Notifications.Error'); 3004 } 3005 } elseif (isset($_GET['position'])) { 3006 /* Move an object */ 3007 if ($this->access('edit') == '1') { 3008 $this->action = 'position'; 3009 } else { 3010 $this->errors[] = $this->trans('You do not have permission to edit this.', [], 'Admin.Notifications.Error'); 3011 } 3012 } elseif (Tools::isSubmit('submitAdd' . $this->table) 3013 || Tools::isSubmit('submitAdd' . $this->table . 'AndStay') 3014 || Tools::isSubmit('submitAdd' . $this->table . 'AndPreview') 3015 || Tools::isSubmit('submitAdd' . $this->table . 'AndBackToParent')) { 3016 // case 1: updating existing entry 3017 if ($this->id_object) { 3018 if ($this->access('edit')) { 3019 $this->action = 'save'; 3020 if (Tools::isSubmit('submitAdd' . $this->table . 'AndStay')) { 3021 $this->display = 'edit'; 3022 } else { 3023 $this->display = 'list'; 3024 } 3025 } else { 3026 $this->errors[] = $this->trans('You do not have permission to edit this.', [], 'Admin.Notifications.Error'); 3027 } 3028 } else { 3029 // case 2: creating new entry 3030 if ($this->access('add')) { 3031 $this->action = 'save'; 3032 if (Tools::isSubmit('submitAdd' . $this->table . 'AndStay')) { 3033 $this->display = 'edit'; 3034 } else { 3035 $this->display = 'list'; 3036 } 3037 } else { 3038 $this->errors[] = $this->trans('You do not have permission to add this.', [], 'Admin.Notifications.Error'); 3039 } 3040 } 3041 } elseif (isset($_GET['add' . $this->table])) { 3042 if ($this->access('add')) { 3043 $this->action = 'new'; 3044 $this->display = 'add'; 3045 } else { 3046 $this->errors[] = $this->trans('You do not have permission to add this.', [], 'Admin.Notifications.Error'); 3047 } 3048 } elseif (isset($_GET['update' . $this->table], $_GET[$this->identifier])) { 3049 $this->display = 'edit'; 3050 if (!$this->access('edit')) { 3051 $this->errors[] = $this->trans('You do not have permission to edit this.', [], 'Admin.Notifications.Error'); 3052 } 3053 } elseif (isset($_GET['view' . $this->table])) { 3054 if ($this->access('view')) { 3055 $this->display = 'view'; 3056 $this->action = 'view'; 3057 } else { 3058 $this->errors[] = $this->trans('You do not have permission to view this.', [], 'Admin.Notifications.Error'); 3059 } 3060 } elseif (isset($_GET['details' . $this->table])) { 3061 if ($this->access('view')) { 3062 $this->display = 'details'; 3063 $this->action = 'details'; 3064 } else { 3065 $this->errors[] = $this->trans('You do not have permission to view this.', [], 'Admin.Notifications.Error'); 3066 } 3067 } elseif (isset($_GET['export' . $this->table])) { 3068 if ($this->access('view')) { 3069 $this->action = 'export'; 3070 } 3071 } elseif (isset($_POST['submitReset' . $this->list_id])) { 3072 /* Cancel all filters for this tab */ 3073 $this->action = 'reset_filters'; 3074 } elseif (Tools::isSubmit('submitOptions' . $this->table) || Tools::isSubmit('submitOptions')) { 3075 /* Submit options list */ 3076 $this->display = 'options'; 3077 if ($this->access('edit')) { 3078 $this->action = 'update_options'; 3079 } else { 3080 $this->errors[] = $this->trans('You do not have permission to edit this.', [], 'Admin.Notifications.Error'); 3081 } 3082 } elseif (Tools::getValue('action') && method_exists($this, 'process' . ucfirst(Tools::toCamelCase(Tools::getValue('action'))))) { 3083 $this->action = Tools::getValue('action'); 3084 } elseif (Tools::isSubmit('submitFields') && $this->required_database && $this->access('add') && $this->access('delete')) { 3085 $this->action = 'update_fields'; 3086 } elseif (is_array($this->bulk_actions)) { 3087 $submit_bulk_actions = array_merge([ 3088 'enableSelection' => [ 3089 'text' => $this->trans('Enable selection'), 3090 'icon' => 'icon-power-off text-success', 3091 ], 3092 'disableSelection' => [ 3093 'text' => $this->trans('Disable selection'), 3094 'icon' => 'icon-power-off text-danger', 3095 ], 3096 ], $this->bulk_actions); 3097 foreach ($submit_bulk_actions as $bulk_action => $params) { 3098 if (Tools::isSubmit('submitBulk' . $bulk_action . $this->table) || Tools::isSubmit('submitBulk' . $bulk_action)) { 3099 if ($bulk_action === 'delete') { 3100 if ($this->access('delete')) { 3101 $this->action = 'bulk' . $bulk_action; 3102 $this->boxes = Tools::getValue($this->table . 'Box'); 3103 if (empty($this->boxes) && $this->table == 'attribute') { 3104 $this->boxes = Tools::getValue($this->table . '_valuesBox'); 3105 } 3106 } else { 3107 $this->errors[] = $this->trans('You do not have permission to delete this.', [], 'Admin.Notifications.Error'); 3108 } 3109 3110 break; 3111 } elseif ($this->access('edit')) { 3112 $this->action = 'bulk' . $bulk_action; 3113 $this->boxes = Tools::getValue($this->table . 'Box'); 3114 } else { 3115 $this->errors[] = $this->trans('You do not have permission to edit this.', [], 'Admin.Notifications.Error'); 3116 } 3117 3118 break; 3119 } elseif (Tools::isSubmit('submitBulk')) { 3120 if ($bulk_action === 'delete') { 3121 if ($this->access('delete')) { 3122 $this->action = 'bulk' . $bulk_action; 3123 $this->boxes = Tools::getValue($this->table . 'Box'); 3124 } else { 3125 $this->errors[] = $this->trans('You do not have permission to delete this.', [], 'Admin.Notifications.Error'); 3126 } 3127 3128 break; 3129 } elseif ($this->access('edit')) { 3130 $this->action = 'bulk' . Tools::getValue('select_submitBulk'); 3131 $this->boxes = Tools::getValue($this->table . 'Box'); 3132 } else { 3133 $this->errors[] = $this->trans('You do not have permission to edit this.', [], 'Admin.Notifications.Error'); 3134 } 3135 3136 break; 3137 } 3138 } 3139 } elseif (!empty($this->fields_options) && empty($this->fields_list)) { 3140 $this->display = 'options'; 3141 } 3142 } 3143 3144 /** 3145 * Get the current objects' list form the database. 3146 * 3147 * @param int $id_lang Language used for display 3148 * @param string|null $order_by ORDER BY clause 3149 * @param string|null $order_way Order way (ASC, DESC) 3150 * @param int $start Offset in LIMIT clause 3151 * @param int|null $limit Row count in LIMIT clause 3152 * @param int|bool $id_lang_shop 3153 * 3154 * @throws PrestaShopDatabaseException 3155 * @throws PrestaShopException 3156 */ 3157 public function getList( 3158 $id_lang, 3159 $order_by = null, 3160 $order_way = null, 3161 $start = 0, 3162 $limit = null, 3163 $id_lang_shop = false 3164 ) { 3165 Hook::exec('action' . $this->controller_name . 'ListingFieldsModifier', [ 3166 'select' => &$this->_select, 3167 'join' => &$this->_join, 3168 'where' => &$this->_where, 3169 'group_by' => &$this->_group, 3170 'order_by' => &$this->_orderBy, 3171 'order_way' => &$this->_orderWay, 3172 'fields' => &$this->fields_list, 3173 ]); 3174 3175 if (!isset($this->list_id)) { 3176 $this->list_id = $this->table; 3177 } 3178 3179 if (!Validate::isTableOrIdentifier($this->table)) { 3180 throw new PrestaShopException(sprintf('Table name %s is invalid:', $this->table)); 3181 } 3182 3183 /* Check params validity */ 3184 if (!is_numeric($start) || !Validate::isUnsignedId($id_lang)) { 3185 throw new PrestaShopException('get list params is not valid'); 3186 } 3187 3188 $limit = $this->checkSqlLimit($limit); 3189 3190 /* Determine offset from current page */ 3191 $start = 0; 3192 if ((int) Tools::getValue('submitFilter' . $this->list_id)) { 3193 $start = ((int) Tools::getValue('submitFilter' . $this->list_id) - 1) * $limit; 3194 } elseif ( 3195 empty($start) 3196 && isset($this->context->cookie->{$this->list_id . '_start'}) 3197 && Tools::isSubmit('export' . $this->table) 3198 ) { 3199 $start = $this->context->cookie->{$this->list_id . '_start'}; 3200 } 3201 3202 // Either save or reset the offset in the cookie 3203 if ($start) { 3204 $this->context->cookie->{$this->list_id . '_start'} = $start; 3205 } elseif (isset($this->context->cookie->{$this->list_id . '_start'})) { 3206 unset($this->context->cookie->{$this->list_id . '_start'}); 3207 } 3208 3209 /* Cache */ 3210 $this->_lang = (int) $id_lang; 3211 3212 // Add SQL shop restriction 3213 $select_shop = ''; 3214 if ($this->shopLinkType) { 3215 $select_shop = ', shop.name as shop_name '; 3216 } 3217 3218 if ($this->multishop_context && Shop::isTableAssociated($this->table) && !empty($this->className)) { 3219 if (Shop::getContext() != Shop::CONTEXT_ALL || !$this->context->employee->isSuperAdmin()) { 3220 $test_join = !preg_match('#`?' . preg_quote(_DB_PREFIX_ . $this->table . '_shop') . '`? *sa#', $this->_join); 3221 if (Shop::isFeatureActive() && $test_join && Shop::isTableAssociated($this->table)) { 3222 $this->_where .= ' AND EXISTS ( 3223 SELECT 1 3224 FROM `' . _DB_PREFIX_ . $this->table . '_shop` sa 3225 WHERE a.`' . bqSQL($this->identifier) . '` = sa.`' . bqSQL($this->identifier) . '` 3226 AND sa.id_shop IN (' . implode(', ', Shop::getContextListShopID()) . ') 3227 )'; 3228 } 3229 } 3230 } 3231 3232 $fromClause = $this->getFromClause(); 3233 $joinClause = $this->getJoinClause($id_lang, $id_lang_shop); 3234 $whereClause = $this->getWhereClause(); 3235 $orderByClause = $this->getOrderByClause($order_by, $order_way); 3236 3237 $shouldLimitSqlResults = $this->shouldLimitSqlResults($limit); 3238 3239 do { 3240 $this->_listsql = ''; 3241 3242 if ($this->explicitSelect) { 3243 foreach ($this->fields_list as $key => $array_value) { 3244 // Add it only if it is not already in $this->_select 3245 if (isset($this->_select) && preg_match('/[\s]`?' . preg_quote($key, '/') . '`?\s*,/', $this->_select)) { 3246 continue; 3247 } 3248 3249 if (isset($array_value['filter_key'])) { 3250 $this->_listsql .= str_replace('!', '.`', $array_value['filter_key']) . '` AS `' . $key . '`, '; 3251 } elseif ($key == 'id_' . $this->table) { 3252 $this->_listsql .= 'a.`' . bqSQL($key) . '`, '; 3253 } elseif ($key != 'image' && !preg_match('/' . preg_quote($key, '/') . '/i', $this->_select)) { 3254 $this->_listsql .= '`' . bqSQL($key) . '`, '; 3255 } 3256 } 3257 $this->_listsql = rtrim(trim($this->_listsql), ','); 3258 } else { 3259 $this->_listsql .= ($this->lang ? 'b.*,' : '') . ' a.*'; 3260 } 3261 3262 $this->_listsql .= "\n" . (isset($this->_select) ? ', ' . rtrim($this->_select, ', ') : '') . $select_shop; 3263 3264 $limitClause = ' ' . (($shouldLimitSqlResults) ? ' LIMIT ' . (int) $start . ', ' . (int) $limit : ''); 3265 3266 if ($this->_use_found_rows || isset($this->_filterHaving) || isset($this->_having)) { 3267 $this->_listsql = 'SELECT SQL_CALC_FOUND_ROWS ' . ($this->_tmpTableFilter ? ' * FROM (SELECT ' : '') . 3268 $this->_listsql . 3269 $fromClause . 3270 $joinClause . 3271 $whereClause . 3272 $orderByClause . 3273 $limitClause; 3274 3275 $list_count = 'SELECT FOUND_ROWS() AS `' . _DB_PREFIX_ . $this->table . '`'; 3276 } else { 3277 $this->_listsql = 'SELECT ' . ($this->_tmpTableFilter ? ' * FROM (SELECT ' : '') . 3278 $this->_listsql . 3279 $fromClause . 3280 $joinClause . 3281 $whereClause . 3282 $orderByClause . 3283 $limitClause; 3284 3285 $list_count = 'SELECT COUNT(*) AS `' . _DB_PREFIX_ . $this->table . '` ' . 3286 $fromClause . 3287 $joinClause . 3288 $whereClause; 3289 } 3290 3291 $this->_list = Db::getInstance()->executeS($this->_listsql, true, false); 3292 3293 if ($this->_list === false) { 3294 $this->_list_error = Db::getInstance()->getMsgError(); 3295 3296 break; 3297 } 3298 3299 $this->_listTotal = Db::getInstance()->getValue($list_count, false); 3300 3301 if ($shouldLimitSqlResults) { 3302 $start = (int) $start - (int) $limit; 3303 if ($start < 0) { 3304 break; 3305 } 3306 } else { 3307 break; 3308 } 3309 } while (empty($this->_list)); 3310 3311 Hook::exec('action' . $this->controller_name . 'ListingResultsModifier', [ 3312 'list' => &$this->_list, 3313 'list_total' => &$this->_listTotal, 3314 ]); 3315 } 3316 3317 /** 3318 * @return string 3319 */ 3320 protected function getFromClause() 3321 { 3322 $sql_table = $this->table == 'order' ? 'orders' : $this->table; 3323 3324 return "\n" . 'FROM `' . _DB_PREFIX_ . $sql_table . '` a '; 3325 } 3326 3327 /** 3328 * @param $id_lang 3329 * @param $id_lang_shop 3330 * 3331 * @return string 3332 */ 3333 protected function getJoinClause($id_lang, $id_lang_shop) 3334 { 3335 $shopJoinClause = ''; 3336 if ($this->shopLinkType) { 3337 $shopJoinClause = ' LEFT JOIN `' . _DB_PREFIX_ . bqSQL($this->shopLinkType) . '` shop 3338 ON a.`id_' . bqSQL($this->shopLinkType) . '` = shop.`id_' . bqSQL($this->shopLinkType) . '`'; 3339 } 3340 3341 return "\n" . $this->getLanguageJoinClause($id_lang, $id_lang_shop) . 3342 "\n" . (isset($this->_join) ? $this->_join . ' ' : '') . 3343 "\n" . $shopJoinClause; 3344 } 3345 3346 /** 3347 * @param $idLang 3348 * @param $idLangShop 3349 * 3350 * @return string 3351 */ 3352 protected function getLanguageJoinClause($idLang, $idLangShop) 3353 { 3354 $languageJoinClause = ''; 3355 if ($this->lang) { 3356 $languageJoinClause = 'LEFT JOIN `' . _DB_PREFIX_ . bqSQL($this->table) . '_lang` b 3357 ON (b.`' . bqSQL($this->identifier) . '` = a.`' . bqSQL($this->identifier) . '` AND b.`id_lang` = ' . (int) $idLang; 3358 3359 if ($idLangShop) { 3360 if (!Shop::isFeatureActive()) { 3361 $languageJoinClause .= ' AND b.`id_shop` = ' . (int) Configuration::get('PS_SHOP_DEFAULT'); 3362 } elseif (Shop::getContext() == Shop::CONTEXT_SHOP) { 3363 $languageJoinClause .= ' AND b.`id_shop` = ' . (int) $idLangShop; 3364 } else { 3365 $languageJoinClause .= ' AND b.`id_shop` = a.id_shop_default'; 3366 } 3367 } 3368 $languageJoinClause .= ')'; 3369 } 3370 3371 return $languageJoinClause; 3372 } 3373 3374 /** 3375 * @return string 3376 */ 3377 protected function getWhereClause() 3378 { 3379 $whereShop = ''; 3380 if ($this->shopLinkType) { 3381 $whereShop = Shop::addSqlRestriction($this->shopShareDatas, 'a', $this->shopLinkType); 3382 } 3383 $whereClause = ' WHERE 1 ' . (isset($this->_where) ? $this->_where . ' ' : '') . 3384 ($this->deleted ? 'AND a.`deleted` = 0 ' : '') . 3385 (isset($this->_filter) ? $this->_filter : '') . $whereShop . "\n" . 3386 (isset($this->_group) ? $this->_group . ' ' : '') . "\n" . 3387 $this->getHavingClause(); 3388 3389 return $whereClause; 3390 } 3391 3392 /** 3393 * @param string|null $orderBy 3394 * @param string|null $orderDirection 3395 * 3396 * @return string 3397 */ 3398 protected function getOrderByClause($orderBy, $orderDirection) 3399 { 3400 $this->_orderBy = $this->checkOrderBy($orderBy); 3401 $this->_orderWay = $this->checkOrderDirection($orderDirection); 3402 3403 return ' ORDER BY ' . ((str_replace('`', '', $this->_orderBy) == $this->identifier) ? 'a.' : '') . 3404 $this->_orderBy . ' ' . $this->_orderWay . 3405 ($this->_tmpTableFilter ? ') tmpTable WHERE 1' . $this->_tmpTableFilter : ''); 3406 } 3407 3408 /** 3409 * @param string|null $orderBy 3410 * 3411 * @return false|string 3412 */ 3413 protected function checkOrderBy($orderBy) 3414 { 3415 if (empty($orderBy)) { 3416 $prefix = $this->getCookieFilterPrefix(); 3417 3418 if ($this->context->cookie->{$prefix . $this->list_id . 'Orderby'}) { 3419 $orderBy = $this->context->cookie->{$prefix . $this->list_id . 'Orderby'}; 3420 } elseif ($this->_orderBy) { 3421 $orderBy = $this->_orderBy; 3422 } else { 3423 $orderBy = $this->_defaultOrderBy; 3424 } 3425 } 3426 3427 /* Check params validity */ 3428 if (!Validate::isOrderBy($orderBy)) { 3429 throw new PrestaShopException('Invalid "order by" clause.'); 3430 } 3431 3432 if (!isset($this->fields_list[$orderBy]['order_key']) && isset($this->fields_list[$orderBy]['filter_key'])) { 3433 $this->fields_list[$orderBy]['order_key'] = $this->fields_list[$orderBy]['filter_key']; 3434 } 3435 3436 if (isset($this->fields_list[$orderBy]['order_key'])) { 3437 $orderBy = $this->fields_list[$orderBy]['order_key']; 3438 } 3439 3440 if (preg_match('/[.!]/', $orderBy)) { 3441 $orderBySplit = preg_split('/[.!]/', $orderBy); 3442 $orderBy = bqSQL($orderBySplit[0]) . '.`' . bqSQL($orderBySplit[1]) . '`'; 3443 } elseif ($orderBy) { 3444 $orderBy = bqSQL($orderBy); 3445 } 3446 3447 return $orderBy; 3448 } 3449 3450 /** 3451 * @param string|null $orderDirection 3452 * 3453 * @return string 3454 */ 3455 protected function checkOrderDirection($orderDirection) 3456 { 3457 $prefix = $this->getCookieOrderByPrefix(); 3458 if (empty($orderDirection)) { 3459 if ($this->context->cookie->{$prefix . $this->list_id . 'Orderway'}) { 3460 $orderDirection = $this->context->cookie->{$prefix . $this->list_id . 'Orderway'}; 3461 } elseif ($this->_orderWay) { 3462 $orderDirection = $this->_orderWay; 3463 } else { 3464 $orderDirection = $this->_defaultOrderWay; 3465 } 3466 } 3467 3468 if (!Validate::isOrderWay($orderDirection)) { 3469 throw new PrestaShopException('Invalid order direction.'); 3470 } 3471 3472 return pSQL(Tools::strtoupper($orderDirection)); 3473 } 3474 3475 /** 3476 * @return mixed 3477 */ 3478 protected function getCookieOrderByPrefix() 3479 { 3480 return str_replace(['admin', 'controller'], '', Tools::strtolower(get_class($this))); 3481 } 3482 3483 /** 3484 * @return string 3485 */ 3486 protected function getHavingClause() 3487 { 3488 $havingClause = ''; 3489 if (isset($this->_filterHaving) || isset($this->_having)) { 3490 $havingClause = ' HAVING '; 3491 if (isset($this->_filterHaving)) { 3492 $havingClause .= ltrim($this->_filterHaving, ' AND '); 3493 } 3494 if (isset($this->_having)) { 3495 $havingClause .= $this->_having . ' '; 3496 } 3497 } 3498 3499 return $havingClause; 3500 } 3501 3502 /** 3503 * @param $limit 3504 * 3505 * @return bool 3506 */ 3507 protected function shouldLimitSqlResults($limit) 3508 { 3509 return $limit !== false; 3510 } 3511 3512 /** 3513 * @param $limit 3514 * 3515 * @return int 3516 */ 3517 protected function checkSqlLimit($limit) 3518 { 3519 if (empty($limit)) { 3520 if ( 3521 isset($this->context->cookie->{$this->list_id . '_pagination'}) && 3522 $this->context->cookie->{$this->list_id . '_pagination'} 3523 ) { 3524 $limit = $this->context->cookie->{$this->list_id . '_pagination'}; 3525 } else { 3526 $limit = $this->_default_pagination; 3527 } 3528 } 3529 3530 $limit = (int) Tools::getValue($this->list_id . '_pagination', $limit); 3531 if (in_array($limit, $this->_pagination) && $limit != $this->_default_pagination) { 3532 $this->context->cookie->{$this->list_id . '_pagination'} = $limit; 3533 } else { 3534 unset($this->context->cookie->{$this->list_id . '_pagination'}); 3535 } 3536 3537 if (!is_numeric($limit)) { 3538 throw new PrestaShopException('Invalid limit. It should be a numeric.'); 3539 } 3540 3541 return $limit; 3542 } 3543 3544 /** 3545 * @param array|string $filter_modules_list 3546 * @param string|bool $tracking_source 3547 * 3548 * @return bool 3549 * 3550 * @throws PrestaShopException 3551 */ 3552 public function getModulesList($filter_modules_list, $tracking_source = false) 3553 { 3554 if (!is_array($filter_modules_list) && null !== $filter_modules_list) { 3555 $filter_modules_list = [$filter_modules_list]; 3556 } 3557 3558 if (null === $filter_modules_list || !count($filter_modules_list)) { 3559 return false; 3560 } //if there is no modules to display just return false; 3561 3562 $all_modules = Module::getModulesOnDisk(true); 3563 $this->modules_list = []; 3564 foreach ($all_modules as $module) { 3565 $perm = true; 3566 if ($module->id) { 3567 $perm &= Module::getPermissionStatic($module->id, 'configure'); 3568 } else { 3569 $id_admin_module = Tab::getIdFromClassName('AdminModules'); 3570 $access = Profile::getProfileAccess($this->context->employee->id_profile, $id_admin_module); 3571 if (!$access['edit']) { 3572 $perm &= false; 3573 } 3574 } 3575 3576 if (in_array($module->name, $filter_modules_list) && $perm) { 3577 $this->fillModuleData($module, 'array', null, $tracking_source); 3578 $this->modules_list[array_search($module->name, $filter_modules_list)] = $module; 3579 } 3580 } 3581 ksort($this->modules_list); 3582 3583 if (count($this->modules_list)) { 3584 return true; 3585 } 3586 3587 return false; //no module found on disk just return false; 3588 } 3589 3590 /** 3591 * @return array 3592 */ 3593 public function getLanguages() 3594 { 3595 $cookie = $this->context->cookie; 3596 $this->allow_employee_form_lang = (int) Configuration::get('PS_BO_ALLOW_EMPLOYEE_FORM_LANG'); 3597 if ($this->allow_employee_form_lang && !$cookie->employee_form_lang) { 3598 $cookie->employee_form_lang = (int) Configuration::get('PS_LANG_DEFAULT'); 3599 } 3600 3601 $lang_exists = false; 3602 $this->_languages = Language::getLanguages(false); 3603 foreach ($this->_languages as $lang) { 3604 if (isset($cookie->employee_form_lang) && $cookie->employee_form_lang == $lang['id_lang']) { 3605 $lang_exists = true; 3606 } 3607 } 3608 3609 $this->default_form_language = $lang_exists ? (int) $cookie->employee_form_lang : (int) Configuration::get('PS_LANG_DEFAULT'); 3610 3611 foreach ($this->_languages as $k => $language) { 3612 $this->_languages[$k]['is_default'] = (int) ($language['id_lang'] == $this->default_form_language); 3613 } 3614 3615 return $this->_languages; 3616 } 3617 3618 /** 3619 * Return the list of fields value. 3620 * 3621 * @param ObjectModel $obj Object 3622 * 3623 * @return array 3624 */ 3625 public function getFieldsValue($obj) 3626 { 3627 foreach ($this->fields_form as $fieldset) { 3628 if (isset($fieldset['form']['input'])) { 3629 foreach ($fieldset['form']['input'] as $input) { 3630 if (!isset($this->fields_value[$input['name']])) { 3631 if (isset($input['type']) && $input['type'] == 'shop') { 3632 if ($obj->id) { 3633 $result = Shop::getShopById((int) $obj->id, $this->identifier, $this->table); 3634 foreach ($result as $row) { 3635 $this->fields_value['shop'][$row['id_' . $input['type']]][] = $row['id_shop']; 3636 } 3637 } 3638 } elseif (isset($input['lang']) && $input['lang']) { 3639 foreach ($this->_languages as $language) { 3640 $field_value = $this->getFieldValue($obj, $input['name'], $language['id_lang']); 3641 if (empty($field_value)) { 3642 if (isset($input['default_value']) && is_array($input['default_value']) && isset($input['default_value'][$language['id_lang']])) { 3643 $field_value = $input['default_value'][$language['id_lang']]; 3644 } elseif (isset($input['default_value'])) { 3645 $field_value = $input['default_value']; 3646 } 3647 } 3648 $this->fields_value[$input['name']][$language['id_lang']] = $field_value; 3649 } 3650 } else { 3651 $field_value = $this->getFieldValue($obj, $input['name']); 3652 if ($field_value === false && isset($input['default_value'])) { 3653 $field_value = $input['default_value']; 3654 } 3655 $this->fields_value[$input['name']] = $field_value; 3656 } 3657 } 3658 } 3659 } 3660 } 3661 3662 return $this->fields_value; 3663 } 3664 3665 /** 3666 * Return field value if possible (both classical and multilingual fields). 3667 * 3668 * Case 1 : Return value if present in $_POST / $_GET 3669 * Case 2 : Return object value 3670 * 3671 * @param ObjectModel $obj Object 3672 * @param string $key Field name 3673 * @param int|null $id_lang Language id (optional) 3674 * 3675 * @return string 3676 */ 3677 public function getFieldValue($obj, $key, $id_lang = null) 3678 { 3679 if ($id_lang) { 3680 $default_value = (isset($obj->id) && $obj->id && isset($obj->{$key}[$id_lang])) ? $obj->{$key}[$id_lang] : false; 3681 } else { 3682 $default_value = isset($obj->{$key}) ? $obj->{$key} : false; 3683 } 3684 3685 return Tools::getValue($key . ($id_lang ? '_' . $id_lang : ''), $default_value); 3686 } 3687 3688 /** 3689 * Manage page display (form, list...). 3690 * 3691 * @param string|bool $class_name Allow to validate a different class than the current one 3692 * 3693 * @throws PrestaShopException 3694 */ 3695 public function validateRules($class_name = false) 3696 { 3697 if (!$class_name) { 3698 $class_name = $this->className; 3699 } 3700 3701 /** @var $object ObjectModel */ 3702 $object = new $class_name(); 3703 3704 if (method_exists($this, 'getValidationRules')) { 3705 $definition = $this->getValidationRules(); 3706 } else { 3707 $definition = ObjectModel::getDefinition($class_name); 3708 } 3709 3710 $default_language = new Language((int) Configuration::get('PS_LANG_DEFAULT')); 3711 $languages = Language::getLanguages(false); 3712 3713 foreach ($definition['fields'] as $field => $def) { 3714 $skip = []; 3715 if (in_array($field, ['passwd', 'no-picture'])) { 3716 $skip = ['required']; 3717 } 3718 3719 if (isset($def['lang']) && $def['lang']) { 3720 if (isset($def['required']) && $def['required']) { 3721 $value = Tools::getValue($field . '_' . $default_language->id); 3722 // !isset => not exist || "" == $value can be === 0 (before, empty $value === 0 returned true) 3723 if (!isset($value) || '' == $value) { 3724 $this->errors[$field . '_' . $default_language->id] = $this->trans( 3725 'The field %field_name% is required at least in %lang%.', 3726 ['%field_name%' => $object->displayFieldName($field, $class_name), '%lang%' => $default_language->name], 3727 'Admin.Notifications.Error' 3728 ); 3729 } 3730 } 3731 3732 foreach ($languages as $language) { 3733 $value = Tools::getValue($field . '_' . $language['id_lang']); 3734 if (!empty($value)) { 3735 if (($error = $object->validateField($field, $value, $language['id_lang'], $skip, true)) !== true) { 3736 $this->errors[$field . '_' . $language['id_lang']] = $error; 3737 } 3738 } 3739 } 3740 } elseif (($error = $object->validateField($field, Tools::getValue($field), null, $skip, true)) !== true) { 3741 $this->errors[$field] = $error; 3742 } 3743 } 3744 3745 /* Overload this method for custom checking */ 3746 $this->_childValidation(); 3747 3748 /* Checking for multilingual fields validity */ 3749 if (isset($rules['validateLang']) && is_array($rules['validateLang'])) { 3750 foreach ($rules['validateLang'] as $field_lang => $function) { 3751 foreach ($languages as $language) { 3752 if (($value = Tools::getValue($field_lang . '_' . $language['id_lang'])) !== false && !empty($value)) { 3753 if (Tools::strtolower($function) == 'iscleanhtml' && Configuration::get('PS_ALLOW_HTML_IFRAME')) { 3754 $res = Validate::$function($value, true); 3755 } else { 3756 $res = Validate::$function($value); 3757 } 3758 if (!$res) { 3759 $this->errors[$field_lang . '_' . $language['id_lang']] = $this->trans( 3760 'The %field_name% field (%lang%) is invalid.', 3761 ['%field_name%' => call_user_func([$class_name, 'displayFieldName'], $field_lang, $class_name), '%lang%' => $language['name']], 3762 'Admin.Notifications.Error' 3763 ); 3764 } 3765 } 3766 } 3767 } 3768 } 3769 } 3770 3771 /** 3772 * Overload this method for custom checking. 3773 */ 3774 protected function _childValidation() 3775 { 3776 } 3777 3778 /** 3779 * Display object details. 3780 */ 3781 public function viewDetails() 3782 { 3783 } 3784 3785 /** 3786 * Called before deletion. 3787 * 3788 * @param ObjectModel $object Object 3789 * 3790 * @return bool 3791 */ 3792 protected function beforeDelete($object) 3793 { 3794 return false; 3795 } 3796 3797 /** 3798 * Called before deletion. 3799 * 3800 * @param ObjectModel $object Object 3801 * @param int $old_id 3802 * 3803 * @return bool 3804 */ 3805 protected function afterDelete($object, $old_id) 3806 { 3807 return true; 3808 } 3809 3810 /** 3811 * @param ObjectModel $object 3812 * 3813 * @return bool 3814 */ 3815 protected function afterAdd($object) 3816 { 3817 return true; 3818 } 3819 3820 /** 3821 * @param ObjectModel $object 3822 * 3823 * @return bool 3824 */ 3825 protected function afterUpdate($object) 3826 { 3827 return true; 3828 } 3829 3830 /** 3831 * Check rights to view the current tab. 3832 * 3833 * @return bool 3834 */ 3835 protected function afterImageUpload() 3836 { 3837 return true; 3838 } 3839 3840 /** 3841 * Copy data values from $_POST to object. 3842 * 3843 * @param ObjectModel &$object Object 3844 * @param string $table Object table 3845 */ 3846 protected function copyFromPost(&$object, $table) 3847 { 3848 /* Classical fields */ 3849 foreach ($_POST as $key => $value) { 3850 if (array_key_exists($key, get_object_vars($object)) && $key != 'id_' . $table) { 3851 /* Do not take care of password field if empty */ 3852 if ($key == 'passwd' && Tools::getValue('id_' . $table) && empty($value)) { 3853 continue; 3854 } 3855 /* Automatically hash password in MD5 */ 3856 if ($key == 'passwd' && !empty($value)) { 3857 $value = $this->get('hashing')->hash($value, _COOKIE_KEY_); 3858 } 3859 $object->{$key} = $value; 3860 } 3861 } 3862 3863 /* Multilingual fields */ 3864 $class_vars = get_class_vars(get_class($object)); 3865 $fields = []; 3866 if (isset($class_vars['definition']['fields'])) { 3867 $fields = $class_vars['definition']['fields']; 3868 } 3869 3870 foreach ($fields as $field => $params) { 3871 if (array_key_exists('lang', $params) && $params['lang']) { 3872 foreach (Language::getIDs(false) as $id_lang) { 3873 if (Tools::isSubmit($field . '_' . (int) $id_lang)) { 3874 $object->{$field}[(int) $id_lang] = Tools::getValue($field . '_' . (int) $id_lang); 3875 } 3876 } 3877 } 3878 } 3879 } 3880 3881 /** 3882 * Returns an array with selected shops and type (group or boutique shop). 3883 * 3884 * @param string $table 3885 * 3886 * @return array 3887 */ 3888 protected function getSelectedAssoShop($table) 3889 { 3890 if (!Shop::isFeatureActive() || !Shop::isTableAssociated($table)) { 3891 return []; 3892 } 3893 3894 $shops = Shop::getShops(true, null, true); 3895 if (count($shops) == 1 && isset($shops[0])) { 3896 return [$shops[0], 'shop']; 3897 } 3898 3899 $assos = []; 3900 if (Tools::isSubmit('checkBoxShopAsso_' . $table)) { 3901 foreach (Tools::getValue('checkBoxShopAsso_' . $table) as $id_shop => $value) { 3902 $assos[] = (int) $id_shop; 3903 } 3904 } elseif (Shop::getTotalShops(false) == 1) { 3905 // if we do not have the checkBox multishop, we can have an admin with only one shop and being in multishop 3906 $assos[] = (int) Shop::getContextShopID(); 3907 } 3908 3909 return $assos; 3910 } 3911 3912 /** 3913 * Update the associations of shops. 3914 * 3915 * @param int $id_object 3916 * 3917 * @return bool|void 3918 * 3919 * @throws PrestaShopDatabaseException 3920 */ 3921 protected function updateAssoShop($id_object) 3922 { 3923 if (!Shop::isFeatureActive()) { 3924 return; 3925 } 3926 3927 if (!Shop::isTableAssociated($this->table)) { 3928 return; 3929 } 3930 3931 $assos_data = $this->getSelectedAssoShop($this->table); 3932 3933 // Get list of shop id we want to exclude from asso deletion 3934 $exclude_ids = $assos_data; 3935 foreach (Db::getInstance()->executeS('SELECT id_shop FROM ' . _DB_PREFIX_ . 'shop') as $row) { 3936 if (!$this->context->employee->hasAuthOnShop($row['id_shop'])) { 3937 $exclude_ids[] = $row['id_shop']; 3938 } 3939 } 3940 Db::getInstance()->delete($this->table . '_shop', '`' . bqSQL($this->identifier) . '` = ' . (int) $id_object . ($exclude_ids ? ' AND id_shop NOT IN (' . implode(', ', array_map('intval', $exclude_ids)) . ')' : '')); 3941 3942 $insert = []; 3943 foreach ($assos_data as $id_shop) { 3944 $insert[] = [ 3945 $this->identifier => (int) $id_object, 3946 'id_shop' => (int) $id_shop, 3947 ]; 3948 } 3949 3950 return Db::getInstance()->insert($this->table . '_shop', $insert, false, true, Db::INSERT_IGNORE); 3951 } 3952 3953 /** 3954 * @param mixed $value 3955 * @param array $field 3956 * 3957 * @return bool 3958 */ 3959 protected function validateField($value, $field) 3960 { 3961 if (isset($field['validation'])) { 3962 $valid_method_exists = method_exists('Validate', $field['validation']); 3963 if ((!isset($field['empty']) || !$field['empty'] || (isset($field['empty']) && $field['empty'] && $value)) && $valid_method_exists) { 3964 $field_validation = $field['validation']; 3965 if (!Validate::$field_validation($value)) { 3966 $this->errors[] = $this->trans('%s: Incorrect value', [$field['title']], 'Admin.Notifications.Error'); 3967 3968 return false; 3969 } 3970 } 3971 } 3972 3973 return true; 3974 } 3975 3976 /** 3977 * Can be overridden. 3978 */ 3979 public function beforeUpdateOptions() 3980 { 3981 } 3982 3983 /** 3984 * Overload this method for custom checking. 3985 * 3986 * @param int $id Object id used for deleting images 3987 * 3988 * @return bool 3989 */ 3990 protected function postImage($id) 3991 { 3992 if (isset($this->fieldImageSettings['name'], $this->fieldImageSettings['dir'])) { 3993 return $this->uploadImage($id, $this->fieldImageSettings['name'], $this->fieldImageSettings['dir'] . '/'); 3994 } elseif (!empty($this->fieldImageSettings)) { 3995 foreach ($this->fieldImageSettings as $image) { 3996 if (isset($image['name'], $image['dir'])) { 3997 $this->uploadImage($id, $image['name'], $image['dir'] . '/'); 3998 } 3999 } 4000 } 4001 4002 return !count($this->errors) ? true : false; 4003 } 4004 4005 /** 4006 * @param int $id 4007 * @param string $name 4008 * @param string $dir 4009 * @param string|bool $ext 4010 * @param int|null $width 4011 * @param int|null $height 4012 * 4013 * @return bool 4014 */ 4015 protected function uploadImage($id, $name, $dir, $ext = false, $width = null, $height = null) 4016 { 4017 if (isset($_FILES[$name]['tmp_name']) && !empty($_FILES[$name]['tmp_name'])) { 4018 // Delete old image 4019 if (Validate::isLoadedObject($object = $this->loadObject())) { 4020 $object->deleteImage(); 4021 } else { 4022 return false; 4023 } 4024 4025 // Check image validity 4026 $max_size = isset($this->max_image_size) ? $this->max_image_size : 0; 4027 if ($error = ImageManager::validateUpload($_FILES[$name], Tools::getMaxUploadSize($max_size))) { 4028 $this->errors[] = $error; 4029 } 4030 4031 $tmp_name = tempnam(_PS_TMP_IMG_DIR_, 'PS'); 4032 if (!$tmp_name) { 4033 return false; 4034 } 4035 4036 if (!move_uploaded_file($_FILES[$name]['tmp_name'], $tmp_name)) { 4037 return false; 4038 } 4039 4040 // Evaluate the memory required to resize the image: if it's too much, you can't resize it. 4041 if (!ImageManager::checkImageMemoryLimit($tmp_name)) { 4042 $this->errors[] = $this->trans('Due to memory limit restrictions, this image cannot be loaded. Please increase your memory_limit value via your server\'s configuration settings.', [], 'Admin.Notifications.Error'); 4043 } 4044 4045 // Copy new image 4046 if (empty($this->errors) && !ImageManager::resize($tmp_name, _PS_IMG_DIR_ . $dir . $id . '.' . $this->imageType, (int) $width, (int) $height, ($ext ? $ext : $this->imageType))) { 4047 $this->errors[] = $this->trans('An error occurred while uploading the image.', [], 'Admin.Notifications.Error'); 4048 } 4049 4050 if (count($this->errors)) { 4051 return false; 4052 } 4053 if ($this->afterImageUpload()) { 4054 unlink($tmp_name); 4055 4056 return true; 4057 } 4058 4059 return false; 4060 } 4061 4062 return true; 4063 } 4064 4065 /** 4066 * Delete multiple items. 4067 * 4068 * @return bool true if success 4069 */ 4070 protected function processBulkDelete() 4071 { 4072 if (is_array($this->boxes) && !empty($this->boxes)) { 4073 $object = new $this->className(); 4074 4075 if (isset($object->noZeroObject)) { 4076 $objects_count = count(call_user_func([$this->className, $object->noZeroObject])); 4077 4078 // Check if all object will be deleted 4079 if ($objects_count <= 1 || count($this->boxes) == $objects_count) { 4080 $this->errors[] = $this->trans('You need at least one object.', [], 'Admin.Notifications.Error') . 4081 ' <b>' . $this->table . '</b><br />' . 4082 $this->trans('You cannot delete all of the items.', [], 'Admin.Notifications.Error'); 4083 } 4084 } else { 4085 $result = true; 4086 foreach ($this->boxes as $id) { 4087 /** @var $to_delete ObjectModel */ 4088 $to_delete = new $this->className((int) $id); 4089 $delete_ok = true; 4090 if ($this->deleted) { 4091 $to_delete->deleted = true; 4092 if (!$to_delete->update()) { 4093 $result = false; 4094 $delete_ok = false; 4095 } 4096 } elseif (!$to_delete->delete()) { 4097 $result = false; 4098 $delete_ok = false; 4099 } 4100 4101 if ($delete_ok) { 4102 PrestaShopLogger::addLog( 4103 $this->trans('%s deletion', [$this->className]), 4104 1, 4105 null, 4106 $this->className, 4107 (int) $to_delete->id, 4108 true, 4109 (int) $this->context->employee->id 4110 ); 4111 } else { 4112 $this->errors[] = $this->trans('Can\'t delete #%id%', ['%id%' => (int) $id], 'Admin.Notifications.Error'); 4113 } 4114 } 4115 if ($result) { 4116 $this->redirect_after = self::$currentIndex . '&conf=2&token=' . $this->token; 4117 } 4118 $this->errors[] = $this->trans('An error occurred while deleting this selection.', [], 'Admin.Notifications.Error'); 4119 } 4120 } else { 4121 $this->errors[] = $this->trans('You must select at least one element to delete.', [], 'Admin.Notifications.Error'); 4122 } 4123 4124 if (isset($result)) { 4125 return $result; 4126 } else { 4127 return false; 4128 } 4129 } 4130 4131 protected function ajaxProcessOpenHelp() 4132 { 4133 $help_class_name = $_GET['controller']; 4134 $popup_content = "<!doctype html> 4135 <html> 4136 <head> 4137 <meta charset='UTF-8'> 4138 <title>PrestaShop Help</title> 4139 <link href='//help.prestashop.com/css/help.css' rel='stylesheet'> 4140 <link href='//fonts.googleapis.com/css?family=Open+Sans:400,700' rel='stylesheet'> 4141 <script src='" . _PS_JS_DIR_ . "jquery/jquery-1.11.0.min.js'></script> 4142 <script src='" . _PS_JS_DIR_ . "admin.js'></script> 4143 <script src='" . _PS_JS_DIR_ . "tools.js'></script> 4144 <script> 4145 help_class_name='" . addslashes($help_class_name) . "'; 4146 iso_user = '" . addslashes($this->context->language->iso_code) . "' 4147 </script> 4148 <script src='themes/default/js/help.js'></script> 4149 <script> 4150 $(function(){ 4151 initHelp(); 4152 }); 4153 </script> 4154 </head> 4155 <body><div id='help-container' class='help-popup'></div></body> 4156 </html>"; 4157 die($popup_content); 4158 } 4159 4160 /** 4161 * Enable multiple items. 4162 * 4163 * @return bool true if success 4164 */ 4165 protected function processBulkEnableSelection() 4166 { 4167 return $this->processBulkStatusSelection(1); 4168 } 4169 4170 /** 4171 * Disable multiple items. 4172 * 4173 * @return bool true if success 4174 */ 4175 protected function processBulkDisableSelection() 4176 { 4177 return $this->processBulkStatusSelection(0); 4178 } 4179 4180 /** 4181 * Toggle status of multiple items. 4182 * 4183 * @param bool $status 4184 * 4185 * @return bool true if success 4186 * 4187 * @throws PrestaShopException 4188 */ 4189 protected function processBulkStatusSelection($status) 4190 { 4191 $result = true; 4192 if (is_array($this->boxes) && !empty($this->boxes)) { 4193 foreach ($this->boxes as $id) { 4194 /** @var ObjectModel $object */ 4195 $object = new $this->className((int) $id); 4196 $object->active = (int) $status; 4197 $isUpdated = (bool) $object->update(); 4198 $result &= $isUpdated; 4199 4200 if (!$isUpdated) { 4201 $this->errors[] = $this->trans('Can\'t update #%id% status', ['%id%' => (int) $id], 'Admin.Notifications.Error'); 4202 } 4203 } 4204 4205 if ($result) { 4206 $this->redirect_after = self::$currentIndex . '&conf=5&token=' . $this->token; 4207 } else { 4208 $this->errors[] = $this->trans('An error occurred while updating the status.', [], 'Admin.Notifications.Error'); 4209 } 4210 } else { 4211 $this->errors[] = $this->trans('You must select at least one item to perform a bulk action.', [], 'Admin.Notifications.Error'); 4212 } 4213 4214 return $result; 4215 } 4216 4217 /** 4218 * @return bool 4219 */ 4220 protected function processBulkAffectZone() 4221 { 4222 $result = false; 4223 if (is_array($this->boxes) && !empty($this->boxes)) { 4224 /** @var Country|State $object */ 4225 $object = new $this->className(); 4226 $result = $object->affectZoneToSelection(Tools::getValue($this->table . 'Box'), Tools::getValue('zone_to_affect')); 4227 4228 if ($result) { 4229 $this->redirect_after = self::$currentIndex . '&conf=28&token=' . $this->token; 4230 } 4231 $this->errors[] = $this->trans('An error occurred while assigning a zone to the selection.', [], 'Admin.Notifications.Error'); 4232 } else { 4233 $this->errors[] = $this->trans('You must select at least one element to assign a new zone.', [], 'Admin.Notifications.Error'); 4234 } 4235 4236 return $result; 4237 } 4238 4239 /** 4240 * Called before Add. 4241 * 4242 * @param ObjectModel $object Object 4243 * 4244 * @return bool 4245 */ 4246 protected function beforeAdd($object) 4247 { 4248 return true; 4249 } 4250 4251 /** 4252 * Prepare the view to display the required fields form. 4253 * 4254 * @return string|void 4255 */ 4256 public function displayRequiredFields() 4257 { 4258 if (!$this->access('add') || !$this->access('delete') || !$this->required_database) { 4259 return; 4260 } 4261 4262 $helper = new Helper(); 4263 $helper->currentIndex = self::$currentIndex; 4264 $helper->token = $this->token; 4265 $helper->override_folder = $this->override_folder; 4266 4267 return $helper->renderRequiredFields($this->className, $this->identifier, $this->required_fields); 4268 } 4269 4270 /** 4271 * Create a template from the override file, else from the base file. 4272 * 4273 * @param string $tpl_name filename 4274 * 4275 * @return Smarty_Internal_Template 4276 */ 4277 public function createTemplate($tpl_name) 4278 { 4279 // Use override tpl if it exists 4280 // If view access is denied, we want to use the default template that will be used to display an error 4281 if ($this->viewAccess() && $this->override_folder) { 4282 if (!Configuration::get('PS_DISABLE_OVERRIDES') && file_exists($this->context->smarty->getTemplateDir(1) . DIRECTORY_SEPARATOR . $this->override_folder . $tpl_name)) { 4283 return $this->context->smarty->createTemplate($this->override_folder . $tpl_name, $this->context->smarty); 4284 } elseif (file_exists($this->context->smarty->getTemplateDir(0) . 'controllers' . DIRECTORY_SEPARATOR . $this->override_folder . $tpl_name)) { 4285 return $this->context->smarty->createTemplate('controllers' . DIRECTORY_SEPARATOR . $this->override_folder . $tpl_name, $this->context->smarty); 4286 } 4287 } 4288 4289 return $this->context->smarty->createTemplate($this->context->smarty->getTemplateDir(0) . $tpl_name, $this->context->smarty); 4290 } 4291 4292 /** 4293 * Shortcut to set up a json success payload. 4294 * 4295 * @param string $message Success message 4296 */ 4297 public function jsonConfirmation($message) 4298 { 4299 $this->json = true; 4300 $this->confirmations[] = $message; 4301 if ($this->status === '') { 4302 $this->status = 'ok'; 4303 } 4304 } 4305 4306 /** 4307 * Shortcut to set up a json error payload. 4308 * 4309 * @param string $message Error message 4310 */ 4311 public function jsonError($message) 4312 { 4313 $this->json = true; 4314 $this->errors[] = $message; 4315 if ($this->status === '') { 4316 $this->status = 'error'; 4317 } 4318 } 4319 4320 /** 4321 * @deprecated Since 1.7.7 Use Tools::isFileFresh instead 4322 * 4323 * @param string $file 4324 * @param int $timeout 4325 * 4326 * @return bool 4327 */ 4328 public function isFresh($file, $timeout = 604800) 4329 { 4330 return Tools::isFileFresh($file, $timeout); 4331 } 4332 4333 /** 4334 * @deprecated Since 1.7.7 Use Tools::refreshFile instead 4335 * 4336 * @param string $file_to_refresh 4337 * @param string $external_file 4338 * 4339 * @return bool 4340 */ 4341 public function refresh($file_to_refresh, $external_file) 4342 { 4343 return Tools::refreshFile($file_to_refresh, $external_file); 4344 } 4345 4346 /** 4347 * @param Module $module 4348 * @param string $output_type 4349 * @param string|null $back 4350 * @param string|bool $install_source_tracking 4351 */ 4352 public function fillModuleData(&$module, $output_type = 'link', $back = null, $install_source_tracking = false) 4353 { 4354 /** @var Module $obj */ 4355 $obj = null; 4356 if ($module->onclick_option) { 4357 $obj = new $module->name(); 4358 } 4359 // Fill module data 4360 $module->logo = '../../img/questionmark.png'; 4361 4362 if (@filemtime(_PS_ROOT_DIR_ . DIRECTORY_SEPARATOR . basename(_PS_MODULE_DIR_) . DIRECTORY_SEPARATOR . $module->name 4363 . DIRECTORY_SEPARATOR . 'logo.gif')) { 4364 $module->logo = 'logo.gif'; 4365 } 4366 if (@filemtime(_PS_ROOT_DIR_ . DIRECTORY_SEPARATOR . basename(_PS_MODULE_DIR_) . DIRECTORY_SEPARATOR . $module->name 4367 . DIRECTORY_SEPARATOR . 'logo.png')) { 4368 $module->logo = 'logo.png'; 4369 } 4370 4371 $link_admin_modules = $this->context->link->getAdminLink('AdminModules', true); 4372 4373 $module->options['install_url'] = $link_admin_modules . '&install=' . urlencode($module->name) . '&tab_module=' . $module->tab . '&module_name=' . $module->name 4374 . '&anchor=' . ucfirst($module->name) . ($install_source_tracking ? '&source=' . $install_source_tracking : ''); 4375 $module->options['update_url'] = $link_admin_modules . '&update=' . urlencode($module->name) . '&tab_module=' . $module->tab . '&module_name=' . $module->name . '&anchor=' . ucfirst($module->name); 4376 $module->options['uninstall_url'] = $link_admin_modules . '&uninstall=' . urlencode($module->name) . '&tab_module=' . $module->tab . '&module_name=' . $module->name . '&anchor=' . ucfirst($module->name); 4377 4378 // free modules get their source tracking data here 4379 $module->optionsHtml = $this->displayModuleOptions($module, $output_type, $back, $install_source_tracking); 4380 // pay modules get their source tracking data here 4381 if ($install_source_tracking && isset($module->addons_buy_url)) { 4382 $module->addons_buy_url .= ($install_source_tracking ? '&utm_term=' . $install_source_tracking : ''); 4383 } 4384 4385 $module->options['uninstall_onclick'] = ((!$module->onclick_option) ? 4386 ((empty($module->confirmUninstall)) ? 'return confirm(\'' . $this->trans('Do you really want to uninstall this module?') . '\');' : 'return confirm(\'' . addslashes($module->confirmUninstall) . '\');') : 4387 $obj->onclickOption('uninstall', $module->options['uninstall_url'])); 4388 4389 if ((Tools::getValue('module_name') == $module->name || in_array($module->name, explode('|', Tools::getValue('modules_list')))) && (int) Tools::getValue('conf') > 0) { 4390 $module->message = $this->_conf[(int) Tools::getValue('conf')]; 4391 } 4392 4393 if ((Tools::getValue('module_name') == $module->name || in_array($module->name, explode('|', Tools::getValue('modules_list')))) && (int) Tools::getValue('conf') > 0) { 4394 unset($obj); 4395 } 4396 } 4397 4398 /** 4399 * Display modules list. 4400 * 4401 * @param Module $module 4402 * @param string $output_type (link or select) 4403 * @param string|null $back 4404 * @param string|bool $install_source_tracking 4405 * 4406 * @return string|array 4407 */ 4408 public function displayModuleOptions($module, $output_type = 'link', $back = null, $install_source_tracking = false) 4409 { 4410 if (!isset($module->enable_device)) { 4411 $module->enable_device = Context::DEVICE_COMPUTER | Context::DEVICE_TABLET | Context::DEVICE_MOBILE; 4412 } 4413 4414 $this->translationsTab['confirm_uninstall_popup'] = (isset($module->confirmUninstall) ? $module->confirmUninstall : $this->trans('Do you really want to uninstall this module?')); 4415 if (!isset($this->translationsTab['Disable this module'])) { 4416 $this->translationsTab['Disable this module'] = $this->trans('Disable this module'); 4417 $this->translationsTab['Enable this module for all shops'] = $this->trans('Enable this module for all shops'); 4418 $this->translationsTab['Disable'] = $this->trans('Disable'); 4419 $this->translationsTab['Enable'] = $this->trans('Enable'); 4420 $this->translationsTab['Disable on mobiles'] = $this->trans('Disable on mobiles'); 4421 $this->translationsTab['Disable on tablets'] = $this->trans('Disable on tablets'); 4422 $this->translationsTab['Disable on computers'] = $this->trans('Disable on computers'); 4423 $this->translationsTab['Display on mobiles'] = $this->trans('Display on mobiles'); 4424 $this->translationsTab['Display on tablets'] = $this->trans('Display on tablets'); 4425 $this->translationsTab['Display on computers'] = $this->trans('Display on computers'); 4426 $this->translationsTab['Reset'] = $this->trans('Reset'); 4427 $this->translationsTab['Configure'] = $this->trans('Configure'); 4428 $this->translationsTab['Delete'] = $this->trans('Delete'); 4429 $this->translationsTab['Install'] = $this->trans('Install'); 4430 $this->translationsTab['Uninstall'] = $this->trans('Uninstall'); 4431 $this->translationsTab['Would you like to delete the content related to this module ?'] = $this->trans('Would you like to delete the content related to this module ?'); 4432 $this->translationsTab['This action will permanently remove the module from the server. Are you sure you want to do this?'] = $this->trans('This action will permanently remove the module from the server. Are you sure you want to do this?'); 4433 $this->translationsTab['Remove from Favorites'] = $this->trans('Remove from Favorites'); 4434 $this->translationsTab['Mark as Favorite'] = $this->trans('Mark as Favorite'); 4435 } 4436 4437 $link_admin_modules = $this->context->link->getAdminLink('AdminModules', true); 4438 $modules_options = []; 4439 4440 $configure_module = [ 4441 'href' => $link_admin_modules . '&configure=' . urlencode($module->name) . '&tab_module=' . $module->tab . '&module_name=' . urlencode($module->name), 4442 'onclick' => $module->onclick_option && isset($module->onclick_option_content['configure']) ? $module->onclick_option_content['configure'] : '', 4443 'title' => '', 4444 'text' => $this->translationsTab['Configure'], 4445 'cond' => $module->id && isset($module->is_configurable) && $module->is_configurable, 4446 'icon' => 'wrench', 4447 ]; 4448 4449 $desactive_module = [ 4450 'href' => $link_admin_modules . '&module_name=' . urlencode($module->name) . '&' . ($module->active ? 'enable=0' : 'enable=1') . '&tab_module=' . $module->tab, 4451 'onclick' => $module->active && $module->onclick_option && isset($module->onclick_option_content['desactive']) ? $module->onclick_option_content['desactive'] : '', 4452 'title' => Shop::isFeatureActive() ? htmlspecialchars($module->active ? $this->translationsTab['Disable this module'] : $this->translationsTab['Enable this module for all shops']) : '', 4453 'text' => $module->active ? $this->translationsTab['Disable'] : $this->translationsTab['Enable'], 4454 'cond' => $module->id, 4455 'icon' => 'off', 4456 ]; 4457 $link_reset_module = $link_admin_modules . '&module_name=' . urlencode($module->name) . '&reset&tab_module=' . $module->tab; 4458 4459 $is_reset_ready = false; 4460 if (Validate::isModuleName($module->name)) { 4461 if (method_exists(Module::getInstanceByName($module->name), 'reset')) { 4462 $is_reset_ready = true; 4463 } 4464 } 4465 4466 $reset_module = [ 4467 'href' => $link_reset_module, 4468 'onclick' => $module->onclick_option && isset($module->onclick_option_content['reset']) ? $module->onclick_option_content['reset'] : '', 4469 'title' => '', 4470 'text' => $this->translationsTab['Reset'], 4471 'cond' => $module->id && $module->active, 4472 'icon' => 'undo', 4473 'class' => ($is_reset_ready ? 'reset_ready' : ''), 4474 ]; 4475 4476 $delete_module = [ 4477 'href' => $link_admin_modules . '&delete=' . urlencode($module->name) . '&tab_module=' . $module->tab . '&module_name=' . urlencode($module->name), 4478 'onclick' => $module->onclick_option && isset($module->onclick_option_content['delete']) ? $module->onclick_option_content['delete'] : 'return confirm(\'' . $this->translationsTab['This action will permanently remove the module from the server. Are you sure you want to do this?'] . '\');', 4479 'title' => '', 4480 'text' => $this->translationsTab['Delete'], 4481 'cond' => true, 4482 'icon' => 'trash', 4483 'class' => 'text-danger', 4484 ]; 4485 4486 $display_mobile = [ 4487 'href' => $link_admin_modules . '&module_name=' . urlencode($module->name) . '&' . ($module->enable_device & Context::DEVICE_MOBILE ? 'disable_device' : 'enable_device') . '=' . Context::DEVICE_MOBILE . '&tab_module=' . $module->tab, 4488 'onclick' => '', 4489 'title' => htmlspecialchars($module->enable_device & Context::DEVICE_MOBILE ? $this->translationsTab['Disable on mobiles'] : $this->translationsTab['Display on mobiles']), 4490 'text' => $module->enable_device & Context::DEVICE_MOBILE ? $this->translationsTab['Disable on mobiles'] : $this->translationsTab['Display on mobiles'], 4491 'cond' => $module->id, 4492 'icon' => 'mobile', 4493 ]; 4494 4495 $display_tablet = [ 4496 'href' => $link_admin_modules . '&module_name=' . urlencode($module->name) . '&' . ($module->enable_device & Context::DEVICE_TABLET ? 'disable_device' : 'enable_device') . '=' . Context::DEVICE_TABLET . '&tab_module=' . $module->tab, 4497 'onclick' => '', 4498 'title' => htmlspecialchars($module->enable_device & Context::DEVICE_TABLET ? $this->translationsTab['Disable on tablets'] : $this->translationsTab['Display on tablets']), 4499 'text' => $module->enable_device & Context::DEVICE_TABLET ? $this->translationsTab['Disable on tablets'] : $this->translationsTab['Display on tablets'], 4500 'cond' => $module->id, 4501 'icon' => 'tablet', 4502 ]; 4503 4504 $display_computer = [ 4505 'href' => $link_admin_modules . '&module_name=' . urlencode($module->name) . '&' . ($module->enable_device & Context::DEVICE_COMPUTER ? 'disable_device' : 'enable_device') . '=' . Context::DEVICE_COMPUTER . '&tab_module=' . $module->tab, 4506 'onclick' => '', 4507 'title' => htmlspecialchars($module->enable_device & Context::DEVICE_COMPUTER ? $this->translationsTab['Disable on computers'] : $this->translationsTab['Display on computers']), 4508 'text' => $module->enable_device & Context::DEVICE_COMPUTER ? $this->translationsTab['Disable on computers'] : $this->translationsTab['Display on computers'], 4509 'cond' => $module->id, 4510 'icon' => 'desktop', 4511 ]; 4512 4513 $install = [ 4514 'href' => $link_admin_modules . '&install=' . urlencode($module->name) . '&tab_module=' . $module->tab . '&module_name=' . $module->name . '&anchor=' . ucfirst($module->name) 4515 . (null !== $back ? '&back=' . urlencode($back) : '') . ($install_source_tracking ? '&source=' . $install_source_tracking : ''), 4516 'onclick' => '', 4517 'title' => $this->translationsTab['Install'], 4518 'text' => $this->translationsTab['Install'], 4519 'cond' => $module->id, 4520 'icon' => 'plus-sign-alt', 4521 ]; 4522 4523 $uninstall = [ 4524 'href' => $link_admin_modules . '&uninstall=' . urlencode($module->name) . '&tab_module=' . $module->tab . '&module_name=' . $module->name . '&anchor=' . ucfirst($module->name) . (null !== $back ? '&back=' . urlencode($back) : ''), 4525 'onclick' => (isset($module->onclick_option_content['uninstall']) ? $module->onclick_option_content['uninstall'] : 'return confirm(\'' . $this->translationsTab['confirm_uninstall_popup'] . '\');'), 4526 'title' => $this->translationsTab['Uninstall'], 4527 'text' => $this->translationsTab['Uninstall'], 4528 'cond' => $module->id, 4529 'icon' => 'minus-sign-alt', 4530 ]; 4531 4532 $remove_from_favorite = [ 4533 'href' => '#', 4534 'class' => 'action_unfavorite toggle_favorite', 4535 'onclick' => '', 4536 'title' => $this->translationsTab['Remove from Favorites'], 4537 'text' => $this->translationsTab['Remove from Favorites'], 4538 'cond' => $module->id, 4539 'icon' => 'star', 4540 'data-value' => '0', 4541 'data-module' => $module->name, 4542 ]; 4543 4544 $mark_as_favorite = [ 4545 'href' => '#', 4546 'class' => 'action_favorite toggle_favorite', 4547 'onclick' => '', 4548 'title' => $this->translationsTab['Mark as Favorite'], 4549 'text' => $this->translationsTab['Mark as Favorite'], 4550 'cond' => $module->id, 4551 'icon' => 'star', 4552 'data-value' => '1', 4553 'data-module' => $module->name, 4554 ]; 4555 4556 $update = [ 4557 'href' => $module->options['update_url'], 4558 'onclick' => '', 4559 'title' => 'Update it!', 4560 'text' => 'Update it!', 4561 'icon' => 'refresh', 4562 'cond' => $module->id, 4563 ]; 4564 4565 $divider = [ 4566 'href' => '#', 4567 'onclick' => '', 4568 'title' => 'divider', 4569 'text' => 'divider', 4570 'cond' => $module->id, 4571 ]; 4572 4573 if (isset($module->version_addons) && $module->version_addons) { 4574 $modules_options[] = $update; 4575 } 4576 4577 if ($module->active) { 4578 $modules_options[] = $configure_module; 4579 $modules_options[] = $desactive_module; 4580 $modules_options[] = $display_mobile; 4581 $modules_options[] = $display_tablet; 4582 $modules_options[] = $display_computer; 4583 } else { 4584 $modules_options[] = $desactive_module; 4585 $modules_options[] = $configure_module; 4586 } 4587 4588 $modules_options[] = $reset_module; 4589 4590 if ($output_type == 'select') { 4591 if (!$module->id) { 4592 $modules_options[] = $install; 4593 } else { 4594 $modules_options[] = $uninstall; 4595 } 4596 } elseif ($output_type == 'array') { 4597 if ($module->id) { 4598 $modules_options[] = $uninstall; 4599 } 4600 } 4601 4602 if (isset($module->preferences, $module->preferences['favorite']) && $module->preferences['favorite'] == 1) { 4603 $remove_from_favorite['style'] = ''; 4604 $mark_as_favorite['style'] = 'display:none;'; 4605 $modules_options[] = $remove_from_favorite; 4606 $modules_options[] = $mark_as_favorite; 4607 } else { 4608 $mark_as_favorite['style'] = ''; 4609 $remove_from_favorite['style'] = 'display:none;'; 4610 $modules_options[] = $remove_from_favorite; 4611 $modules_options[] = $mark_as_favorite; 4612 } 4613 4614 if ($module->id == 0) { 4615 $install['cond'] = 1; 4616 $install['flag_install'] = 1; 4617 $modules_options[] = $install; 4618 } 4619 $modules_options[] = $divider; 4620 $modules_options[] = $delete_module; 4621 4622 $return = ''; 4623 foreach ($modules_options as $option_name => $option) { 4624 if ($option['cond']) { 4625 if ($output_type == 'link') { 4626 $return .= '<li><a class="' . $option_name . ' action_module'; 4627 $return .= '" href="' . $option['href'] . (null !== $back ? '&back=' . urlencode($back) : '') . '"'; 4628 $return .= ' onclick="' . $option['onclick'] . '" title="' . $option['title'] . '"><i class="icon-' . (isset($option['icon']) && $option['icon'] ? $option['icon'] : 'cog') . '"></i> ' . $option['text'] . '</a></li>'; 4629 } elseif ($output_type == 'array') { 4630 if (!is_array($return)) { 4631 $return = []; 4632 } 4633 4634 $html = '<a class="'; 4635 4636 $is_install = isset($option['flag_install']) ? true : false; 4637 4638 if (isset($option['class'])) { 4639 $html .= $option['class']; 4640 } 4641 if ($is_install) { 4642 $html .= ' btn btn-success'; 4643 } 4644 if (!$is_install && count($return) == 0) { 4645 $html .= ' btn btn-default'; 4646 } 4647 4648 $html .= '"'; 4649 4650 if (isset($option['data-value'])) { 4651 $html .= ' data-value="' . $option['data-value'] . '"'; 4652 } 4653 4654 if (isset($option['data-module'])) { 4655 $html .= ' data-module="' . $option['data-module'] . '"'; 4656 } 4657 4658 if (isset($option['style'])) { 4659 $html .= ' style="' . $option['style'] . '"'; 4660 } 4661 4662 $html .= ' href="' . htmlentities($option['href']) . (null !== $back ? '&back=' . urlencode($back) : '') . '" onclick="' . $option['onclick'] . '" title="' . $option['title'] . '"><i class="icon-' . (isset($option['icon']) && $option['icon'] ? $option['icon'] : 'cog') . '"></i> ' . $option['text'] . '</a>'; 4663 $return[] = $html; 4664 } elseif ($output_type == 'select') { 4665 $return .= '<option id="' . $option_name . '" data-href="' . htmlentities($option['href']) . (null !== $back ? '&back=' . urlencode($back) : '') . '" data-onclick="' . $option['onclick'] . '">' . $option['text'] . '</option>'; 4666 } 4667 } 4668 } 4669 4670 if ($output_type == 'select') { 4671 $return = '<select id="select_' . $module->name . '">' . $return . '</select>'; 4672 } 4673 4674 return $return; 4675 } 4676 4677 public function ajaxProcessGetModuleQuickView() 4678 { 4679 $modules = Module::getModulesOnDisk(); 4680 4681 foreach ($modules as $module) { 4682 if ($module->name == Tools::getValue('module')) { 4683 break; 4684 } 4685 } 4686 4687 $url = $module->url; 4688 4689 if (isset($module->type) && ($module->type == 'addonsPartner' || $module->type == 'addonsNative')) { 4690 $url = $this->context->link->getAdminLink('AdminModules') . '&install=' . urlencode($module->name) . '&tab_module=' . $module->tab . '&module_name=' . $module->name . '&anchor=' . ucfirst($module->name); 4691 } 4692 4693 $this->context->smarty->assign([ 4694 'displayName' => $module->displayName, 4695 'image' => $module->image, 4696 'nb_rates' => (int) $module->nb_rates[0], 4697 'avg_rate' => (int) $module->avg_rate[0], 4698 'badges' => $module->badges, 4699 'compatibility' => $module->compatibility, 4700 'description_full' => $module->description_full, 4701 'additional_description' => $module->additional_description, 4702 'is_addons_partner' => (isset($module->type) && ($module->type == 'addonsPartner' || $module->type == 'addonsNative')), 4703 'url' => $url, 4704 'price' => $module->price, 4705 ]); 4706 // Fetch the translations in the right place - they are not defined by our current controller! 4707 Context::getContext()->override_controller_name_for_translations = 'AdminModules'; 4708 $this->smartyOutputContent('controllers/modules/quickview.tpl'); 4709 die(1); 4710 } 4711 4712 /** 4713 * Add an entry to the meta title. 4714 * 4715 * @param string $entry new entry 4716 */ 4717 public function addMetaTitle($entry) 4718 { 4719 // Only add entry if the meta title was not forced. 4720 if (is_array($this->meta_title)) { 4721 $this->meta_title[] = $entry; 4722 } 4723 } 4724 4725 /** 4726 * Set action. 4727 * 4728 * @param string $action 4729 */ 4730 public function setAction($action) 4731 { 4732 $this->action = $action; 4733 } 4734 4735 /** 4736 * Set IdObject. 4737 * 4738 * @param int $id_object 4739 */ 4740 public function setIdObject($id_object) 4741 { 4742 $this->id_object = (int) $id_object; 4743 } 4744 4745 /** 4746 * @return string 4747 */ 4748 public function getTabSlug() 4749 { 4750 if (empty($this->tabSlug)) { 4751 $this->tabSlug = Access::findSlugByIdTab($this->id); 4752 } 4753 4754 return $this->tabSlug; 4755 } 4756 4757 /** 4758 * {@inheritdoc} 4759 */ 4760 protected function buildContainer() 4761 { 4762 return SymfonyContainer::getInstance(); 4763 } 4764 4765 /** 4766 * Return the type of authorization on module page. 4767 * 4768 * @return int 4769 */ 4770 public function authorizationLevel() 4771 { 4772 if ( 4773 Access::isGranted( 4774 'ROLE_MOD_TAB_' . strtoupper($this->controller_name) . '_DELETE', 4775 $this->context->employee->id_profile 4776 ) 4777 ) { 4778 return AdminController::LEVEL_DELETE; 4779 } elseif ( 4780 Access::isGranted( 4781 'ROLE_MOD_TAB_' . strtoupper($this->controller_name) . '_CREATE', 4782 $this->context->employee->id_profile 4783 ) 4784 ) { 4785 return AdminController::LEVEL_ADD; 4786 } elseif ( 4787 Access::isGranted( 4788 'ROLE_MOD_TAB_' . strtoupper($this->controller_name) . '_UPDATE', 4789 $this->context->employee->id_profile 4790 ) 4791 ) { 4792 return AdminController::LEVEL_EDIT; 4793 } elseif ( 4794 Access::isGranted( 4795 'ROLE_MOD_TAB_' . strtoupper($this->controller_name) . '_READ', 4796 $this->context->employee->id_profile 4797 ) 4798 ) { 4799 return AdminController::LEVEL_VIEW; 4800 } else { 4801 return 0; 4802 } 4803 } 4804 4805 /** 4806 * Get the url of the first active sub-tab. 4807 * 4808 * @param array[] $subtabs 4809 * 4810 * @return string Url, or empty if no active sub-tab 4811 */ 4812 private function getTabLinkFromSubTabs(array $subtabs) 4813 { 4814 foreach ($subtabs as $tab) { 4815 if ($tab['active'] && $tab['enabled']) { 4816 return $tab['href']; 4817 } 4818 } 4819 4820 return ''; 4821 } 4822 4823 /** 4824 * Prepare price specifications to display cldr prices in javascript context. 4825 * 4826 * @param Context $context 4827 * 4828 * @return array 4829 */ 4830 private function preparePriceSpecifications(Context $context) 4831 { 4832 /* @var Currency */ 4833 $currency = $context->currency; 4834 /* @var PriceSpecification */ 4835 $priceSpecification = $context->getCurrentLocale()->getPriceSpecification($currency->iso_code); 4836 if (empty($priceSpecification)) { 4837 return []; 4838 } 4839 4840 return array_merge( 4841 ['symbol' => $priceSpecification->getSymbolsByNumberingSystem(Locale::NUMBERING_SYSTEM_LATIN)->toArray()], 4842 $priceSpecification->toArray() 4843 ); 4844 } 4845 4846 /** 4847 * Prepare number specifications to display cldr numbers in javascript context. 4848 * 4849 * @param Context $context 4850 * 4851 * @return array 4852 */ 4853 private function prepareNumberSpecifications(Context $context) 4854 { 4855 /* @var NumberSpecification */ 4856 $numberSpecification = $context->getCurrentLocale()->getNumberSpecification(); 4857 if (empty($numberSpecification)) { 4858 return []; 4859 } 4860 4861 return array_merge( 4862 ['symbol' => $numberSpecification->getSymbolsByNumberingSystem(Locale::NUMBERING_SYSTEM_LATIN)->toArray()], 4863 $numberSpecification->toArray() 4864 ); 4865 } 4866 4867 /** 4868 * Set if anonymous is allowed to run this controller 4869 * 4870 * @param bool $value 4871 * 4872 * @return bool 4873 */ 4874 protected function setAllowAnonymous($value) 4875 { 4876 $this->allowAnonymous = (bool) $value; 4877 } 4878 4879 /** 4880 * Return if an anonymous is allowed to run this controller 4881 * 4882 * @return bool 4883 */ 4884 protected function isAnonymousAllowed() 4885 { 4886 return $this->allowAnonymous; 4887 } 4888} 4889