· 8 years ago · Aug 27, 2018, 03:34 AM
1<?php
2/**
3 * 2007-2016 PrestaShop
4 *
5 * thirty bees is an extension to the PrestaShop e-commerce software developed by PrestaShop SA
6 * Copyright (C) 2017-2018 thirty bees
7 *
8 * NOTICE OF LICENSE
9 *
10 * This source file is subject to the Open Software License (OSL 3.0)
11 * that is bundled with this package in the file LICENSE.txt.
12 * It is also available through the world-wide-web at this URL:
13 * http://opensource.org/licenses/osl-3.0.php
14 * If you did not receive a copy of the license and are unable to
15 * obtain it through the world-wide-web, please send an email
16 * to license@thirtybees.com so we can send you a copy immediately.
17 *
18 * DISCLAIMER
19 *
20 * Do not edit or add to this file if you wish to upgrade PrestaShop to newer
21 * versions in the future. If you wish to customize PrestaShop for your
22 * needs please refer to https://www.thirtybees.com for more information.
23 *
24 * @author thirty bees <contact@thirtybees.com>
25 * @author PrestaShop SA <contact@prestashop.com>
26 * @copyright 2017-2018 thirty bees
27 * @copyright 2007-2016 PrestaShop SA
28 * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
29 * PrestaShop is an internationally registered trademark & property of PrestaShop SA
30 */
31
32/**
33 * Class AdminThemesControllerCore
34 *
35 * @since 1.0.0
36 */
37class AdminThemesControllerCore extends AdminController
38{
39 const MAX_NAME_LENGTH = 128;
40 // @codingStandardsIgnoreStart
41 /**
42 * This value is used in isThemeCompatible method. only version node with an
43 * higher version number will be used in [theme]/config.xml
44 *
45 * @since 1.4.0.11, check theme compatibility 1.4
46 * @var string
47 */
48 public static $check_features_version = '1.4';
49 /**
50 * Multidimensional array used to check [theme]/config.xml values,
51 * and also checks thirty bees current configuration if not match.
52 *
53 * @var array
54 */
55 public static $check_features = [
56 'ccc' => [
57 'attributes' => [
58 'available' => [
59 'value' => 'true',
60 /*
61 * accepted attribute value if value doesn't match, thirty bees configuration value must have those values
62 */
63 'check_if_not_valid' => [
64 'PS_CSS_THEME_CACHE' => 0,
65 'PS_JS_THEME_CACHE' => 0,
66 'PS_JS_HTML_THEME_COMPRESSION' => 0,
67 ],
68 ],
69 ],
70 'error' => 'This theme may not correctly use thirty bees\' "combine, compress and cache" options.',
71 'tab' => 'AdminPerformance',
72 ],
73 'guest_checkout' => [
74 'attributes' => [
75 'available' => [
76 'value' => 'true',
77 'check_if_not_valid' => ['PS_GUEST_CHECKOUT_ENABLED' => 0],
78 ],
79 ],
80 'error' => 'This theme may not correctly use thirty bees\' "guest checkout" feature.',
81 'tab' => 'AdminPreferences',
82 ],
83 'one_page_checkout' => [
84 'attributes' => [
85 'available' => [
86 'value' => 'true',
87 'check_if_not_valid' => ['PS_ORDER_PROCESS_TYPE' => 0],
88 ],
89 ],
90 'error' => 'This theme may not correctly use thirty bees\' "one-page checkout" feature.',
91 'tab' => 'AdminPreferences',
92 ],
93 'store_locator' => [
94 'attributes' => [
95 'available' => [
96 'value' => 'true',
97 'check_if_not_valid' => [
98 'PS_STORES_SIMPLIFIED' => 0,
99 'PS_STORES_DISPLAY_FOOTER' => 0,
100 ],
101 ],
102 ],
103 'error' => 'This theme may not correctly use thirty bees\' "store locator" feature.',
104 'tab' => 'AdminStores',
105 ],
106 ];
107 public $className = 'Theme';
108 public $table = 'theme';
109 protected $toolbar_scroll = false;
110 private $img_error;
111 public $can_display_themes = false;
112 public $to_install = [];
113 public $to_enable = [];
114 public $to_disable = [];
115 public $to_hook = [];
116 public $hook_list = [];
117 public $module_list = [];
118 public $native_modules = [];
119 public $user_doc = [];
120 public $image_list = [];
121 public $to_export = [];
122 // @codingStandardsIgnoreEnd
123
124 /**
125 * AdminThemesControllerCore constructor.
126 *
127 * @since 1.0.0
128 * @throws PrestaShopException
129 */
130 public function __construct()
131 {
132 $this->bootstrap = true;
133 parent::__construct();
134 }
135
136 /**
137 * Initialize
138 *
139 * @return void
140 *
141 * @throws Exception
142 * @throws PrestaShopDatabaseException
143 * @throws PrestaShopException
144 * @throws SmartyException
145 * @since 1.0.0
146 */
147 public function init()
148 {
149 // No cache for auto-refresh uploaded logo
150 header('Cache-Control: no-cache, must-revalidate');
151 header('Expires: Mon, 26 Jul 1997 05:00:00 GMT');
152
153 parent::init();
154 $this->can_display_themes = (!Shop::isFeatureActive() || Shop::getContext() == Shop::CONTEXT_SHOP);
155
156 libxml_use_internal_errors(true);
157
158 // Employee languages used for link and utm_source
159 $lang = new Language($this->context->language->id);
160 $isoLangUc = strtoupper($lang->iso_code);
161
162 $this->fields_options = [
163 'appearance' => [
164 'title' => $this->l('Your current theme'),
165 'icon' => 'icon-html5',
166 'tabs' => [
167 'logo' => $this->l('Logo'),
168 'logo2' => $this->l('Invoice & Email Logos'),
169 'icons' => $this->l('Icons'),
170 ],
171 'fields' => [
172 'PS_LOGO' => [
173 'title' => $this->l('Header logo'),
174 'hint' => $this->l('Will appear on main page. Recommended height: 52px. Maximum height on default theme: 65px.'),
175 'type' => 'file',
176 'name' => 'PS_LOGO',
177 'tab' => 'logo',
178 'thumb' => _PS_IMG_.Configuration::get('PS_LOGO'),
179 ],
180 'PS_LOGO_MAIL' => [
181 'title' => $this->l('Mail logo'),
182 'desc' => ((Configuration::get('PS_LOGO_MAIL') === false) ? '<span class="light-warning">'.$this->l('Warning: if no email logo is available, the main logo will be used instead.').'</span><br />' : ''),
183 'hint' => $this->l('Will appear on email headers. If undefined, the header logo will be used.'),
184 'type' => 'file',
185 'name' => 'PS_LOGO_MAIL',
186 'tab' => 'logo2',
187 'thumb' => (Configuration::get('PS_LOGO_MAIL') !== false && file_exists(_PS_IMG_DIR_.Configuration::get('PS_LOGO_MAIL'))) ? _PS_IMG_.Configuration::get('PS_LOGO_MAIL') : _PS_IMG_.Configuration::get('PS_LOGO'),
188 ],
189 'PS_LOGO_INVOICE' => [
190 'title' => $this->l('Invoice logo'),
191 'desc' => ((Configuration::get('PS_LOGO_INVOICE') === false) ? '<span class="light-warning">'.$this->l('Warning: if no invoice logo is available, the main logo will be used instead.').'</span><br />' : ''),
192 'hint' => $this->l('Will appear on invoice headers.').' '.$this->l('Warning: you can use a PNG file for transparency, but it can take up to 1 second per page for processing. Please consider using JPG instead.'),
193 'type' => 'file',
194 'name' => 'PS_LOGO_INVOICE',
195 'tab' => 'logo2',
196 'thumb' => (Configuration::get('PS_LOGO_INVOICE') !== false && file_exists(_PS_IMG_DIR_.Configuration::get('PS_LOGO_INVOICE'))) ? _PS_IMG_.Configuration::get('PS_LOGO_INVOICE') : _PS_IMG_.Configuration::get('PS_LOGO'),
197 ],
198 'PS_FAVICON' => [
199 'title' => $this->l('Favicon and phone icon'),
200 'hint' => $this->l('Will appear in the address bar of your web browser or home phone screen'),
201 'desc' => $this->l('Use a square image 512 x 512 for best results.'),
202 'type' => 'file',
203 'name' => 'PS_FAVICON',
204 'tab' => 'icons',
205 'thumb' => Media::getMediaPath(_PS_IMG_DIR_."favicon_{$this->context->shop->id}.ico").'?'.time(),
206 ],
207 'TB_SOURCE_FAVICON' => [
208 'title' => $this->l('Source favicon (PNG)'),
209 'hint' => $this->l('Will appear in the address bar of your web browser.'),
210 'desc' => $this->l('Make sure you upload a big enough favicon. Preferably one that covers all sizes and a square size.'),
211 'type' => 'file',
212 'name' => 'TB_SOURCE_FAVICON',
213 'tab' => 'icons',
214 'thumb' => $this->thumbnail(_PS_IMG_DIR_."favicon/favicon_{$this->context->shop->id}_source.png", 'favicon_source.png', 512, 'png', true, true),
215 ],
216 'TB_SOURCE_FAVICON_CODE' => [
217 'title' => $this->l('Favicon metas'),
218 'hint' => $this->l('The literal favicon meta code that gets included on every page.'),
219 'type' => 'code',
220 'mode' => 'html',
221 'enableBasicAutocompletion' => true,
222 'enableSnippets' => true,
223 'enableLiveAutocompletion' => true,
224 'visibility' => Shop::CONTEXT_ALL,
225 'minLines' => 20,
226 'maxLines' => 30,
227 'tab' => 'icons',
228 'grab_favicon_template' => true,
229 'auto_value' => false,
230 'value' => preg_replace('/\<br(\s*)?\/?\>/i', "\n", Configuration::get('TB_SOURCE_FAVICON_CODE')),
231 ],
232 'PS_STORES_ICON' => [
233 'title' => $this->l('Store icon'),
234 'hint' => $this->l('Will appear on the store locator (inside Google Maps).').'<br />'.$this->l('Suggested size: 30x30, transparent GIF.'),
235 'type' => 'file',
236 'name' => 'PS_STORES_ICON',
237 'tab' => 'icons',
238 'thumb' => _PS_IMG_.Configuration::get('PS_STORES_ICON'),
239 ],
240 ],
241 'after_tabs' => [
242 'cur_theme' => Theme::getThemeInfo($this->context->shop->id_theme),
243 ],
244 'submit' => ['title' => $this->l('Save')],
245 'buttons' => [],
246 ],
247 ];
248
249 $installedTheme = Theme::getAllThemes([$this->context->shop->id_theme]);
250 $nonInstalledTheme = ($this->context->mode == Context::MODE_HOST) ? [] : Theme::getNonInstalledTheme();
251 if (count($installedTheme) || !empty($nonInstalledTheme)) {
252 $this->fields_options['theme'] = [
253 'title' => sprintf($this->l('Select a theme for the "%s" shop'), $this->context->shop->name),
254 'description' => (!$this->can_display_themes) ? $this->l('You must select a shop from the above list if you wish to choose a theme.') : '',
255 'fields' => [
256 'theme_for_shop' => [
257 'type' => 'theme',
258 'themes' => $installedTheme,
259 'not_installed' => $nonInstalledTheme,
260 'id_theme' => $this->context->shop->id_theme,
261 'can_display_themes' => $this->can_display_themes,
262 'no_multishop_checkbox' => true,
263 ],
264 ],
265 ];
266 }
267 }
268
269 /**
270 * Render form
271 *
272 * @return string
273 *
274 * @throws Exception
275 * @throws PrestaShopDatabaseException
276 * @throws PrestaShopException
277 * @throws SmartyException
278 * @since 1.0.0
279 */
280 public function renderForm()
281 {
282 $getAvailableThemes = Theme::getAvailable(false);
283 $availableThemeDir = [];
284 $selectedThemeDir = null;
285 $metas = Meta::getMetas();
286 $formatedMetas = [];
287
288 $imageUrl = false;
289 if ($this->object) {
290 if ((int) $this->object->id > 0) {
291 $theme = new Theme((int) $this->object->id);
292 $themeMetas = Db::getInstance()->executeS(
293 (new DbQuery())
294 ->select('ml.`title`, m.`page`, tm.`left_column` as `left`, tm.`right_column` as `right`, m.`id_meta`, tm.`id_theme_meta`')
295 ->from('theme_meta', 'tm')
296 ->leftJoin('meta', 'm', 'm.`id_meta` = tm.`id_meta`')
297 ->leftJoin('meta_lang', 'ml', 'ml.`id_meta` = m.`id_meta`')
298 ->where('ml.`id_lang` = '.(int) $this->context->language->id)
299 ->where('ml.`id_shop` = '.(int) $this->context->shop->id)
300 ->where('tm.`id_theme` = '.(int) $this->object->id)
301 );
302
303 // if no theme_meta are found, we must create them
304 if (empty($themeMetas)) {
305 $metas = Db::getInstance(_PS_USE_SQL_SLAVE_)->executeS(
306 (new DbQuery())
307 ->select('`id_meta`')
308 ->from('meta')
309 );
310 $metasDefault = [];
311 foreach ($metas as $meta) {
312 $tmpMeta['id_meta'] = (int) $meta['id_meta'];
313 $tmpMeta['left'] = 1;
314 $tmpMeta['right'] = 1;
315 $metasDefault[] = $tmpMeta;
316 }
317 $theme->updateMetas($metasDefault);
318 $themeMetas = Db::getInstance()->executeS(
319 (new DbQuery())
320 ->select('ml.`title`, m.`page`, tm.`left_column` as `left`, tm.`right_column` as `right`, m.`id_meta`, tm.`id_theme_meta`')
321 ->from('theme_meta', 'tm')
322 ->leftJoin('meta', 'm', 'm.`id_meta` = tm.`id_meta`')
323 ->leftJoin('meta_lang', 'ml', 'ml.`id_meta` = m.`id_meta`')
324 ->where('ml.`id_lang` = '.(int) $this->context->language->id)
325 ->where('tm.`id_theme` = '.(int) $this->object->id)
326 );
327 }
328
329 $imageUrl = '<img alt="preview" src="'.__PS_BASE_URI__.'themes/'.$theme->directory.'/preview.jpg">';
330
331 foreach ($themeMetas as $key => &$meta) {
332 if (!isset($meta['title']) || !$meta['title'] || $meta['title'] == '') {
333 $meta['title'] = $meta['page'];
334 }
335 }
336
337 $formatedMetas = $themeMetas;
338 }
339 $selectedThemeDir = $this->object->directory;
340 }
341
342 foreach ($getAvailableThemes as $k => $dirname) {
343 $availableThemeDir[$k]['value'] = $dirname;
344 $availableThemeDir[$k]['label'] = $dirname;
345 $availableThemeDir[$k]['id'] = $dirname;
346 };
347
348 $this->fields_form = [
349 'tinymce' => false,
350 'legend' => [
351 'title' => $this->l('Theme'),
352 'icon' => 'icon-picture',
353 ],
354 'input' => [
355 [
356 'type' => 'text',
357 'label' => $this->l('Name of the theme'),
358 'name' => 'name',
359 'required' => true,
360 'hint' => $this->l('Invalid characters:').' <>;=#{}',
361 ],
362 [
363 'type' => 'file',
364 'label' => $this->l('Preview image for the theme'),
365 'name' => 'image_preview',
366 'display_image' => true,
367 'hint' => sprintf($this->l('Maximum image size: %1s'), Tools::formatBytes(Tools::getMaxUploadSize())),
368 'image' => $imageUrl,
369 ],
370 [
371 'type' => 'switch',
372 'label' => $this->l('Default left column'),
373 'name' => 'default_left_column',
374 'hint' => $this->l('Choose a default behavior when displaying the column in a new page added by you or by a module.'),
375 'values' => [
376 [
377 'id' => 'default_left_column_on',
378 'value' => 1,
379 'label' => $this->l('Yes'),
380 ],
381 [
382 'id' => 'default_left_column_off',
383 'value' => 0,
384 'label' => $this->l('No'),
385 ],
386 ],
387 ],
388 [
389 'type' => 'switch',
390 'label' => $this->l('Default right column'),
391 'name' => 'default_right_column',
392 'hint' => $this->l('Choose a default behavior when displaying the column in a new page added by you or by a module.'),
393 'values' => [
394 [
395 'id' => 'default_right_column_on',
396 'value' => 1,
397 'label' => $this->l('Yes'),
398 ],
399 [
400 'id' => 'default_right_column_off',
401 'value' => 0,
402 'label' => $this->l('No'),
403 ],
404 ],
405 ],
406 [
407 'type' => 'text',
408 'label' => $this->l('Number of products per page'),
409 'name' => 'product_per_page',
410 'hint' => $this->l('This value will be used when activating the theme.'),
411 ],
412 ],
413 'submit' => [
414 'title' => $this->l('Save'),
415 ],
416 ];
417 // adding a new theme, you can create a directory, and copy from an existing theme
418 if ($this->display == 'add' || !Validate::isLoadedObject($this->object)) {
419 $this->fields_form['input'][] = [
420 'type' => 'text',
421 'label' => $this->l('Name of the theme\'s directory'),
422 'name' => 'directory',
423 'required' => true,
424 'hint' => $this->l('If the directory does not exist, thirty bees will create it automatically.'),
425 ];
426
427 $themeQuery = Theme::getThemes();
428 $this->fields_form['input'][] = [
429 'type' => 'select',
430 'name' => 'based_on',
431 'label' => $this->l('Copy missing files from existing theme'),
432 'hint' => $this->l('If you create a new theme from scratch, it is recommended that you use the files from the default theme as a foundation.'),
433 'options' => [
434 'id' => 'id',
435 'name' => 'name',
436 'default' => [
437 'value' => 0,
438 'label' => '-',
439 ],
440 'query' => $themeQuery,
441 ],
442 ];
443
444 $this->fields_form['input'][] = [
445 'type' => 'switch',
446 'label' => $this->l('Responsive'),
447 'name' => 'responsive',
448 'hint' => $this->l('Please indicate if the theme is adapted to all screen sizes (mobile, tablet, desktop).'),
449 'values' => [
450 [
451 'id' => 'responsive_on',
452 'value' => 1,
453 'label' => $this->l('Yes'),
454 ],
455 [
456 'id' => 'responsive_off',
457 'value' => 0,
458 'label' => $this->l('No'),
459 ],
460 ],
461 ];
462 } else {
463 $this->fields_form['input'][] = [
464 'type' => 'radio',
465 'label' => $this->l('Directory'),
466 'name' => 'directory',
467 'required' => true,
468 'br' => true,
469 'values' => $availableThemeDir,
470 'selected' => $selectedThemeDir,
471 'hint' => $this->l('Please select a valid theme directory.'),
472 ];
473 }
474
475 $list = '';
476 if (Tools::getIsset('update'.$this->table)) {
477 $fieldsList = [
478 'title' => [
479 'title' => $this->l('Meta'),
480 'align' => 'center',
481 'width' => 'auto',
482 ],
483 'left' => [
484 'title' => $this->l('Left column'),
485 'active' => 'left',
486 'type' => 'bool',
487 'ajax' => true,
488 ],
489 'right' => [
490 'title' => $this->l('Right column'),
491 'active' => 'right',
492 'type' => 'bool',
493 'ajax' => true,
494 ],
495 ];
496 $helperList = new HelperList();
497 $helperList->tpl_vars = ['icon' => 'icon-columns'];
498 $helperList->title = $this->l('Appearance of columns');
499 $helperList->no_link = true;
500 $helperList->shopLinkType = '';
501 $helperList->identifier = 'id_theme_meta';
502 $helperList->table = 'meta';
503 $helperList->tpl_vars['show_filters'] = false;
504 $helperList->currentIndex = $this->context->link->getAdminLink('AdminThemes', false);
505 $helperList->token = Tools::getAdminTokenLite('AdminThemes');
506
507 $list = $helperList->generateList($formatedMetas, $fieldsList);
508 }
509
510 return parent::renderForm().$list;
511 }
512
513 /**
514 * Render list
515 *
516 * @return false|string
517 *
518 * @throws PrestaShopException
519 * @throws PrestaShopExceptionCore
520 * @since 1.0.0
521 */
522 public function renderList()
523 {
524 return parent::renderList();
525 }
526
527 /**
528 * @return bool
529 *
530 * @since 1.0.0
531 */
532 public function downloadAddonsThemes()
533 {
534 return true;
535 }
536
537 /**
538 * Process add
539 *
540 * @return bool|Theme
541 *
542 * @since 1.0.0
543 * @throws PrestaShopException
544 */
545 public function processAdd()
546 {
547 if (Tools::getValue('directory') == '' || Tools::getValue('name') == '') {
548 $this->errors[] = $this->l('Form invalid');
549 $this->display = 'form';
550
551 return false;
552 }
553 if (($newDir = Tools::getValue('directory')) != '') {
554 if (!Validate::isDirName($newDir)) {
555 $this->display = 'add';
556
557 return !($this->errors[] = sprintf(Tools::displayError('"%s" is not a valid directory name'), $newDir));
558 }
559 if (Theme::getByDirectory($newDir)) {
560 $this->display = 'add';
561
562 return !($this->errors[] = Tools::displayError('A directory with this name already exists.'));
563 }
564
565 if (mkdir(_PS_ALL_THEMES_DIR_.$newDir, Theme::$access_rights)) {
566 $this->confirmations[] = $this->l('The directory was successfully created.');
567 }
568
569 if (0 !== $idBased = (int) Tools::getValue('based_on')) {
570 $baseTheme = new Theme($idBased);
571 $this->copyTheme($baseTheme->directory, $newDir);
572 }
573
574 if (isset($_FILES['image_preview']) && $_FILES['image_preview']['error'] == 0) {
575 if (@getimagesize($_FILES['image_preview']['tmp_name']) && !ImageManager::validateUpload($_FILES['image_preview'], Tools::getMaxUploadSize())) {
576 move_uploaded_file($_FILES['image_preview']['tmp_name'], _PS_ALL_THEMES_DIR_.$newDir.'/preview.jpg');
577 } else {
578 $this->errors[] = $this->l('Image is not valid.');
579 $this->display = 'form';
580
581 return false;
582 }
583 }
584 }
585
586 /** @var Theme $theme */
587 $theme = parent::processAdd();
588 if ((int) $theme->product_per_page == 0) {
589 $theme->product_per_page = 1;
590 $theme->save();
591 }
592 if (is_object($theme) && (int) $theme->id > 0) {
593 $metas = Meta::getMetas();
594
595 foreach ($metas as &$meta) {
596 $meta['left'] = $theme->default_left_column;
597 $meta['right'] = $theme->default_right_column;
598 }
599 $theme->updateMetas($metas, true);
600 }
601
602 return $theme;
603 }
604
605 /**
606 * copy $base_theme_dir into $target_theme_dir.
607 *
608 * @param string $baseThemeDir relative path to base dir
609 * @param string $targetThemeDir relative path to target dir
610 *
611 * @return bool true if success
612 *
613 * @since 1.0.0
614 */
615 protected static function copyTheme($baseThemeDir, $targetThemeDir)
616 {
617 $res = true;
618 $baseThemeDir = Tools::normalizeDirectory($baseThemeDir);
619 $baseDir = _PS_ALL_THEMES_DIR_.$baseThemeDir;
620 $targetThemeDir = Tools::normalizeDirectory($targetThemeDir);
621 $targetDir = _PS_ALL_THEMES_DIR_.$targetThemeDir;
622 $files = scandir($baseDir);
623
624 foreach ($files as $file) {
625 if (!in_array($file[0], ['.', '..', '.svn'])) {
626 if (is_dir($baseDir.$file)) {
627 if (!is_dir($targetDir.$file)) {
628 mkdir($targetDir.$file, Theme::$access_rights);
629 }
630
631 $res &= static::copyTheme($baseThemeDir.$file, $targetThemeDir.$file);
632 } elseif (!file_exists($targetDir.$file)) {
633 $res &= copy($baseDir.$file, $targetDir.$file);
634 }
635 }
636 }
637
638 return $res;
639 }
640
641 /**
642 * Process update
643 *
644 * @return void
645 *
646 * @throws PrestaShopDatabaseException
647 * @throws PrestaShopException
648 * @since 1.0.0
649 */
650 public function processUpdate()
651 {
652 if (!($this->tabAccess['delete'] && $this->tabAccess['edit'] && $this->tabAccess['add']) || _PS_MODE_DEMO_) {
653 $this->errors[] = Tools::displayError('You do not have permission to edit here.');
654 } else {
655 if (Tools::getIsset('id_theme') && Tools::getIsset('name') && Tools::getIsset('directory')) {
656 $theme = new Theme((int) Tools::getValue('id_theme'));
657 $theme->name = Tools::getValue('name');
658 $theme->directory = Tools::getValue('directory');
659 $theme->default_left_column = Tools::getValue('default_left_column');
660 $theme->default_right_column = Tools::getValue('default_right_column');
661 $nbProductPerPage = (int) Tools::getValue('product_per_page');
662 if ($nbProductPerPage == 0) {
663 $nbProductPerPage = 1;
664 }
665 $theme->product_per_page = $nbProductPerPage;
666 if ($this->context->shop->id_theme == (int) Tools::getValue('id_theme')) {
667 Configuration::updateValue('PS_PRODUCTS_PER_PAGE', $nbProductPerPage);
668 }
669 if (isset($_FILES['image_preview']) && $_FILES['image_preview']['error'] == 0) {
670 if (@getimagesize($_FILES['image_preview']['tmp_name']) && !ImageManager::validateUpload($_FILES['image_preview'], 300000)) {
671 move_uploaded_file($_FILES['image_preview']['tmp_name'], _PS_ALL_THEMES_DIR_.$theme->directory.'/preview.jpg');
672 } else {
673 $this->errors[] = $this->l('Image is not valid.');
674 $this->display = 'form';
675
676 return;
677 }
678 }
679 $theme->update();
680 }
681 Tools::redirectAdmin($this->context->link->getAdminLink('AdminThemes').'&conf=29');
682 }
683 }
684
685 /**
686 * Process delete
687 *
688 * @return bool|false|ObjectModel
689 *
690 * @throws PrestaShopDatabaseException
691 * @throws PrestaShopException
692 * @since 1.0.0
693 */
694 public function processDelete()
695 {
696 if (!$this->tabAccess['delete'] || _PS_MODE_DEMO_) {
697 $this->errors[] = Tools::displayError('You do not have permission to delete here.');
698 } else {
699 /** @var Theme $obj */
700 $obj = $this->loadObject();
701 if ($obj) {
702 if ($obj->isUsed()) {
703 $this->errors[] = $this->l('The theme is being used by at least one shop. Please choose another theme before continuing.');
704
705 return false;
706 }
707 $themes = [];
708 foreach (Theme::getThemes() as $theme) {
709 /** @var Theme $theme */
710 if ($theme->id != $obj->id) {
711 $themes[] = $theme->directory;
712 }
713 }
714 if (is_dir(_PS_ALL_THEMES_DIR_.$obj->directory) && !in_array($obj->directory, $themes)) {
715 Tools::deleteDirectory(_PS_ALL_THEMES_DIR_.$obj->directory.'/');
716 }
717 $obj->removeMetas();
718 } elseif ($obj === false && $themeDir = Tools::getValue('theme_dir')) {
719 $themeDir = basename($themeDir);
720 if (Tools::deleteDirectory(_PS_ALL_THEMES_DIR_.$themeDir.'/')) {
721 Tools::redirectAdmin($this->context->link->getAdminLink('AdminThemes').'&conf=2');
722 } else {
723 $this->errors[] = Tools::displayError('The folder cannot be deleted');
724 }
725 }
726 }
727
728 return parent::processDelete();
729 }
730
731 /**
732 * Process theme export
733 *
734 * @return void
735 *
736 * @throws Adapter_Exception
737 * @throws PrestaShopDatabaseException
738 * @throws PrestaShopException
739 * @since 1.0.0
740 */
741 public function processExportTheme()
742 {
743 if (Tools::isSubmit('name')) {
744 if ($this->checkPostedDatas()) {
745 $filename = Tools::htmlentitiesUTF8($_FILES['documentation']['name']);
746 $name = Tools::htmlentitiesUTF8(Tools::getValue('documentationName'));
747 $this->user_doc = [$name.'¤doc/'.$filename];
748
749 $table = Db::getInstance(_PS_USE_SQL_SLAVE_)->executeS(
750 (new DbQuery())
751 ->select('`name`, `width`, `products`, `categories`, `manufacturers`, `suppliers`, `scenes`')
752 ->from('image_type')
753 );
754
755 $this->image_list = [];
756 foreach ($table as $row) {
757 $this->image_list[] = $row['name'].';'.$row['width'].';'.$row['height'].';'.
758 ($row['products'] == 1 ? 'true' : 'false').';'.
759 ($row['categories'] == 1 ? 'true' : 'false').';'.
760 ($row['manufacturers'] == 1 ? 'true' : 'false').';'.
761 ($row['suppliers'] == 1 ? 'true' : 'false').';'.
762 ($row['scenes'] == 1 ? 'true' : 'false');
763 }
764
765 $idShop = Db::getInstance(_PS_USE_SQL_SLAVE_)->getValue(
766 (new DbQuery())
767 ->select('`id_shop`')
768 ->from('shop')
769 ->where('`id_theme` = '.(int) Tools::getValue('id_theme_export'))
770 );
771
772 // Select the list of module for this shop
773 $this->module_list = Db::getInstance(_PS_USE_SQL_SLAVE_)->executeS(
774 (new DbQuery())
775 ->select('m.`id_module`, m.`name`, m.`active`, ms.`id_shop`')
776 ->from('module', 'm')
777 ->leftJoin('module_shop', 'ms', 'm.`id_module` = ms.`id_module`')
778 ->where('ms.`id_shop` = '.(int) $idShop)
779 );
780
781 // Select the list of hook for this shop
782 $this->hook_list = Db::getInstance(_PS_USE_SQL_SLAVE_)->executeS(
783 (new DbQuery())
784 ->select('h.`id_hook`, h.`name` AS `name_hook`, hm.`position`, hm.`id_module`, m.`name` AS `name_module`, GROUP_CONCAT(hme.`file_name`, ",") AS `exceptions`')
785 ->from('hook', 'h')
786 ->leftJoin('hook_module', 'hm', 'hm.`id_hook` = h.`id_hook`')
787 ->leftJoin('module', 'm', 'hm.`id_module` = m.`id_module`')
788 ->leftOuterJoin('hook_module_exceptions', 'hme', 'hme.`id_module` = hm.`id_module` AND hme.`id_hook` = h.`id_hook`')
789 ->where('hm.`id_shop` = '.(int) $idShop)
790 ->groupBy('hm.`id_module`, h.`id_hook`')
791 ->orderBy('name_module')
792 );
793
794 $this->native_modules = $this->getNativeModule();
795
796 foreach ($this->hook_list as &$row) {
797 $row['exceptions'] = trim(preg_replace('/(,,+)/', ',', $row['exceptions']), ',');
798 }
799
800 $this->to_install = [];
801 $this->to_enable = [];
802 $this->to_hook = [];
803
804 foreach ($this->module_list as $array) {
805 if (!static::checkParentClass($array['name'])) {
806 continue;
807 }
808 if (in_array($array['name'], $this->native_modules)) {
809 if ($array['active'] == 1) {
810 $this->to_enable[] = $array['name'];
811 } else {
812 $this->to_disable[] = $array['name'];
813 }
814 } elseif ($array['active'] == 1) {
815 $this->to_install[] = $array['name'];
816 }
817 }
818 foreach ($this->native_modules as $str) {
819 $flag = 0;
820 if (!static::checkParentClass($str)) {
821 continue;
822 }
823 foreach ($this->module_list as $tmp) {
824 if (in_array($str, $tmp)) {
825 $flag = 1;
826 break;
827 }
828 }
829 if ($flag == 0) {
830 $this->to_disable[] = $str;
831 }
832 }
833
834 foreach ($_POST as $key => $value) {
835 if (strncmp($key, 'modulesToExport_module', strlen('modulesToExport_module')) == 0) {
836 $this->to_export[] = $value;
837 }
838 }
839
840 if ($this->to_install) {
841 foreach ($this->to_install as $string) {
842 foreach ($this->hook_list as $tmp) {
843 if ($tmp['name_module'] == $string) {
844 $this->to_hook[] = $string.';'.$tmp['name_hook'].';'.$tmp['position'].';'.$tmp['exceptions'];
845 }
846 }
847 }
848 }
849 if ($this->to_enable) {
850 foreach ($this->to_enable as $string) {
851 foreach ($this->hook_list as $tmp) {
852 if ($tmp['name_module'] == $string) {
853 $this->to_hook[] = $string.';'.$tmp['name_hook'].';'.$tmp['position'].';'.$tmp['exceptions'];
854 }
855 }
856 }
857 }
858
859 $themeToExport = new Theme((int) Tools::getValue('id_theme_export'));
860 $metas = $themeToExport->getMetas();
861
862 $this->generateXML($themeToExport, $metas);
863 $this->generateArchive();
864 } else {
865 $this->display = 'exporttheme';
866 }
867 } else {
868 $this->display = 'exporttheme';
869 }
870 }
871
872 /**
873 * Check posted data
874 *
875 * @return bool
876 *
877 * @since 1.0.0
878 */
879 protected function checkPostedDatas()
880 {
881 $mail = Tools::getValue('email');
882 $website = Tools::getValue('website');
883
884 if ($mail && !preg_match('#^[\w.-]+@[\w.-]+\.[a-zA-Z]{2,6}$#', $mail)) {
885 $this->errors[] = $this->l('There is an error in your email syntax!');
886 } elseif ($website && (!Validate::isURL($website) || !Validate::isAbsoluteUrl($website))) {
887 $this->errors[] = $this->l('There is an error in your URL syntax!');
888 } elseif (!$this->checkVersionsAndCompatibility() || !$this->checkNames() || !$this->checkDocumentation()) {
889 return false;
890 } else {
891 return true;
892 }
893
894 return false;
895 }
896
897 /**
898 * Check versions and compatibility
899 *
900 * @return bool
901 *
902 * @since 1.0.0
903 */
904 protected function checkVersionsAndCompatibility()
905 {
906 $exp = '#^[0-9]+[.]+[0-9.]*[0-9]$#';
907
908 if (!preg_match('#^[0-9][.][0-9]$#', Tools::getValue('theme_version')) ||
909 !preg_match($exp, Tools::getValue('compa_from')) || !preg_match($exp, Tools::getValue('compa_to')) ||
910 version_compare(Tools::getValue('compa_from'), Tools::getValue('compa_to')) == 1
911 ) {
912 $this->errors[] = $this->l('Syntax error on version field. Only digits and periods (.) are allowed, and the compatibility version should be increasing or at least be equal to the previous version.');
913 }
914
915 if (count($this->errors) > 0) {
916 return false;
917 }
918
919 return true;
920 }
921
922 /**
923 * Check names
924 *
925 * @return bool
926 *
927 * @since 1.0.0
928 */
929 protected function checkNames()
930 {
931 $author = Tools::getValue('name');
932 $themeName = Tools::getValue('theme_name');
933
934 if (!$author || !Validate::isGenericName($author) || strlen($author) > static::MAX_NAME_LENGTH) {
935 $this->errors[] = $this->l('Please enter a valid author name');
936 } elseif (!$themeName || !Validate::isGenericName($themeName) || strlen($themeName) > static::MAX_NAME_LENGTH) {
937 $this->errors[] = $this->l('Please enter a valid theme name');
938 }
939
940 if (count($this->errors) > 0) {
941 return false;
942 }
943
944 return true;
945 }
946
947 /**
948 * Check documentation
949 *
950 * @return bool
951 *
952 * @since 1.0.0
953 */
954 protected function checkDocumentation()
955 {
956 $extensions = [
957 '.pdf',
958 '.txt',
959 ];
960
961 if (isset($_FILES['documentation']) && $_FILES['documentation']['name'] != '') {
962 $extension = strrchr($_FILES['documentation']['name'], '.');
963 $name = Tools::getValue('documentationName');
964
965 if (!in_array($extension, $extensions)) {
966 $this->errors[] = $this->l('File extension must be .txt or .pdf');
967 } elseif ($_FILES['documentation']['error'] > 0) {
968 $this->errors[] = $this->l('An error occurred during documentation upload');
969 } elseif ($_FILES['documentation']['size'] > 1048576) {
970 $this->errors[] = $this->l('An error occurred while uploading the documentation. Maximum size allowed is 1MB.');
971 } elseif (!$name || !Validate::isGenericName($name) || strlen($name) > static::MAX_NAME_LENGTH) {
972 $this->errors[] = $this->l('Please enter a valid documentation name');
973 }
974 }
975
976 if (count($this->errors) > 0) {
977 return false;
978 }
979
980 return true;
981 }
982
983 /**
984 *
985 * @param int $type
986 * $type = 0 both native & partner (default)
987 * $type = 1 native
988 * $type = 2 partner
989 *
990 *
991 * @return array
992 *
993 * @since 1.0.0
994 */
995 protected function getNativeModule($type = 0)
996 {
997 return [
998 'addsharethis',
999 'bankwire',
1000 'blockadvertising',
1001 'blockbanner',
1002 'blockbestsellers',
1003 'blockcart',
1004 'blockcategories',
1005 'blockcms',
1006 'blockcmsinfo',
1007 'blockcontact',
1008 'blockcontactinfos',
1009 'blockcurrencies',
1010 'blockcustomerprivacy',
1011 'blockfacebook',
1012 'blocklanguages',
1013 'blocklayered',
1014 'blocklink',
1015 'blockmanufacturer',
1016 'blockmyaccount',
1017 'blockmyaccountfooter',
1018 'blocknewproducts',
1019 'blocknewsletter',
1020 'blockpaymentlogo',
1021 'blockpermanentlinks',
1022 'blockreinsurance',
1023 'blockrss',
1024 'blocksearch',
1025 'blocksharefb',
1026 'blocksocial',
1027 'blockspecials',
1028 'blockstore',
1029 'blocksupplier',
1030 'blocktags',
1031 'blocktopmenu',
1032 'blockuserinfo',
1033 'blockviewed',
1034 'blockwishlist',
1035 'carriercompare',
1036 'cashondelivery',
1037 'cheque',
1038 'crossselling',
1039 'dashactivity',
1040 'dashgoals',
1041 'dashproducts',
1042 'dashtrends',
1043 'dateofdelivery',
1044 'editorial',
1045 'favoriteproducts',
1046 'feeder',
1047 'followup',
1048 'gapi',
1049 'graphnvd3',
1050 'gridhtml',
1051 'homefeatured',
1052 'homeslider',
1053 'loyalty',
1054 'mailalerts',
1055 'newsletter',
1056 'pagesnotfound',
1057 'productcomments',
1058 'productpaymentlogos',
1059 'productscategory',
1060 'producttooltip',
1061 'pscleaner',
1062 'referralprogram',
1063 'sekeywords',
1064 'sendtoafriend',
1065 'socialsharing',
1066 'statsbestcategories',
1067 'statsbestcustomers',
1068 'statsbestmanufacturers',
1069 'statsbestproducts',
1070 'statsbestsuppliers',
1071 'statsbestvouchers',
1072 'statscarrier',
1073 'statscatalog',
1074 'statscheckup',
1075 'statsdata',
1076 'statsequipment',
1077 'statsforecast',
1078 'statslive',
1079 'statsnewsletter',
1080 'statsorigin',
1081 'statspersonalinfos',
1082 'statsproduct',
1083 'statsregistrations',
1084 'statssales',
1085 'statssearch',
1086 'statsstock',
1087 'statsvisits',
1088 'themeconfigurator',
1089 'trackingfront',
1090 'vatnumber',
1091 'watermark',
1092 ];
1093 }
1094
1095 /**
1096 * Check parent class
1097 *
1098 * @param $name
1099 *
1100 * @return bool
1101 *
1102 * @throws Adapter_Exception
1103 * @throws PrestaShopDatabaseException
1104 * @throws PrestaShopException
1105 * @since 1.0.0
1106 */
1107 protected function checkParentClass($name)
1108 {
1109 if (!$obj = Module::getInstanceByName($name)) {
1110 return false;
1111 }
1112 if (is_callable([$obj, 'validateOrder'])) {
1113 return false;
1114 }
1115 if (is_callable([$obj, 'getDateBetween'])) {
1116 return false;
1117 }
1118 if (is_callable([$obj, 'getGridEngines'])) {
1119 return false;
1120 }
1121 if (is_callable([$obj, 'getGraphEngines'])) {
1122 return false;
1123 }
1124 if (is_callable([$obj, 'hookAdminStatsModules'])) {
1125 return false;
1126 } else {
1127 return true;
1128 }
1129 }
1130
1131 /**
1132 * Generate XML
1133 *
1134 * @param Theme $themeToExport
1135 * @param array $metas
1136 *
1137 * @since 1.0.0
1138 * @throws PrestaShopException
1139 */
1140 protected function generateXML($themeToExport, $metas)
1141 {
1142 $theme = new SimpleXMLElement('<?xml version="1.0" encoding="UTF-8"?><!-- Copyright thirty bees --><theme></theme>');
1143 $theme->addAttribute('version', Tools::getValue('theme_version'));
1144 $theme->addAttribute('name', Tools::htmlentitiesUTF8(Tools::getValue('theme_name')));
1145 $theme->addAttribute('directory', Tools::htmlentitiesUTF8(Tools::getValue('theme_directory')));
1146 $author = $theme->addChild('author');
1147 $author->addAttribute('name', Tools::htmlentitiesUTF8(Tools::getValue('name')));
1148 $author->addAttribute('email', Tools::htmlentitiesUTF8(Tools::getValue('email')));
1149 $author->addAttribute('url', Tools::htmlentitiesUTF8(Tools::getValue('website')));
1150
1151 $descriptions = $theme->addChild('descriptions');
1152 $languages = Language::getLanguages();
1153 foreach ($languages as $language) {
1154 $val = Tools::htmlentitiesUTF8(Tools::getValue('body_title_'.$language['id_lang']));
1155 $description = $descriptions->addChild('description', Tools::htmlentitiesUTF8($val));
1156 $description->addAttribute('iso', $language['iso_code']);
1157 }
1158
1159 $variations = $theme->addChild('variations');
1160
1161 $variation = $variations->addChild('variation');
1162 $variation->addAttribute('name', Tools::htmlentitiesUTF8(Tools::getValue('theme_name')));
1163 $variation->addAttribute('directory', Tools::getValue('theme_directory'));
1164 $variation->addAttribute('responsive', $themeToExport->responsive);
1165 $variation->addAttribute('default_left_column', $themeToExport->default_left_column);
1166 $variation->addAttribute('default_right_column', $themeToExport->default_right_column);
1167 $variation->addAttribute('product_per_page', $themeToExport->product_per_page);
1168 $variation->addAttribute('from', Tools::getValue('compa_from'));
1169 $variation->addAttribute('to', Tools::getValue('compa_to'));
1170
1171 $docs = $theme->addChild('docs');
1172 if (isset($this->user_doc)) {
1173 foreach ($this->user_doc as $row) {
1174 $array = explode('¤', $row);
1175 $doc = $docs->addChild('doc');
1176 $doc->addAttribute('name', $array[0]);
1177 $doc->addAttribute('path', $array[1]);
1178 }
1179 }
1180
1181 $metasXml = $theme->addChild('metas');
1182
1183 foreach ($metas as $row) {
1184 $metaObj = new Meta((int) $row['id_meta']);
1185
1186 $metaXml = $metasXml->addChild('meta');
1187 $metaXml->addAttribute('meta_page', $metaObj->page);
1188 $metaXml->addAttribute('left', $row['left_column']);
1189 $metaXml->addAttribute('right', $row['right_column']);
1190 }
1191 $modules = $theme->addChild('modules');
1192 if (isset($this->to_export)) {
1193 foreach ($this->to_export as $row) {
1194 if (!in_array($row, $this->native_modules)) {
1195 $module = $modules->addChild('module');
1196 $module->addAttribute('action', 'install');
1197 $module->addAttribute('name', $row);
1198 }
1199 }
1200 }
1201 foreach ($this->to_enable as $row) {
1202 $module = $modules->addChild('module');
1203 $module->addAttribute('action', 'enable');
1204 $module->addAttribute('name', $row);
1205 }
1206 foreach ($this->to_disable as $row) {
1207 $module = $modules->addChild('module');
1208 $module->addAttribute('action', 'disable');
1209 $module->addAttribute('name', $row);
1210 }
1211
1212 $hooks = $modules->addChild('hooks');
1213 foreach ($this->to_hook as $row) {
1214 $array = explode(';', $row);
1215 $hook = $hooks->addChild('hook');
1216 $hook->addAttribute('module', $array[0]);
1217 $hook->addAttribute('hook', $array[1]);
1218 $hook->addAttribute('position', $array[2]);
1219 if (!empty($array[3])) {
1220 $hook->addAttribute('exceptions', $array[3]);
1221 }
1222 }
1223
1224 $images = $theme->addChild('images');
1225 foreach ($this->image_list as $row) {
1226 $array = explode(';', $row);
1227 $image = $images->addChild('image');
1228 $image->addAttribute('name', Tools::htmlentitiesUTF8($array[0]));
1229 $image->addAttribute('width', $array[1]);
1230 $image->addAttribute('height', $array[2]);
1231 $image->addAttribute('products', $array[3]);
1232 $image->addAttribute('categories', $array[4]);
1233 $image->addAttribute('manufacturers', $array[5]);
1234 $image->addAttribute('suppliers', $array[6]);
1235 $image->addAttribute('scenes', $array[7]);
1236 }
1237 $this->xml_file = $theme->asXML();
1238 }
1239
1240 /**
1241 * Generate archive
1242 *
1243 * @return void
1244 *
1245 * @since 1.0.0
1246 */
1247 protected function generateArchive()
1248 {
1249 $zip = new ZipArchive();
1250 $zipFileName = md5(time()).'.zip';
1251 if ($zip->open(_PS_CACHE_DIR_.$zipFileName, ZipArchive::OVERWRITE | ZipArchive::CREATE) === true) {
1252 if (!$zip->addFromString('Config.xml', $this->xml_file)) {
1253 $this->errors[] = $this->l('Cannot create config file.');
1254 }
1255
1256 if (isset($_FILES['documentation'])) {
1257 if (!empty($_FILES['documentation']['tmp_name']) &&
1258 !empty($_FILES['documentation']['name']) &&
1259 !$zip->addFile($_FILES['documentation']['tmp_name'], 'doc/'.$_FILES['documentation']['name'])
1260 ) {
1261 $this->errors[] = $this->l('Cannot copy documentation.');
1262 }
1263 }
1264
1265 $givenPath = realpath(_PS_ALL_THEMES_DIR_.Tools::getValue('theme_directory'));
1266
1267 if ($givenPath !== false) {
1268 $psAllThemeDirLenght = strlen(realpath(_PS_ALL_THEMES_DIR_));
1269 $toComparePath = substr($givenPath, 0, $psAllThemeDirLenght);
1270 if ($toComparePath != realpath(_PS_ALL_THEMES_DIR_)) {
1271 $this->errors[] = $this->l('Wrong theme directory path');
1272 } else {
1273 $this->archiveThisFile($zip, Tools::getValue('theme_directory'), _PS_ALL_THEMES_DIR_, 'themes/');
1274 foreach ($this->to_export as $row) {
1275 if (!in_array($row, $this->native_modules)) {
1276 $this->archiveThisFile($zip, $row, _PS_ROOT_DIR_.'/modules/', 'modules/');
1277 }
1278 }
1279 }
1280 } else {
1281 $this->errors[] = $this->l('Wrong theme directory path');
1282 }
1283
1284 $zip->close();
1285
1286 if (!is_file(_PS_CACHE_DIR_.$zipFileName)) {
1287 $this->errors[] = $this->l(sprintf('Could not create %1s', _PS_CACHE_DIR_.$zipFileName));
1288 }
1289
1290 if (!$this->errors) {
1291 if (ob_get_length() > 0) {
1292 ob_end_clean();
1293 }
1294
1295 ob_start();
1296 header('Pragma: public');
1297 header('Expires: 0');
1298 header('Cache-Control: must-revalidate, post-check=0, pre-check=0');
1299 header('Cache-Control: public');
1300 header('Content-Description: File Transfer');
1301 header('Content-type: application/octet-stream');
1302 header('Content-Disposition: attachment; filename="'.$zipFileName.'"');
1303 header('Content-Transfer-Encoding: binary');
1304 ob_end_flush();
1305 readfile(_PS_CACHE_DIR_.$zipFileName);
1306 @unlink(_PS_CACHE_DIR_.$zipFileName);
1307 exit;
1308 }
1309 }
1310
1311 $this->errors[] = $this->l('An error occurred during the archive generation');
1312 }
1313
1314 /**
1315 * @param ZipArchive $obj
1316 * @param string $file
1317 * @param string $serverPath
1318 * @param string $archivePath
1319 *
1320 * @since 1.0.0
1321 */
1322 protected function archiveThisFile($obj, $file, $serverPath, $archivePath)
1323 {
1324 if (is_dir($serverPath.$file)) {
1325 $dir = scandir($serverPath.$file);
1326 foreach ($dir as $row) {
1327 if ($row[0] != '.') {
1328 $this->archiveThisFile($obj, $row, $serverPath.$file.'/', $archivePath.$file.'/');
1329 }
1330 }
1331 } elseif (!$obj->addFile($serverPath.$file, $archivePath.$file)) {
1332 $this->error = true;
1333 }
1334 }
1335
1336 /**
1337 * Render theme export
1338 *
1339 * @return string
1340 *
1341 * @throws Adapter_Exception
1342 * @throws Exception
1343 * @throws PrestaShopDatabaseException
1344 * @throws PrestaShopException
1345 * @throws SmartyException
1346 * @since 1.0.0
1347 */
1348 public function renderExportTheme()
1349 {
1350 if (Tools::getIsset('id_theme_export') && (int) Tools::getValue('id_theme_export') > 0) {
1351 return $this->renderExportTheme1();
1352 }
1353
1354 $themeList = Theme::getThemes();
1355 $fieldsForm = [
1356 'form' => [
1357 'tinymce' => false,
1358 'legend' => [
1359 'title' => $this->l('Theme'),
1360 'icon' => 'icon-picture',
1361 ],
1362 'input' => [
1363 [
1364 'type' => 'select',
1365 'name' => 'id_theme_export',
1366 'label' => $this->l('Choose the theme that you want to export'),
1367 'options' => [
1368 'id' => 'id',
1369 'name' => 'name',
1370 'query' => $themeList,
1371 ],
1372
1373 ],
1374 ],
1375 'submit' => [
1376 'title' => $this->l('Save'),
1377 ],
1378 ],
1379 ];
1380
1381 $toolbarBtn['save'] = [
1382 'href' => '#',
1383 'desc' => $this->l('Export'),
1384 ];
1385
1386 $fieldsValue['id_theme_export'] = [];
1387 $helper = new HelperForm();
1388
1389 $helper->currentIndex = $this->context->link->getAdminLink('AdminThemes', false).'&action=exporttheme';
1390 $helper->token = Tools::getAdminTokenLite('AdminThemes');
1391 $helper->show_toolbar = true;
1392 $helper->fields_value = $fieldsValue;
1393 $helper->toolbar_btn = $toolbarBtn;
1394 $helper->override_folder = $this->tpl_folder;
1395
1396 return $helper->generateForm([$fieldsForm]);
1397 }
1398
1399 /**
1400 * Render export theme 1
1401 *
1402 * @return string
1403 *
1404 * @throws Adapter_Exception
1405 * @throws Exception
1406 * @throws PrestaShopDatabaseException
1407 * @throws PrestaShopException
1408 * @throws SmartyException
1409 * @since 1.0.0
1410 */
1411 protected function renderExportTheme1()
1412 {
1413 $toInstall = [];
1414
1415 $moduleList = Db::getInstance()->executeS(
1416 '
1417 SELECT m.`id_module`, m.`name`, m.`active`, ms.`id_shop`
1418 FROM `'._DB_PREFIX_.'module` m
1419 LEFT JOIN `'._DB_PREFIX_.'module_shop` ms On (m.`id_module` = ms.`id_module`)
1420 WHERE ms.`id_shop` = '.(int) $this->context->shop->id.'
1421 '
1422 );
1423
1424 // Select the list of hook for this shop
1425 $hookList = Db::getInstance()->executeS(
1426 '
1427 SELECT h.`id_hook`, h.`name` as name_hook, hm.`position`, hm.`id_module`, m.`name` as name_module, GROUP_CONCAT(hme.`file_name`, ",") as exceptions
1428 FROM `'._DB_PREFIX_.'hook` h
1429 LEFT JOIN `'._DB_PREFIX_.'hook_module` hm ON hm.`id_hook` = h.`id_hook`
1430 LEFT JOIN `'._DB_PREFIX_.'module` m ON hm.`id_module` = m.`id_module`
1431 LEFT OUTER JOIN `'._DB_PREFIX_.'hook_module_exceptions` hme ON (hme.`id_module` = hm.`id_module` AND hme.`id_hook` = h.`id_hook`)
1432 WHERE hm.`id_shop` = '.(int) $this->context->shop->id.'
1433 GROUP BY `id_module`, `id_hook`
1434 ORDER BY `name_module`
1435 '
1436 );
1437
1438 foreach ($hookList as &$row) {
1439 $row['exceptions'] = trim(preg_replace('/(,,+)/', ',', $row['exceptions']), ',');
1440 }
1441
1442 $nativeModules = $this->getNativeModule();
1443
1444 foreach ($moduleList as $array) {
1445 if (!static::checkParentClass($array['name'])) {
1446 continue;
1447 }
1448 if (in_array($array['name'], $nativeModules)) {
1449 if ($array['active'] == 1) {
1450 $toEnable[] = $array['name'];
1451 } else {
1452 $toDisable[] = $array['name'];
1453 }
1454 } elseif ($array['active'] == 1) {
1455 $toInstall[] = $array['name'];
1456 }
1457 }
1458 foreach ($nativeModules as $str) {
1459 $flag = 0;
1460 if (!$this->checkParentClass($str)) {
1461 continue;
1462 }
1463 foreach ($moduleList as $tmp) {
1464 if (in_array($str, $tmp)) {
1465 $flag = 1;
1466 break;
1467 }
1468 }
1469 if ($flag == 0) {
1470 $toDisable[] = $str;
1471 }
1472 }
1473
1474 $employee = $this->context->employee;
1475 $mail = Tools::getValue('email') ? Tools::getValue('email') : $employee->email;
1476 $author = Tools::getValue('author_name') ? Tools::getValue('author_name') : $employee->firstname.' '.$employee->lastname;
1477 $website = Tools::getValue('website') ? Tools::getValue('website') : Tools::getHttpHost(true);
1478
1479 $this->formatHelperArray($toInstall);
1480
1481 $theme = new Theme(Tools::getValue('id_theme_export'));
1482
1483 $fieldsForm = [
1484 'form' => [
1485 'tinymce' => false,
1486 'legend' => [
1487 'title' => $this->l('Theme configuration'),
1488 'icon' => 'icon-picture',
1489 ],
1490 'input' => [
1491 [
1492 'type' => 'hidden',
1493 'name' => 'id_theme_export',
1494 ],
1495 [
1496 'type' => 'text',
1497 'name' => 'name',
1498 'label' => $this->l('Name'),
1499 ],
1500 [
1501 'type' => 'text',
1502 'name' => 'email',
1503 'label' => $this->l('Email'),
1504 ],
1505 [
1506 'type' => 'text',
1507 'name' => 'website',
1508 'label' => $this->l('Website'),
1509 ],
1510 [
1511 'type' => 'text',
1512 'name' => 'theme_name',
1513 'label' => $this->l('Theme name'),
1514 ],
1515 [
1516 'type' => 'text',
1517 'name' => 'theme_directory',
1518 'label' => $this->l('Theme directory'),
1519 ],
1520 [
1521 'type' => 'text',
1522 'name' => 'body_title',
1523 'lang' => true,
1524 'label' => $this->l('Description'),
1525 ],
1526 [
1527 'type' => 'text',
1528 'name' => 'theme_version',
1529 'label' => $this->l('Theme version'),
1530 ],
1531 [
1532 'type' => 'text',
1533 'name' => 'compa_from',
1534 'label' => $this->l('Compatible from'),
1535 ],
1536 [
1537 'type' => 'text',
1538 'name' => 'compa_to',
1539 'label' => $this->l('Compatible to'),
1540 ],
1541 [
1542 'type' => 'file',
1543 'name' => 'documentation',
1544 'label' => $this->l('Documentation'),
1545 ],
1546 [
1547 'type' => 'text',
1548 'name' => 'documentationName',
1549 'label' => $this->l('Documentation name'),
1550 ],
1551 ],
1552 'submit' => [
1553 'title' => $this->l('Save'),
1554 ],
1555 ],
1556 ];
1557
1558 if (count($toInstall) > 0) {
1559 foreach ($toInstall as $module) {
1560 $fieldsValue['modulesToExport_module'.$module] = true;
1561 }
1562
1563 $fieldsForm['form']['input'][] = [
1564 'type' => 'checkbox',
1565 'label' => $this->l('Select the theme\'s modules that you wish to export'),
1566 'values' => [
1567 'query' => $this->formatHelperArray($toInstall),
1568 'id' => 'id',
1569 'name' => 'name',
1570 ],
1571 'name' => 'modulesToExport',
1572 ];
1573 }
1574
1575 $defaultLanguage = (int) $this->context->language->id;
1576 $languages = $this->getLanguages();
1577
1578 foreach ($languages as $language) {
1579 $fieldsValue['body_title'][$language['id_lang']] = '';
1580 }
1581
1582 $helper = new HelperForm();
1583 $helper->languages = $languages;
1584 $helper->default_form_language = $defaultLanguage;
1585 $fieldsValue['name'] = $author;
1586 $fieldsValue['email'] = $mail;
1587 $fieldsValue['website'] = $website;
1588 $fieldsValue['theme_name'] = $theme->name;
1589 $fieldsValue['theme_directory'] = $theme->directory;
1590 $fieldsValue['theme_version'] = '1.0';
1591 $fieldsValue['compa_from'] = _PS_VERSION_;
1592 $fieldsValue['compa_to'] = _PS_VERSION_;
1593 $fieldsValue['id_theme_export'] = Tools::getValue('id_theme_export');
1594 $fieldsValue['documentationName'] = $this->l('documentation');
1595
1596 $toolbarBtn['save'] = [
1597 'href' => '',
1598 'desc' => $this->l('Save'),
1599 ];
1600
1601 $helper->currentIndex = $this->context->link->getAdminLink('AdminThemes', false).'&action=exporttheme';
1602 $helper->token = Tools::getAdminTokenLite('AdminThemes');
1603 $helper->show_toolbar = true;
1604 $helper->fields_value = $fieldsValue;
1605 $helper->toolbar_btn = $toolbarBtn;
1606 $helper->override_folder = $this->tpl_folder;
1607
1608 return $helper->generateForm([$fieldsForm]);
1609 }
1610
1611 /**
1612 * Format helper array
1613 *
1614 * @param array $originArr
1615 *
1616 * @return array
1617 *
1618 * @throws Adapter_Exception
1619 * @throws PrestaShopDatabaseException
1620 * @throws PrestaShopException
1621 * @since 1.0.0
1622 */
1623 protected function formatHelperArray($originArr)
1624 {
1625 $formatArray = [];
1626 foreach ($originArr as $module) {
1627 $displayName = $module;
1628
1629 $moduleObj = Module::getInstanceByName($module);
1630 if (Validate::isLoadedObject($moduleObj)) {
1631 $displayName = $moduleObj->displayName;
1632 }
1633
1634 $tmp = [];
1635 $tmp['id'] = 'module'.$module;
1636 $tmp['val'] = $module;
1637 $tmp['name'] = $displayName;
1638 $formatArray[] = $tmp;
1639 }
1640
1641 return $formatArray;
1642 }
1643
1644 /**
1645 * Process import theme
1646 *
1647 * @return bool
1648 *
1649 * @since 1.0.0
1650 * @throws PrestaShopException
1651 */
1652 public function processImportTheme()
1653 {
1654 if (!($this->tabAccess['add'] && $this->tabAccess['delete']) || _PS_MODE_DEMO_) {
1655 $this->errors[] = Tools::displayError('You do not have permission to add here.');
1656
1657 return false;
1658 } else {
1659 $this->display = 'importtheme';
1660 if ($this->context->mode == Context::MODE_HOST) {
1661 return true;
1662 }
1663 if ($_SERVER['REQUEST_METHOD'] == 'POST' && isset($_FILES['themearchive']) && isset($_POST['filename']) && Tools::isSubmit('theme_archive_server')) {
1664 $uniqid = uniqid();
1665 $sandbox = _PS_CACHE_DIR_.'sandbox'.DIRECTORY_SEPARATOR.$uniqid.DIRECTORY_SEPARATOR;
1666 mkdir($sandbox, 0777, true);
1667 $archiveUploaded = false;
1668 if (Tools::getValue('filename') != '') {
1669 $uploader = new Uploader('themearchive');
1670 $uploader->setCheckFileSize(false);
1671 $uploader->setAcceptTypes(array('zip'));
1672 $uploader->setSavePath($sandbox);
1673 $file = $uploader->process(Theme::UPLOADED_THEME_DIR_NAME.'.zip');
1674 if ($file[0]['error'] === 0) {
1675 if (Tools::ZipTest($sandbox.Theme::UPLOADED_THEME_DIR_NAME.'.zip')) {
1676 $archiveUploaded = true;
1677 } else {
1678 $this->errors[] = $this->l('Zip file seems to be broken');
1679 }
1680 } else {
1681 $this->errors[] = $file[0]['error'];
1682 }
1683 } elseif (Tools::getValue('themearchiveUrl') != '') {
1684 if (!Validate::isModuleUrl($url = Tools::getValue('themearchiveUrl'), $this->errors)) {
1685 $this->errors[] = $this->l('Only zip files are allowed');
1686 } elseif (!Tools::copy($url, $sandbox.Theme::UPLOADED_THEME_DIR_NAME.'.zip')) {
1687 $this->errors[] = $this->l('Error during the file download');
1688 } elseif (Tools::ZipTest($sandbox.Theme::UPLOADED_THEME_DIR_NAME.'.zip')) {
1689 $archiveUploaded = true;
1690 } else {
1691 $this->errors[] = $this->l('Zip file seems to be broken');
1692 }
1693 } elseif (Tools::getValue('theme_archive_server') != '') {
1694 $filename = _PS_ALL_THEMES_DIR_.Tools::getValue('theme_archive_server');
1695 if (substr($filename, -4) != '.zip') {
1696 $this->errors[] = $this->l('Only zip files are allowed');
1697 } elseif (!copy($filename, $sandbox.Theme::UPLOADED_THEME_DIR_NAME.'.zip')) {
1698 $this->errors[] = $this->l('An error has occurred during the file copy.');
1699 } elseif (Tools::ZipTest($sandbox.Theme::UPLOADED_THEME_DIR_NAME.'.zip')) {
1700 $archiveUploaded = true;
1701 } else {
1702 $this->errors[] = $this->l('Zip file seems to be broken');
1703 }
1704 } else {
1705 $this->errors[] = $this->l('You must upload or enter a location of your zip');
1706 }
1707 if ($archiveUploaded) {
1708 if ($this->extractTheme($sandbox.Theme::UPLOADED_THEME_DIR_NAME.'.zip', $sandbox)) {
1709 $this->installTheme(Theme::UPLOADED_THEME_DIR_NAME, $sandbox);
1710 }
1711 }
1712 Tools::deleteDirectory($sandbox);
1713 if (count($this->errors) > 0) {
1714 $this->display = 'importtheme';
1715 } else {
1716 Tools::redirectAdmin($this->context->link->getAdminLink('AdminThemes').'&conf=18');
1717 }
1718 } elseif ($_SERVER['REQUEST_METHOD'] == 'POST') {
1719 //method is POST but no uplad info -> there is post error
1720 $maxPost = (int)ini_get('post_max_size');
1721 $this->errors[] = sprintf($this->l('The file size exceeds the size allowed by the server. The limit is set to %s MB.'), '<b>'.$maxPost.'</b>');
1722 }
1723 }
1724 }
1725
1726 /**
1727 * Extract theme
1728 *
1729 * @param $themeZipFile
1730 * @param $sandbox
1731 *
1732 * @return bool
1733 *
1734 * @since 1.0.0
1735 */
1736 protected function extractTheme($themeZipFile, $sandbox)
1737 {
1738 if (!($this->tabAccess['add'] && $this->tabAccess['edit'] && $this->tabAccess['delete']) || _PS_MODE_DEMO_) {
1739 $this->errors[] = $this->l('You do not have permission to extract here.');
1740
1741 return false;
1742 }
1743
1744 if (Tools::ZipExtract($themeZipFile, $sandbox.Theme::UPLOADED_THEME_DIR_NAME.'/')) {
1745 return true;
1746 }
1747 $this->errors[] = $this->l('Error during zip extraction');
1748
1749 return false;
1750 }
1751
1752 /**
1753 * Install theme
1754 *
1755 * @param string $themeDir
1756 * @param bool $sandbox
1757 * @param bool $redirect
1758 *
1759 * @return bool
1760 *
1761 * @throws PrestaShopDatabaseException
1762 * @throws PrestaShopException
1763 * @since 1.0.0
1764 */
1765 protected function installTheme($themeDir, $sandbox = false, $redirect = true)
1766 {
1767 if ($this->tabAccess['add'] && $this->tabAccess['delete'] && !_PS_MODE_DEMO_) {
1768 if (!$sandbox) {
1769 $uniqid = uniqid();
1770 $sandbox = _PS_CACHE_DIR_.'sandbox'.DIRECTORY_SEPARATOR.$uniqid.DIRECTORY_SEPARATOR;
1771 mkdir($sandbox);
1772 Tools::recurseCopy(_PS_ALL_THEMES_DIR_.$themeDir, $sandbox.$themeDir);
1773 }
1774 $xmlFile = $sandbox.$themeDir.'/config.xml';
1775 if (!$this->checkXmlFields($xmlFile)) {
1776 $this->errors[] = $this->l('Bad configuration file');
1777 } else {
1778 $importedTheme = $this->importThemeXmlConfig(simplexml_load_file($xmlFile));
1779 foreach ($importedTheme as $theme) {
1780 if (Validate::isLoadedObject($theme)) {
1781 if (!copy($sandbox.$themeDir.'/Config.xml', _PS_ROOT_DIR_.'/config/xml/themes/'.$theme->directory.'.xml')) {
1782 $this->errors[] = $this->l('Can\'t copy configuration file');
1783 }
1784 $targetDir = _PS_ALL_THEMES_DIR_.$theme->directory;
1785 if (file_exists($targetDir)) {
1786 Tools::deleteDirectory($targetDir);
1787 }
1788 $themeDocDir = $targetDir.'/docs/';
1789 if (file_exists($themeDocDir)) {
1790 Tools::deleteDirectory($themeDocDir);
1791 }
1792 mkdir($targetDir);
1793 mkdir($themeDocDir);
1794 Tools::recurseCopy($sandbox.$themeDir.'/themes/'.$theme->directory.'/', $targetDir.'/');
1795 Tools::recurseCopy($sandbox.$themeDir.'/doc/', $themeDocDir);
1796 Tools::recurseCopy($sandbox.$themeDir.'/modules/', _PS_MODULE_DIR_);
1797 } else {
1798 $this->errors[] = $theme;
1799 }
1800 }
1801 }
1802 Tools::deleteDirectory($sandbox);
1803 }
1804 if (!count($this->errors)) {
1805 if ($redirect) {
1806 Tools::redirectAdmin($this->context->link->getAdminLink('AdminThemes').'&conf=18');
1807 } else {
1808 return true;
1809 }
1810 } else {
1811 return false;
1812 }
1813 }
1814
1815 /**
1816 * Check XML fields
1817 *
1818 * @param string $xmlFile
1819 *
1820 * @return bool
1821 *
1822 * @since 1.0.0
1823 */
1824 protected function checkXmlFields($xmlFile)
1825 {
1826 if (!file_exists($xmlFile) || !$xml = @simplexml_load_file($xmlFile)) {
1827 return false;
1828 }
1829 if (!$xml['version'] || !$xml['name']) {
1830 return false;
1831 }
1832 foreach ($xml->variations->variation as $val) {
1833 if (!$val['name'] || !$val['directory'] || !$val['from'] || !$val['to']) {
1834 return false;
1835 }
1836 }
1837 foreach ($xml->modules->module as $val) {
1838 if (!$val['action'] || !$val['name']) {
1839 return false;
1840 }
1841 }
1842 foreach ($xml->modules->hooks->hook as $val) {
1843 if (!$val['module'] || !$val['hook'] || !$val['position']) {
1844 return false;
1845 }
1846 }
1847
1848 return true;
1849 }
1850
1851 /**
1852 * @param SimpleXMLElement $xml
1853 * @param bool $themeDir only used if the theme directory to import is already located on the shop
1854 *
1855 * @return array|string return array of themes on success, otherwise the error as a string is returned
1856 *
1857 * @throws PrestaShopDatabaseException
1858 * @throws PrestaShopException
1859 * @since 1.0.0
1860 */
1861 protected function importThemeXmlConfig(SimpleXMLElement $xml, $themeDir = false)
1862 {
1863 $attr = $xml->attributes();
1864 $thName = (string) $attr->name;
1865 if ($this->isThemeInstalled($thName)) {
1866 return [sprintf($this->l('Theme %s already installed.'), $thName)];
1867 }
1868
1869 $newThemeArray = [];
1870 foreach ($xml->variations->variation as $variation) {
1871 $name = strval($variation['name']);
1872
1873 $newTheme = new Theme();
1874 $newTheme->name = $name;
1875
1876 $newTheme->directory = strval($variation['directory']);
1877
1878 if ($themeDir) {
1879 $newTheme->name = $themeDir;
1880 $newTheme->directory = $themeDir;
1881 }
1882
1883 if ($this->isThemeInstalled($newTheme->name)) {
1884 continue;
1885 }
1886
1887 $newTheme->product_per_page = Configuration::get('PS_PRODUCTS_PER_PAGE');
1888
1889 if (isset($variation['product_per_page'])) {
1890 $newTheme->product_per_page = intval($variation['product_per_page']);
1891 }
1892
1893 $newTheme->responsive = false;
1894 if (isset($variation['responsive'])) {
1895 $newTheme->responsive = (bool) strval($variation['responsive']);
1896 }
1897
1898 $newTheme->default_left_column = true;
1899 $newTheme->default_right_column = true;
1900
1901 if (isset($variation['default_left_column'])) {
1902 $newTheme->default_left_column = (bool) strval($variation['default_left_column']);
1903 }
1904
1905 if (isset($variation['default_right_column'])) {
1906 $newTheme->default_right_column = (bool) strval($variation['default_right_column']);
1907 }
1908
1909 $fillDefaultMeta = true;
1910 $metasXml = [];
1911 if ($xml->metas->meta) {
1912 foreach ($xml->metas->meta as $meta) {
1913 $metaId = Db::getInstance(_PS_USE_SQL_SLAVE_)->getValue(
1914 (new DbQuery())
1915 ->select('`id_meta`')
1916 ->from('meta')
1917 ->where('`page` = \''.pSQL($meta['meta_page']).'\'')
1918 );
1919 if ((int) $metaId > 0) {
1920 $tmpMeta = [];
1921 $tmpMeta['id_meta'] = (int) $metaId;
1922 $tmpMeta['left'] = intval($meta['left']);
1923 $tmpMeta['right'] = intval($meta['right']);
1924 $metasXml[(int) $metaId] = $tmpMeta;
1925 }
1926 }
1927 $fillDefaultMeta = false;
1928 if (count($xml->metas->meta) < (int) Db::getInstance(_PS_USE_SQL_SLAVE_)->getValue(
1929 (new DbQuery())
1930 ->select('COUNT(*)')
1931 ->from('meta')
1932 )) {
1933 $fillDefaultMeta = true;
1934 }
1935 }
1936
1937 if ($fillDefaultMeta == true) {
1938 $metas = Db::getInstance(_PS_USE_SQL_SLAVE_)->executeS(
1939 (new DbQuery())
1940 ->select('`id_meta`')
1941 ->from('meta')
1942 );
1943 foreach ($metas as $meta) {
1944 if (!isset($metasXml[(int) $meta['id_meta']])) {
1945 $tmpMeta['id_meta'] = (int) $meta['id_meta'];
1946 $tmpMeta['left'] = $newTheme->default_left_column;
1947 $tmpMeta['right'] = $newTheme->default_right_column;
1948 $metasXml[(int) $meta['id_meta']] = $tmpMeta;
1949 }
1950 }
1951 }
1952
1953 if (!is_dir(_PS_ALL_THEMES_DIR_.$newTheme->directory)) {
1954 if (!mkdir(_PS_ALL_THEMES_DIR_.$newTheme->directory)) {
1955 return sprintf($this->l('Error while creating %s directory'), _PS_ALL_THEMES_DIR_.$newTheme->directory);
1956 }
1957 }
1958
1959 $newTheme->add();
1960
1961 if ($newTheme->id > 0) {
1962 $newTheme->updateMetas($metasXml);
1963 $newThemeArray[] = $newTheme;
1964 } else {
1965 $newThemeArray[] = sprintf($this->l('Error while installing theme %s'), $newTheme->name);
1966 }
1967 }
1968
1969 return $newThemeArray;
1970 }
1971
1972 /**
1973 * Check if theme is installed
1974 *
1975 * @param string $themeName
1976 *
1977 * @return bool
1978 *
1979 * @since 1.0.0
1980 * @throws PrestaShopException
1981 */
1982 protected function isThemeInstalled($themeName)
1983 {
1984 $themes = Theme::getThemes();
1985
1986 foreach ($themes as $themeObject) {
1987 /** @var Theme $themeObject */
1988 if ($themeObject->name == $themeName) {
1989 return true;
1990 }
1991 }
1992
1993 return false;
1994 }
1995
1996 /**
1997 * Render theme import
1998 *
1999 * @return string
2000 *
2001 * @throws Exception
2002 * @throws PrestaShopDatabaseException
2003 * @throws PrestaShopException
2004 * @throws SmartyException
2005 * @since 1.0.0
2006 */
2007 public function renderImportTheme()
2008 {
2009 $fieldsForm = [];
2010
2011 $toolbarBtn['save'] = [
2012 'href' => '#',
2013 'desc' => $this->l('Save'),
2014 ];
2015
2016 if ($this->context->mode != Context::MODE_HOST) {
2017 $fieldsForm[0] = [
2018 'form' => [
2019 'tinymce' => false,
2020 'legend' => [
2021 'title' => $this->l('Import from your computer'),
2022 'icon' => 'icon-picture',
2023 ],
2024 'input' => [
2025 [
2026 'type' => 'file',
2027 'label' => $this->l('Zip file'),
2028 'desc' => $this->l('Browse your computer files and select the Zip file for your new theme.'),
2029 'name' => 'themearchive',
2030 ],
2031 ],
2032 'submit' => [
2033 'id' => 'zip',
2034 'title' => $this->l('Save'),
2035 ],
2036 ],
2037 ];
2038
2039 $fieldsForm[1] = [
2040 'form' => [
2041 'tinymce' => false,
2042 'legend' => [
2043 'title' => $this->l('Import from the web'),
2044 'icon' => 'icon-picture',
2045 ],
2046 'input' => [
2047 [
2048 'type' => 'text',
2049 'label' => $this->l('Archive URL'),
2050 'desc' => $this->l('Indicate the complete URL to an online Zip file that contains your new theme. For instance, "http://example.com/files/theme.zip".'),
2051 'name' => 'themearchiveUrl',
2052 ],
2053 ],
2054 'submit' => [
2055 'title' => $this->l('Save'),
2056 ],
2057 ],
2058 ];
2059
2060 $themeArchiveServer = [];
2061 $files = scandir(_PS_ALL_THEMES_DIR_);
2062 $themeArchiveServer[] = '-';
2063
2064 foreach ($files as $file) {
2065 if (is_file(_PS_ALL_THEMES_DIR_.$file) && substr(_PS_ALL_THEMES_DIR_.$file, -4) == '.zip') {
2066 $themeArchiveServer[] = [
2067 'id' => basename(_PS_ALL_THEMES_DIR_.$file),
2068 'name' => basename(_PS_ALL_THEMES_DIR_.$file),
2069 ];
2070 }
2071 }
2072
2073 $fieldsForm[2] = [
2074 'form' => [
2075 'tinymce' => false,
2076 'legend' => [
2077 'title' => $this->l('Import from FTP'),
2078 'icon' => 'icon-picture',
2079 ],
2080 'input' => [
2081 [
2082 'type' => 'select',
2083 'label' => $this->l('Select the archive'),
2084 'name' => 'theme_archive_server',
2085 'desc' => $this->l('This selector lists the Zip files that you uploaded in the \'/themes\' folder.'),
2086 'options' => [
2087 'id' => 'id',
2088 'name' => 'name',
2089 'query' => $themeArchiveServer,
2090 ],
2091 ],
2092 ],
2093 'submit' => [
2094 'title' => $this->l('Save'),
2095 ],
2096 ],
2097 ];
2098 }
2099
2100 $this->context->smarty->assign(
2101 [
2102 'import_theme' => true,
2103 'logged_on_addons' => false,
2104 'iso_code' => $this->context->language->iso_code,
2105 'add_new_theme_href' => static::$currentIndex.'&addtheme&token='.$this->token,
2106 'add_new_theme_label' => $this->l('Create a new theme'),
2107 ]
2108 );
2109
2110 $createNewThemePanel = $this->context->smarty->fetch('controllers/themes/helpers/view/importtheme_view.tpl');
2111
2112 $helper = new HelperForm();
2113
2114 $helper->currentIndex = $this->context->link->getAdminLink('AdminThemes', false).'&action=importtheme';
2115 $helper->token = Tools::getAdminTokenLite('AdminThemes');
2116 $helper->show_toolbar = true;
2117 $helper->toolbar_btn = $toolbarBtn;
2118 $helper->fields_value['themearchiveUrl'] = '';
2119 $helper->fields_value['theme_archive_server'] = [];
2120 $helper->multiple_fieldsets = true;
2121 $helper->override_folder = $this->tpl_folder;
2122 $helper->languages = $this->getLanguages();
2123 $helper->default_form_language = (int) $this->context->language->id;
2124
2125 return $helper->generateForm($fieldsForm).$createNewThemePanel;
2126 }
2127
2128 /**
2129 * Initialize content
2130 *
2131 * @return void
2132 *
2133 * @throws Exception
2134 * @throws PrestaShopException
2135 * @throws SmartyException
2136 * @since 1.0.0
2137 */
2138 public function initContent()
2139 {
2140 if ($this->display == 'list') {
2141 $this->display = '';
2142 }
2143 if (isset($this->display) && method_exists($this, 'render'.$this->display)) {
2144 $this->content .= $this->initPageHeaderToolbar();
2145
2146 $this->content .= $this->{'render'.$this->display}();
2147 $this->context->smarty->assign(
2148 [
2149 'content' => $this->content,
2150 'show_page_header_toolbar' => $this->show_page_header_toolbar,
2151 'page_header_toolbar_title' => $this->page_header_toolbar_title,
2152 'page_header_toolbar_btn' => $this->page_header_toolbar_btn,
2153 ]
2154 );
2155 } else {
2156 $content = '';
2157 if (Configuration::hasKey('PS_LOGO') && trim(Configuration::get('PS_LOGO')) != ''
2158 && file_exists(_PS_IMG_DIR_.Configuration::get('PS_LOGO')) && filesize(_PS_IMG_DIR_.Configuration::get('PS_LOGO'))
2159 ) {
2160 list($width, $height, $type, $attr) = getimagesize(_PS_IMG_DIR_.Configuration::get('PS_LOGO'));
2161 Configuration::updateValue('SHOP_LOGO_HEIGHT', (int) round($height));
2162 Configuration::updateValue('SHOP_LOGO_WIDTH', (int) round($width));
2163 }
2164
2165 $this->content .= $content;
2166
2167 parent::initContent();
2168 }
2169 }
2170
2171 /**
2172 * Initialize page header toolbar
2173 *
2174 * @return void
2175 *
2176 * @since 1.0.0
2177 * @throws PrestaShopException
2178 */
2179 public function initPageHeaderToolbar()
2180 {
2181 parent::initPageHeaderToolbar();
2182
2183 if (empty($this->display)) {
2184 $this->page_header_toolbar_btn['import_theme'] = [
2185 'href' => static::$currentIndex.'&action=importtheme&token='.$this->token,
2186 'desc' => $this->l('Add new theme', null, null, false),
2187 'icon' => 'process-icon-new',
2188 ];
2189
2190 if ($this->context->mode) {
2191 unset($this->toolbar_btn['new']);
2192 }
2193
2194 $this->page_header_toolbar_btn['export_theme'] = [
2195 'href' => static::$currentIndex.'&action=exporttheme&token='.$this->token,
2196 'desc' => $this->l('Export theme', null, null, false),
2197 'icon' => 'process-icon-export',
2198 ];
2199 }
2200
2201 if ($this->display == 'importtheme') {
2202 $this->toolbar_title[] = $this->l('Import theme');
2203 } elseif ($this->display == 'exporttheme') {
2204 $this->toolbar_title[] = $this->l('Export theme');
2205 } else {
2206 $this->toolbar_title[] = $this->l('Theme');
2207 }
2208
2209 $title = implode(' '.Configuration::get('PS_NAVIGATION_PIPE').' ', $this->toolbar_title);
2210 $this->page_header_toolbar_title = $title;
2211 }
2212
2213 /**
2214 * @since 1.0.0
2215 */
2216 public function ajaxProcessGetAddonsThemes()
2217 {
2218 exit;
2219 }
2220
2221 /**
2222 * Render choose theme modules
2223 *
2224 * @return string
2225 *
2226 * @throws Adapter_Exception
2227 * @throws Exception
2228 * @throws PrestaShopDatabaseException
2229 * @throws PrestaShopException
2230 * @throws SmartyException
2231 * @since 1.0.0
2232 */
2233 public function renderChooseThemeModule()
2234 {
2235 $theme = new Theme((int) Tools::getValue('id_theme'));
2236
2237 $xml = false;
2238 if (file_exists(_PS_ROOT_DIR_.'/config/xml/themes/'.$theme->directory.'.xml')) {
2239 $xml = @simplexml_load_file(_PS_ROOT_DIR_.'/config/xml/themes/'.$theme->directory.'.xml');
2240 } elseif (file_exists(_PS_ROOT_DIR_.'/config/xml/themes/default.xml')) {
2241 $xml = @simplexml_load_file(_PS_ROOT_DIR_.'/config/xml/themes/default.xml');
2242 }
2243
2244 if ($xml) {
2245 $themeModule = $this->getModules($xml);
2246
2247 $toolbarBtn['save'] = [
2248 'href' => '#',
2249 'desc' => $this->l('Save'),
2250 ];
2251
2252 $toInstall = [];
2253 $toEnable = [];
2254 $toDisable = [];
2255
2256 if (isset($themeModule['to_install'])) {
2257 $toInstall = $this->formatHelperArray($themeModule['to_install']);
2258 }
2259 if (isset($themeModule['to_enable'])) {
2260 $toEnable = $this->formatHelperArray($themeModule['to_enable']);
2261 }
2262 if (isset($themeModule['to_disable'])) {
2263 $toDisable = $this->formatHelperArray($themeModule['to_disable']);
2264 }
2265
2266 $fieldsForm = [
2267 'form' => [
2268 'tinymce' => false,
2269 'legend' => [
2270 'title' => $this->l('Modules to install'),
2271 'icon' => 'icon-picture',
2272 ],
2273 'description' => $this->l('Themes often include their own modules in order to work properly. This option enables you to choose which modules should be enabled and which should be disabled. If you are unsure of what to do next, just press the "Save" button and proceed to the next step.'),
2274 'input' => [
2275 [
2276 'type' => 'shop',
2277 'label' => $this->l('Shop association'),
2278 'name' => 'checkBoxShopAsso_theme',
2279 ],
2280 [
2281 'type' => 'hidden',
2282 'name' => 'id_theme',
2283 ],
2284 ],
2285 'submit' => [
2286 'title' => $this->l('Save'),
2287 ],
2288 ],
2289 ];
2290
2291 if (count($toInstall) > 0) {
2292 $fieldsForm['form']['input'][] = [
2293 'type' => 'checkbox',
2294 'label' => $this->l('Select the theme\'s modules you wish to install'),
2295 'values' => [
2296 'query' => $toInstall,
2297 'id' => 'id',
2298 'name' => 'name',
2299 ],
2300 'name' => 'to_install',
2301 'expand' => [
2302 'print_total' => count($toInstall),
2303 'default' => 'show',
2304 'show' => ['text' => $this->l('Show'), 'icon' => 'plus-sign-alt'],
2305 'hide' => ['text' => $this->l('Hide'), 'icon' => 'minus-sign-alt'],
2306 ],
2307 ];
2308 }
2309 if (count($toEnable) > 0) {
2310 $fieldsForm['form']['input'][] = [
2311 'type' => 'checkbox',
2312 'label' => $this->l('Select the theme\'s modules you wish to enable'),
2313 'values' => [
2314 'query' => $toEnable,
2315 'id' => 'id',
2316 'name' => 'name',
2317 ],
2318 'name' => 'to_enable',
2319 'expand' => [
2320 'print_total' => count($toEnable),
2321 'default' => 'show',
2322 'show' => ['text' => $this->l('Show'), 'icon' => 'plus-sign-alt'],
2323 'hide' => ['text' => $this->l('Hide'), 'icon' => 'minus-sign-alt'],
2324 ],
2325 ];
2326 }
2327 if (count($toDisable) > 0) {
2328 $fieldsForm['form']['input'][] = [
2329 'type' => 'checkbox',
2330 'label' => $this->l('Select the theme\'s modules you wish to disable'),
2331 'values' => [
2332 'query' => $toDisable,
2333 'id' => 'id',
2334 'name' => 'name',
2335 ],
2336 'name' => 'to_disable',
2337 'expand' => [
2338 'print_total' => count($toDisable),
2339 'default' => 'show',
2340 'show' => ['text' => $this->l('Show'), 'icon' => 'plus-sign-alt'],
2341 'hide' => ['text' => $this->l('Hide'), 'icon' => 'minus-sign-alt'],
2342 ],
2343 ];
2344 }
2345 $shops = [];
2346 $shop = new Shop(Configuration::get('PS_SHOP_DEFAULT'));
2347 $tmp['id_shop'] = $shop->id;
2348 $tmp['id_theme'] = $shop->id_theme;
2349 $shops[] = $tmp;
2350
2351 if (Shop::isFeatureActive()) {
2352 $shops = Shop::getShops();
2353 }
2354
2355 $currentShop = $this->context->shop->id;
2356
2357 foreach ($shops as $shop) {
2358 $shopTheme = new Theme((int) $shop['id_theme']);
2359 if ((int) Tools::getValue('id_theme') == (int) $shop['id_theme']) {
2360 continue;
2361 }
2362
2363 $oldXmlName = 'default.xml';
2364 if (file_exists(_PS_ROOT_DIR_.'/config/xml/themes/'.$shopTheme->directory.'.xml')) {
2365 $oldXmlName = $shopTheme->directory.'.xml';
2366 }
2367
2368 $shopXml = @simplexml_load_file(_PS_ROOT_DIR_.'/config/xml/themes/'.$oldXmlName);
2369
2370 if (!$shopXml) {
2371 continue;
2372 }
2373
2374 $themeShopModule = $this->getModules($shopXml);
2375
2376 $toShopUninstall = array_merge($themeShopModule['to_install'], $themeShopModule['to_enable']);
2377
2378 $toShopUninstall = preg_grep('/dash/', $toShopUninstall, PREG_GREP_INVERT);
2379
2380 $toShopUninstallClean = array_diff($toShopUninstall, $themeModule['to_enable']);
2381
2382 $toShopUninstallFormated = $this->formatHelperArray($toShopUninstallClean);
2383
2384 if (count($toShopUninstallFormated) == 0) {
2385 continue;
2386 }
2387
2388 $class = '';
2389 if ($shop['id_shop'] == $currentShop) {
2390 $themeModule['to_disable_shop'.$shop['id_shop']] = array_merge($themeShopModule['to_install'], $toShopUninstallClean);
2391 } else {
2392 $class = 'hide';
2393 }
2394
2395 $fieldsForm['form']['input'][] = [
2396 'type' => 'checkbox',
2397 'label' => sprintf($this->l('Select the modules from the old %1s theme that you wish to disable'), $shopTheme->directory),
2398 'form_group_class' => $class,
2399 'values' => [
2400 'query' => $toShopUninstallFormated,
2401 'id' => 'id',
2402 'name' => 'name',
2403 ],
2404 'expand' => [
2405 'print_total' => count($toShopUninstallFormated),
2406 'default' => 'show',
2407 'show' => ['text' => $this->l('Show'), 'icon' => 'plus-sign-alt'],
2408 'hide' => ['text' => $this->l('Hide'), 'icon' => 'minus-sign-alt'],
2409 ],
2410 'name' => 'to_disable_shop'.$shop['id_shop'],
2411 ];
2412 }
2413
2414 $fieldsValue = $this->formatHelperValuesArray($themeModule);
2415
2416 $fieldsValue['id_theme'] = (int) Tools::getValue('id_theme');
2417
2418 $helper = new HelperForm();
2419
2420 $helper->currentIndex = $this->context->link->getAdminLink('AdminThemes', false).'&action=ThemeInstall';
2421 $helper->token = Tools::getAdminTokenLite('AdminThemes');
2422 $helper->submit_action = '';
2423 $helper->show_toolbar = true;
2424 $helper->toolbar_btn = $toolbarBtn;
2425 $helper->fields_value = $fieldsValue;
2426 $helper->languages = $this->getLanguages();
2427 $helper->default_form_language = (int) $this->context->language->id;
2428 $helper->table = 'theme';
2429
2430 $helper->override_folder = $this->tpl_folder;
2431
2432 return $helper->generateForm([$fieldsForm]);
2433 }
2434
2435 Tools::redirectAdmin($this->context->link->getAdminLink('AdminThemes'));
2436
2437 return '';
2438 }
2439
2440 /**
2441 * Get modules
2442 *
2443 * @param $xml
2444 *
2445 * @return array
2446 *
2447 * @since 1.0.0
2448 */
2449 protected function getModules($xml)
2450 {
2451 $nativeModules = $this->getNativeModule();
2452 $themeModule = [];
2453
2454 $themeModule['to_install'] = [];
2455 $themeModule['to_enable'] = [];
2456 $themeModule['to_disable'] = [];
2457 foreach ($xml->modules->module as $row) {
2458 if (strval($row['action']) == 'install' && !in_array(strval($row['name']), $nativeModules)) {
2459 $themeModule['to_install'][] = strval($row['name']);
2460 } elseif (strval($row['action']) == 'enable') {
2461 $themeModule['to_enable'][] = strval($row['name']);
2462 } elseif (strval($row['action']) == 'disable') {
2463 $themeModule['to_disable'][] = strval($row['name']);
2464 }
2465 }
2466
2467 return $themeModule;
2468 }
2469
2470 /**
2471 * Format helper values array
2472 *
2473 * @param array $originArr
2474 *
2475 * @return array
2476 *
2477 * @since 1.0.0
2478 */
2479 protected function formatHelperValuesArray($originArr)
2480 {
2481 $fmtArr = [];
2482 foreach ($originArr as $key => $type) {
2483 foreach ($type as $module) {
2484 $fmtArr[$key.'_module'.$module] = true;
2485 }
2486 }
2487
2488 return $fmtArr;
2489 }
2490
2491 /**
2492 * Process theme install
2493 *
2494 * @return void
2495 *
2496 * @throws Adapter_Exception
2497 * @throws PrestaShopDatabaseException
2498 * @throws PrestaShopException
2499 * @since 1.0.0
2500 */
2501 public function processThemeInstall()
2502 {
2503 $shopsAsso = $this->context->employee->getAssociatedShops();
2504 if (Shop::isFeatureActive() && !Tools::getIsset('checkBoxShopAsso_theme') && count($shopsAsso) > 1) {
2505 $this->errors[] = $this->l('You must choose at least one shop.');
2506 $this->display = 'ChooseThemeModule';
2507
2508 return;
2509 }
2510
2511 $theme = new Theme((int) Tools::getValue('id_theme'));
2512
2513 if (count($shopsAsso) == 1) {
2514 $shops = $shopsAsso;
2515 } else {
2516 $shops = [Configuration::get('PS_SHOP_DEFAULT')];
2517 if (Tools::isSubmit('checkBoxShopAsso_theme')) {
2518 $shops = Tools::getValue('checkBoxShopAsso_theme');
2519 }
2520 }
2521
2522 $xml = false;
2523 if (file_exists(_PS_ROOT_DIR_.'/config/xml/themes/'.$theme->directory.'.xml')) {
2524 $xml = @simplexml_load_file(_PS_ROOT_DIR_.'/config/xml/themes/'.$theme->directory.'.xml');
2525 } elseif (file_exists(_PS_ROOT_DIR_.'/config/xml/themes/default.xml')) {
2526 $xml = @simplexml_load_file(_PS_ROOT_DIR_.'/config/xml/themes/default.xml');
2527 }
2528
2529 if ($xml) {
2530 $moduleHook = [];
2531 foreach ($xml->modules->hooks->hook as $row) {
2532 $name = strval($row['module']);
2533
2534 $exceptions = (isset($row['exceptions']) ? explode(',', strval($row['exceptions'])) : []);
2535
2536 $moduleHook[$name]['hook'][] = [
2537 'hook' => strval($row['hook']),
2538 'position' => strval($row['position']),
2539 'exceptions' => $exceptions,
2540 ];
2541 }
2542
2543 $this->img_error = $this->updateImages($xml);
2544
2545 $this->modules_errors = [];
2546 foreach ($shops as $idShop) {
2547 foreach ($_POST as $key => $value) {
2548 if (strncmp($key, 'to_install', strlen('to_install')) == 0) {
2549 $module = Module::getInstanceByName($value);
2550 if ($module) {
2551 $isInstalledSuccess = true;
2552 if (!Module::isInstalled($module->name)) {
2553 $isInstalledSuccess = $module->install();
2554 }
2555 if ($isInstalledSuccess) {
2556 if (!Module::isEnabled($module->name)) {
2557 $module->enable();
2558 }
2559
2560 if ((int) $module->id > 0 && isset($moduleHook[$module->name])) {
2561 $this->hookModule($module->id, $moduleHook[$module->name], $idShop);
2562 }
2563 } else {
2564 $this->modules_errors[] = ['module_name' => $module->name, 'errors' => $module->getErrors()];
2565 }
2566
2567 unset($moduleHook[$module->name]);
2568 }
2569 } elseif (strncmp($key, 'to_enable', strlen('to_enable')) == 0) {
2570 $module = Module::getInstanceByName($value);
2571 if ($module) {
2572 $isInstalledSuccess = true;
2573 if (!Module::isInstalled($module->name)) {
2574 $isInstalledSuccess = $module->install();
2575 }
2576
2577 if ($isInstalledSuccess) {
2578 if (!Module::isEnabled($module->name)) {
2579 $module->enable();
2580 }
2581
2582 if ((int) $module->id > 0 && isset($moduleHook[$module->name])) {
2583 $this->hookModule($module->id, $moduleHook[$module->name], $idShop);
2584 }
2585 } else {
2586 $this->modules_errors[] = ['module_name' => $module->name, 'errors' => $module->getErrors()];
2587 }
2588
2589 unset($moduleHook[$module->name]);
2590 }
2591 } elseif (strncmp($key, 'to_disable', strlen('to_disable')) == 0) {
2592 $keyExploded = explode('_', $key);
2593 $idShopModule = (int) substr($keyExploded[2], 4);
2594
2595 if ((int) $idShopModule > 0 && $idShopModule != (int) $idShop) {
2596 continue;
2597 }
2598
2599 $moduleObj = Module::getInstanceByName($value);
2600 if (Validate::isLoadedObject($moduleObj)) {
2601 if (Module::isEnabled($moduleObj->name)) {
2602 $moduleObj->disable();
2603 }
2604
2605 unset($moduleHook[$moduleObj->name]);
2606 }
2607 }
2608 }
2609 $shop = new Shop((int) $idShop);
2610 $shop->id_theme = (int) Tools::getValue('id_theme');
2611 $this->context->shop->id_theme = $shop->id_theme;
2612 $this->context->shop->update();
2613 $shop->save();
2614
2615 if (Shop::isFeatureActive()) {
2616 Configuration::updateValue('PS_PRODUCTS_PER_PAGE', (int) $theme->product_per_page, false, null, (int) $idShop);
2617 } else {
2618 Configuration::updateValue('PS_PRODUCTS_PER_PAGE', (int) $theme->product_per_page);
2619 }
2620 }
2621
2622 $this->doc = [];
2623 foreach ($xml->docs->doc as $row) {
2624 $this->doc[strval($row['name'])] = __PS_BASE_URI__.'themes/'.$theme->directory.'/docs/'.basename(strval($row['path']));
2625 }
2626 }
2627
2628 Tools::clearCache($this->context->smarty);
2629 $this->theme_name = $theme->name;
2630 $this->display = 'view';
2631 }
2632
2633 /**
2634 * Update images
2635 *
2636 * @param SimpleXMLElement $xml
2637 *
2638 * @return array
2639 *
2640 * @throws PrestaShopDatabaseException
2641 * @throws PrestaShopException
2642 * @since 1.0.0
2643 */
2644 protected function updateImages($xml)
2645 {
2646 $return = [];
2647
2648 if (isset($xml->images->image)) {
2649 foreach ($xml->images->image as $row) {
2650 Db::getInstance()->delete('image_type', '`name` = \''.pSQL($row['name']).'\'');
2651 Db::getInstance()->execute(
2652 '
2653 INSERT INTO `'._DB_PREFIX_.'image_type` (`name`, `width`, `height`, `products`, `categories`, `manufacturers`, `suppliers`, `scenes`)
2654 VALUES (\''.pSQL($row['name']).'\',
2655 '.(int) $row['width'].',
2656 '.(int) $row['height'].',
2657 '.($row['products'] == 'true' ? 1 : 0).',
2658 '.($row['categories'] == 'true' ? 1 : 0).',
2659 '.($row['manufacturers'] == 'true' ? 1 : 0).',
2660 '.($row['suppliers'] == 'true' ? 1 : 0).',
2661 '.($row['scenes'] == 'true' ? 1 : 0).')'
2662 );
2663
2664 $return['ok'][] = [
2665 'name' => strval($row['name']),
2666 'width' => (int) $row['width'],
2667 'height' => (int) $row['height'],
2668 ];
2669 }
2670 }
2671
2672 return $return;
2673 }
2674
2675 /**
2676 * Hook module
2677 *
2678 * @param int $idModule
2679 * @param array $moduleHooks
2680 * @param int $shop
2681 *
2682 * @return void
2683 *
2684 * @throws PrestaShopDatabaseException
2685 * @throws PrestaShopException
2686 * @since 1.0.0
2687 */
2688 protected function hookModule($idModule, $moduleHooks, $shop)
2689 {
2690 Db::getInstance()->execute('INSERT IGNORE INTO '._DB_PREFIX_.'module_shop (id_module, id_shop) VALUES('.(int) $idModule.', '.(int) $shop.')');
2691
2692 Db::getInstance()->execute($sql = 'DELETE FROM `'._DB_PREFIX_.'hook_module` WHERE `id_module` = '.(int) $idModule.' AND id_shop = '.(int) $shop);
2693
2694 foreach ($moduleHooks as $hooks) {
2695 foreach ($hooks as $hook) {
2696 $idHook = (int) Hook::getIdByName($hook['hook']);
2697 // Create new hook if module hook is not registered
2698 if (!$idHook) {
2699 $newHook = new Hook();
2700 $newHook->name = pSQL($hook['hook']);
2701 $newHook->title = pSQL($hook['hook']);
2702 $newHook->live_edit = (bool) preg_match('/^display/i', $newHook->name);
2703 $newHook->position = (bool) $newHook->live_edit;
2704 $newHook->add();
2705 $idHook = (int) $newHook->id;
2706 }
2707 $sqlHookModule = 'INSERT INTO `'._DB_PREFIX_.'hook_module` (`id_module`, `id_shop`, `id_hook`, `position`)
2708 VALUES ('.(int) $idModule.', '.(int) $shop.', '.$idHook.', '.(int) $hook['position'].')';
2709
2710 if (count($hook['exceptions']) > 0) {
2711 foreach ($hook['exceptions'] as $exception) {
2712 $sqlHookModuleExcept = 'INSERT INTO `'._DB_PREFIX_.'hook_module_exceptions` (`id_module`, `id_hook`, `file_name`) VALUES ('.(int) $idModule.', '.$idHook.', "'.pSQL($exception).'")';
2713 Db::getInstance()->execute($sqlHookModuleExcept);
2714 }
2715 }
2716 Db::getInstance()->execute($sqlHookModule);
2717 }
2718 }
2719 }
2720
2721 /**
2722 * Render view
2723 *
2724 * @return string
2725 *
2726 * @throws Exception
2727 * @throws PrestaShopException
2728 * @throws SmartyException
2729 * @since 1.0.0
2730 */
2731 public function renderView()
2732 {
2733 $this->tpl_view_vars = [
2734 'doc' => $this->doc,
2735 'theme_name' => $this->theme_name,
2736 'img_error' => $this->img_error,
2737 'modules_errors' => $this->modules_errors,
2738 'back_link' => $this->context->link->getAdminLink('AdminThemes'),
2739 'image_link' => $this->context->link->getAdminLink('AdminImages'),
2740 ];
2741
2742 parent::renderView();
2743 }
2744
2745 /**
2746 * This functions make checks about AdminThemes configuration edition only.
2747 *
2748 * @since 1.4
2749 */
2750 public function postProcess()
2751 {
2752 if (Tools::isSubmit('submitOptionstheme') && Tools::isSubmit('id_theme') && !Tools::isSubmit('deletetheme')
2753 && Tools::getValue('action') != 'ThemeInstall' && $this->context->shop->id_theme != Tools::getValue('id_theme')
2754 ) {
2755 $this->display = 'ChooseThemeModule';
2756 } elseif (Tools::isSubmit('installThemeFromFolder') && ($this->context->mode != Context::MODE_HOST)) {
2757 $themeDir = Tools::getValue('theme_dir');
2758 $this->installTheme($themeDir);
2759 } else {
2760 // new check compatibility theme feature (1.4) :
2761 $val = Tools::getValue('PS_THEME');
2762 Configuration::updateValue('PS_IMG_UPDATE_TIME', time());
2763 if (!empty($val) && !$this->_isThemeCompatible($val)) { // don't submit if errors
2764 unset($_POST['submitThemes'.$this->table]);
2765 }
2766 Tools::clearCache($this->context->smarty);
2767
2768 return parent::postProcess();
2769 }
2770 }
2771
2772 /**
2773 * This function checks if the theme designer has thunk to make his theme compatible 1.4,
2774 * and noticed it on the $theme_dir/config.xml file. If not, some new functionnalities has
2775 * to be desactivated
2776 *
2777 * @param string $themeDir theme directory
2778 *
2779 * @return bool Validity is ok or not
2780 *
2781 * @since 1.0.0
2782 * @throws PrestaShopException
2783 */
2784 protected function _isThemeCompatible($themeDir)
2785 {
2786 $return = true;
2787 $checkVersion = AdminThemes::$check_features_version;
2788
2789 if (!is_file(_PS_ALL_THEMES_DIR_.$themeDir.'/config.xml')) {
2790 $this->errors[] = Tools::displayError('The config.xml file is missing in your theme path.').'<br/>';
2791 $xml = null;
2792 } else {
2793 $xml = @simplexml_load_file(_PS_ALL_THEMES_DIR_.$themeDir.'/config.xml');
2794 if (!$xml) {
2795 $this->errors[] = Tools::displayError('The config.xml file in your theme path is not a valid XML file.').'<br/>';
2796 }
2797 }
2798 // will be set to false if any version node in xml is correct
2799 $xmlVersionTooOld = true;
2800
2801 // foreach version in xml file,
2802 // node means feature, attributes has to match
2803 // the corresponding value in AdminThemes::$check_features[feature] array
2804 $xmlArray = simpleXMLToArray($xml);
2805 foreach ($xmlArray as $version) {
2806 if (isset($version['value']) && version_compare($version['value'], $checkVersion) >= 0) {
2807 foreach (AdminThemes::$check_features as $codeFeature => $arrConfigToCheck) {
2808 foreach ($arrConfigToCheck['attributes'] as $attr => $v) {
2809 if (!isset($version[$codeFeature]) || !isset($version[$codeFeature][$attr]) || $version[$codeFeature][$attr] != $v['value']) {
2810 if (!$this->_checkConfigForFeatures($codeFeature, $attr)) { // feature missing in config.xml file, or wrong attribute value
2811 $return = false;
2812 }
2813 }
2814 }
2815 }
2816 $xmlVersionTooOld = false;
2817 }
2818 }
2819 if ($xmlVersionTooOld && !$this->_checkConfigForFeatures(array_keys(AdminThemes::$check_features))) {
2820 $this->errors[] .= Tools::displayError('The config.xml file has not been created for this version of thirty bees.');
2821 $return = false;
2822 }
2823
2824 return $return;
2825 }
2826
2827 /**
2828 * _checkConfigForFeatures
2829 *
2830 * @param array $arrFeatures array of feature code to check
2831 * @param mixed $configItem will precise the attribute which not matches. If empty, will check every attributes
2832 *
2833 * @return bool Error message, or null if disabled
2834 * @throws PrestaShopDatabaseException
2835 * @throws PrestaShopException
2836 */
2837 protected function _checkConfigForFeatures($arrFeatures, $configItem = [])
2838 {
2839 $return = true;
2840 if (is_array($configItem)) {
2841 foreach ($arrFeatures as $feature) {
2842 if (!count($configItem)) {
2843 $configItem = array_keys(AdminThemes::$check_features[$feature]['attributes']);
2844 }
2845 }
2846 foreach ($configItem as $attr) {
2847 $check = $this->_checkConfigForFeatures($arrFeatures, $attr);
2848 if ($check == false) {
2849 $return = false;
2850 }
2851 }
2852
2853 return $return;
2854 }
2855
2856 $return = true;
2857 if (!is_array($arrFeatures)) {
2858 $arrFeatures = [$arrFeatures];
2859 }
2860
2861 foreach ($arrFeatures as $feature) {
2862 $arrConfigToCheck = AdminThemes::$check_features[$feature]['attributes'][$configItem]['check_if_not_valid'];
2863 foreach ($arrConfigToCheck as $configKey => $configVal) {
2864 $configGet = Configuration::get($configKey);
2865 if ($configGet != $configVal) {
2866 $this->errors[] = Tools::displayError(AdminThemes::$check_features[$feature]['error']).'.'.(!empty(AdminThemes::$check_features[$feature]['tab']) ? ' <a href="?tab='.AdminThemes::$check_features[$feature]['tab'].'&token='.Tools::getAdminTokenLite(AdminThemes::$check_features[$feature]['tab']).'" ><u>'.Tools::displayError('You can disable this function.').'</u></a>' : '').'<br/>';
2867 $return = false;
2868 break; // break for this attributes
2869 }
2870 }
2871 }
2872
2873 return $return;
2874 }
2875
2876 /**
2877 * Update PS_LOGO
2878 *
2879 * @since 1.0.0
2880 */
2881 public function updateOptionPsLogo()
2882 {
2883 $this->updateLogo('PS_LOGO', 'logo');
2884 }
2885
2886 /**
2887 * Generic function which allows logo upload
2888 *
2889 * @param string $fieldName
2890 * @param string $logoPrefix
2891 *
2892 * @return bool
2893 *
2894 * @since 1.0.0
2895 * @throws PrestaShopException
2896 */
2897 protected function updateLogo($fieldName, $logoPrefix)
2898 {
2899
2900 $idShop = $this->context->shop->id;
2901 if (isset($_FILES[$fieldName]['tmp_name']) && $_FILES[$fieldName]['tmp_name'] && $_FILES[$fieldName]['size']) {
2902 if ($error = ImageManager::validateUpload($_FILES[$fieldName], Tools::getMaxUploadSize())) {
2903 $this->errors[] = $error;
2904
2905 return false;
2906 }
2907 $tmpName = tempnam(_PS_TMP_IMG_DIR_, 'PS');
2908
2909 if (!$tmpName || !move_uploaded_file($_FILES[$fieldName]['tmp_name'], $tmpName)) {
2910 return false;
2911 }
2912
2913 $ext = ($fieldName == 'PS_STORES_ICON') ? '.gif' : '.jpg';
2914 $logoName = str_replace('%', '', urlencode(Tools::link_rewrite($this->context->shop->name))).'-'.$logoPrefix.'-'.(int) Configuration::get('PS_IMG_UPDATE_TIME').(int) $idShop.$ext;
2915
2916 if ($this->context->shop->getContext() == Shop::CONTEXT_ALL || $idShop == 0
2917 || Shop::isFeatureActive() == false
2918 ) {
2919 $logoName = str_replace('%', '', urlencode(Tools::link_rewrite($this->context->shop->name))).'-'.$logoPrefix.'-'.(int) Configuration::get('PS_IMG_UPDATE_TIME').$ext;
2920 }
2921
2922 if ($fieldName == 'PS_STORES_ICON') {
2923 if (!@ImageManager::resize($tmpName, _PS_IMG_DIR_.$logoName, null, null, 'gif', true)) {
2924 $this->errors[] = Tools::displayError('An error occurred while attempting to copy your logo.');
2925 }
2926 } else {
2927 if (!@ImageManager::resize($tmpName, _PS_IMG_DIR_.$logoName)) {
2928 $this->errors[] = Tools::displayError('An error occurred while attempting to copy your logo.');
2929 }
2930 }
2931 $idShop = null;
2932 $idShopGroup = null;
2933 if (!count($this->errors) && @filemtime(_PS_IMG_DIR_.Configuration::get($fieldName))) {
2934 if (Shop::isFeatureActive()) {
2935 if (Shop::getContext() == Shop::CONTEXT_SHOP) {
2936 $idShop = Shop::getContextShopID();
2937 $idShopGroup = Shop::getContextShopGroupID();
2938 Shop::setContext(Shop::CONTEXT_ALL);
2939 $logoAll = Configuration::get($fieldName);
2940 Shop::setContext(Shop::CONTEXT_GROUP);
2941 $logoGroup = Configuration::get($fieldName);
2942 Shop::setContext(Shop::CONTEXT_SHOP);
2943 $logoShop = Configuration::get($fieldName);
2944 if ($logoAll != $logoShop && $logoGroup != $logoShop && $logoShop != false) {
2945 @unlink(_PS_IMG_DIR_.Configuration::get($fieldName));
2946 }
2947 } elseif (Shop::getContext() == Shop::CONTEXT_GROUP) {
2948 $idShopGroup = Shop::getContextShopGroupID();
2949 Shop::setContext(Shop::CONTEXT_ALL);
2950 $logoAll = Configuration::get($fieldName);
2951 Shop::setContext(Shop::CONTEXT_GROUP);
2952 if ($logoAll != Configuration::get($fieldName)) {
2953 @unlink(_PS_IMG_DIR_.Configuration::get($fieldName));
2954 }
2955 }
2956 } else {
2957 @unlink(_PS_IMG_DIR_.Configuration::get($fieldName));
2958 }
2959 }
2960 Configuration::updateValue($fieldName, $logoName, false, $idShopGroup, $idShop);
2961 Hook::exec('actionAdminThemesControllerUpdate_optionsAfter');
2962 @unlink($tmpName);
2963 }
2964 }
2965
2966 /**
2967 * Update PS_LOGO_MAIL
2968 *
2969 * @return void
2970 *
2971 * @since 1.0.0
2972 * @throws PrestaShopException
2973 */
2974 public function updateOptionPsLogoMail()
2975 {
2976 $this->updateLogo('PS_LOGO_MAIL', 'logo_mail');
2977 }
2978
2979 /**
2980 * Update PS_LOGO_INVOICE
2981 *
2982 * @return void
2983 *
2984 * @since 1.0.0
2985 * @throws PrestaShopException
2986 */
2987 public function updateOptionPsLogoInvoice()
2988 {
2989 $this->updateLogo('PS_LOGO_INVOICE', 'logo_invoice');
2990 }
2991
2992 /**
2993 * Update PS_STORES_ICON
2994 *
2995 * @return void
2996 *
2997 * @since 1.0.0
2998 * @throws PrestaShopException
2999 */
3000 public function updateOptionPsStoresIcon()
3001 {
3002 $this->updateLogo('PS_STORES_ICON', 'logo_stores');
3003 }
3004
3005 /**
3006 * Update PS_FAVICON
3007 *
3008 * @return void
3009 *
3010 * @since 1.0.0
3011 * @throws PrestaShopException
3012 */
3013 public function updateOptionPsFavicon()
3014 {
3015 $idShop = $this->context->shop->id;
3016
3017 if ($idShop == Configuration::get('PS_SHOP_DEFAULT')) {
3018 $this->uploadIco('PS_FAVICON', _PS_IMG_DIR_.'favicon.ico');
3019 }
3020 if ($this->uploadIco('PS_FAVICON', _PS_IMG_DIR_.'favicon_'.(int) $idShop.'.ico')) {
3021 Configuration::updateValue('PS_FAVICON', 'favicon_'.(int) $idShop.'.ico');
3022 }
3023
3024 Configuration::updateGlobalValue('PS_FAVICON', 'favicon.ico');
3025
3026 if (!$this->errors) {
3027 $this->redirect_after = static::$currentIndex.'&token='.$this->token;
3028 }
3029 }
3030
3031 /**
3032 * Process the favicon sizes
3033 *
3034 * @since 1.0.4
3035 * @throws PrestaShopException
3036 */
3037 public function updateOptionTbSourceFaviconCode()
3038 {
3039 if (!file_exists(_PS_IMG_DIR_.'favicon')) {
3040 $definedUmask = defined('_TB_UMASK_') ? _TB_UMASK_ : 0000;
3041 $previousUmask = @umask($definedUmask);
3042 mkdir(_PS_IMG_DIR_.'favicon', 0777);
3043 @umask($previousUmask);
3044 }
3045
3046 $idShop = (int) $this->context->shop->id;
3047 $this->uploadIco('TB_SOURCE_FAVICON', _PS_IMG_DIR_."favicon/favicon_{$idShop}_source.png");
3048
3049 $newTemplate = Tools::getValue('TB_SOURCE_FAVICON_CODE');
3050
3051 // Generate the new header HTML
3052 $filteredHtml = '';
3053
3054 // Generate a browserconfig.xml
3055 $browserConfig = new DOMDocument('1.0', 'UTF-8');
3056 $main = $browserConfig->createElement('browserconfig');
3057 $ms = $browserConfig->createElement('msapplication');
3058 $tile = $browserConfig->createElement('tile');
3059 $ms->appendChild($tile);
3060 $main->appendChild($ms);
3061 $browserConfig->appendChild($main);
3062 $browserConfig->formatOutput = true;
3063
3064 // Generate a new manifest.json
3065 $manifest = [
3066 'name' => Configuration::get('PS_SHOP_NAME'),
3067 'icons' => [],
3068 'theme_color' => '#fad629',
3069 'background_color' => '#fad629',
3070 'display' => 'standalone',
3071 ];
3072
3073 // Filter and detect sizes
3074 $dom = new DOMDocument();
3075 $dom->loadHTML($newTemplate);
3076 $links = [];
3077 foreach ($dom->getElementsByTagName('link') as $elem) {
3078 $links[] = $elem;
3079 }
3080 foreach ($dom->getElementsByTagName('meta') as $elem) {
3081 $links[] = $elem;
3082 }
3083 foreach ($links as $link) {
3084 foreach ($link->attributes as $attribute) {
3085 /** @var DOMElement $link */
3086 if ($favicon = Tools::parseFaviconSizeTag(urldecode($attribute->value))) {
3087 ImageManager::resize(
3088 _PS_IMG_DIR_."favicon/favicon_{$idShop}_source.png",
3089 _PS_IMG_DIR_."favicon/favicon_{$idShop}_{$favicon['width']}_{$favicon['height']}.png",
3090 (int) $favicon['width'],
3091 (int) $favicon['height'],
3092 'png'
3093 );
3094
3095 if (in_array("{$favicon['width']}x{$favicon['height']}", [
3096 '70x70',
3097 '150x150',
3098 '310x310',
3099 '310x150'
3100 ])) {
3101 $path = Media::getMediaPath(_PS_IMG_DIR_."favicon/favicon_{$idShop}_{$favicon['width']}_{$favicon['height']}.png");
3102 $logo = $favicon['width'] == $favicon['height']
3103 ? $browserConfig->createElement("square{$favicon['width']}x{$favicon['height']}logo", $path)
3104 : $browserConfig->createElement("wide{$favicon['width']}x{$favicon['height']}logo", $path);
3105 $tile->appendChild($logo);
3106 }
3107
3108 $manifest['icons'][] = [
3109 'src' => Media::getMediaPath(_PS_IMG_DIR_."favicon/favicon_{$idShop}_{$favicon['width']}_{$favicon['height']}.png"),
3110 'sizes' => "{$favicon['width']}x{$favicon['height']}",
3111 'type' => "image/{$favicon['type']}",
3112 ];
3113 }
3114
3115 if ($link->hasAttribute('name') && $link->getAttribute('name') === 'theme-color') {
3116 $manifest['theme_color'] = $link->getAttribute('content');
3117 }
3118 if ($link->hasAttribute('name') && $link->getAttribute('name') === 'background-color') {
3119 $manifest['background_color'] = $link->getAttribute('content');
3120 }
3121 }
3122 $filteredHtml .= $dom->saveHTML($link);
3123 }
3124
3125 file_put_contents(_PS_IMG_DIR_."favicon/browserconfig_{$idShop}.xml", $browserConfig->saveXML());
3126 file_put_contents(_PS_IMG_DIR_."favicon/manifest_{$idShop}.json", json_encode($manifest, JSON_UNESCAPED_SLASHES + JSON_PRETTY_PRINT));
3127 Configuration::updateValue('TB_SOURCE_FAVICON_CODE', nl2br(urldecode($filteredHtml)), true);
3128
3129 if (!$this->errors) {
3130 $this->redirect_after = static::$currentIndex.'&token='.$this->token;
3131 }
3132 }
3133
3134 /**
3135 * Upload ICO
3136 *
3137 * @param string $name
3138 * @param string $dest
3139 *
3140 * @return bool
3141 *
3142 * @since 1.0.0
3143 */
3144 protected function uploadIco($name, $dest)
3145 {
3146 if (isset($_FILES[$name]['tmp_name']) && !empty($_FILES[$name]['tmp_name'])) {
3147 // Check ico validity
3148 if ($error = ImageManager::validateIconUpload($_FILES[$name])) {
3149 $this->errors[] = $name . ': ' . $error;
3150 } elseif (mb_substr($dest, -3) === 'ico' && !@file_put_contents($dest, ImageManager::generateFavicon($_FILES[$name]['tmp_name']))) {
3151 // Copy new ico
3152 $this->errors[] = sprintf(Tools::displayError('An error occurred while uploading the favicon: cannot copy file "%s" to folder "%s".'), $_FILES[$name]['tmp_name'], $dest);
3153 } elseif (mb_substr($dest, -3) !== 'ico' && !@copy($_FILES[$name]['tmp_name'], $dest)) {
3154 $this->errors[] = sprintf(Tools::displayError('An error occurred while uploading the favicon: cannot copy file "%s" to folder "%s".'), $_FILES[$name]['tmp_name'], $dest);
3155 }
3156 }
3157
3158 return !count($this->errors);
3159 }
3160
3161 /**
3162 * Update PS_FAVICON_57
3163 *
3164 * @return void
3165 *
3166 * @since 1.0.0
3167 * @deprecated 1.0.4
3168 * @throws PrestaShopException
3169 */
3170 public function updateOptionPsFavicon_57()
3171 {
3172 $idShop = $this->context->shop->id;
3173
3174 if ($idShop == Configuration::get('PS_SHOP_DEFAULT')) {
3175 $this->uploadIco('PS_FAVICON_57', _PS_IMG_DIR_.'favicon_57.png');
3176 }
3177 if ($this->uploadIco('PS_FAVICON_57', _PS_IMG_DIR_.'favicon_57-'.(int) $idShop.'.png')) {
3178 Configuration::updateValue('PS_FAVICON_57', 'favicon_57-'.(int) $idShop.'.png');
3179 }
3180
3181 Configuration::updateGlobalValue('PS_FAVICON_57', 'favicon_57.png');
3182
3183 if (!$this->errors) {
3184 $this->redirect_after = static::$currentIndex.'&token='.$this->token;
3185 } else $this->redirect_after = false;
3186 }
3187
3188 /**
3189 * Update PS_FAVICON_72
3190 *
3191 * @since 1.0.0
3192 * @deprecated 1.0.4
3193 * @throws PrestaShopException
3194 */
3195 public function updateOptionPsFavicon_72()
3196 {
3197 $idShop = $this->context->shop->id;
3198
3199 if ($idShop == Configuration::get('PS_SHOP_DEFAULT')) {
3200 $this->uploadIco('PS_FAVICON_72', _PS_IMG_DIR_.'favicon_72.png');
3201 }
3202 if ($this->uploadIco('PS_FAVICON_72', _PS_IMG_DIR_.'favicon_72-'.(int) $idShop.'.png')) {
3203 Configuration::updateValue('PS_FAVICON_72', 'favicon_72-'.(int) $idShop.'.png');
3204 }
3205
3206 Configuration::updateGlobalValue('PS_FAVICON_72', 'favicon_72.png');
3207
3208 if (!$this->errors) {
3209 $this->redirect_after = static::$currentIndex.'&token='.$this->token;
3210 } else $this->redirect_after = false;
3211 }
3212
3213 /**
3214 * Update PS_FAVICON_114
3215 *
3216 * @since 1.0.0
3217 * @deprecated 1.0.4
3218 * @throws PrestaShopException
3219 */
3220 public function updateOptionPsFavicon_114()
3221 {
3222 $idShop = $this->context->shop->id;
3223
3224 if ($idShop == Configuration::get('PS_SHOP_DEFAULT')) {
3225 $this->uploadIco('PS_FAVICON_114', _PS_IMG_DIR_.'favicon_114.png');
3226 }
3227 if ($this->uploadIco('PS_FAVICON_114', _PS_IMG_DIR_.'favicon_114-'.(int) $idShop.'.png')) {
3228 Configuration::updateValue('PS_FAVICON_114', 'favicon_114-'.(int) $idShop.'.png');
3229 }
3230
3231 Configuration::updateGlobalValue('PS_FAVICON_114', 'favicon_114.png');
3232
3233 if (!$this->errors) {
3234 $this->redirect_after = static::$currentIndex.'&token='.$this->token;
3235 } else $this->redirect_after = false;
3236 }
3237
3238 /**
3239 * Update PS_FAVICON_144
3240 *
3241 * @since 1.0.0
3242 * @deprecated 1.0.4
3243 * @throws PrestaShopException
3244 */
3245 public function updateOptionPsFavicon_144()
3246 {
3247 $idShop = $this->context->shop->id;
3248
3249 if ($idShop == Configuration::get('PS_SHOP_DEFAULT')) {
3250 $this->uploadIco('PS_FAVICON_144', _PS_IMG_DIR_.'favicon_144.png');
3251 }
3252 if ($this->uploadIco('PS_FAVICON_144', _PS_IMG_DIR_.'favicon_144-'.(int) $idShop.'.png')) {
3253 Configuration::updateValue('PS_FAVICON_144', 'favicon_144-'.(int) $idShop.'.png');
3254 }
3255
3256 Configuration::updateGlobalValue('PS_FAVICON_144', 'favicon_144.png');
3257
3258 if (!$this->errors) {
3259 $this->redirect_after = static::$currentIndex.'&token='.$this->token;
3260 } else $this->redirect_after = false;
3261 }
3262
3263 /**
3264 * Update PS_FAVICON_192
3265 *
3266 * @since 1.0.0
3267 * @deprecated 1.0.4
3268 * @throws PrestaShopException
3269 */
3270 public function updateOptionPsFavicon_192()
3271 {
3272 $idShop = $this->context->shop->id;
3273
3274 if ($idShop == Configuration::get('PS_SHOP_DEFAULT')) {
3275 $this->uploadIco('PS_FAVICON_192', _PS_IMG_DIR_.'favicon_192.png');
3276 }
3277 if ($this->uploadIco('PS_FAVICON_192', _PS_IMG_DIR_.'favicon_192-'.(int) $idShop.'.png')) {
3278 Configuration::updateValue('PS_FAVICON_192', 'favicon_192-'.(int) $idShop.'.png');
3279 }
3280
3281 Configuration::updateGlobalValue('PS_FAVICON_192', 'favicon_192.png');
3282
3283 if (!$this->errors) {
3284 $this->redirect_after = static::$currentIndex.'&token='.$this->token;
3285 } else $this->redirect_after = false;
3286 }
3287
3288 /**
3289 * Refresh the favicon template
3290 *
3291 * @since 1.0.4 to enable the favicon template
3292 */
3293 function ajaxProcessRefreshFaviconTemplate()
3294 {
3295 try {
3296 $template = (string) (new \GuzzleHttp\Client([
3297 'http_errors' => false,
3298 'verify' => _PS_TOOL_DIR_.'cacert.pem',
3299 'timeout' => 60,
3300 ]))->get('https://raw.githubusercontent.com/thirtybees/favicons/master/template.html')->getBody();
3301 } catch (Exception $e) {
3302 $this->ajaxDie(json_encode([
3303 'hasError' => true,
3304 'error' => $e->getMessage(),
3305 ]));
3306 }
3307
3308 if (!$template) {
3309 $this->ajaxDie(json_encode([
3310 'hasError' => true,
3311 'error' => '',
3312 ]));
3313 }
3314
3315 $this->ajaxDie(json_encode([
3316 'hasError' => false,
3317 'template' => base64_encode($template),
3318 'error' => '',
3319 ]));
3320 }
3321
3322 /**
3323 * Update theme for current shop
3324 *
3325 * @return void
3326 *
3327 * @throws PrestaShopDatabaseException
3328 * @throws PrestaShopException
3329 * @since 1.0.0
3330 */
3331 public function updateOptionThemeForShop()
3332 {
3333 if (!$this->can_display_themes) {
3334 return;
3335 }
3336
3337 $idTheme = (int) Tools::getValue('id_theme');
3338 if ($idTheme && $this->context->shop->id_theme != $idTheme) {
3339 $this->context->shop->id_theme = $idTheme;
3340 $this->context->shop->update();
3341 $this->redirect_after = static::$currentIndex.'&token='.$this->token;
3342 }
3343 }
3344
3345 /**
3346 * Initialize processing
3347 *
3348 * @return void
3349 *
3350 * @since 1.0.0
3351 */
3352 public function initProcess()
3353 {
3354 if (isset($_GET['error'])) {
3355 $this->errors[] = Tools::displayError('You do not have permission to edit this.');
3356 }
3357
3358 if ((isset($_GET['responsive'.$this->table]) || isset($_GET['responsive'])) && Tools::getValue($this->identifier)) {
3359 if ($this->tabAccess['edit'] === '1') {
3360 $this->action = 'responsive';
3361 } else {
3362 $this->errors[] = Tools::displayError('You do not have permission to edit this.');
3363 }
3364 } elseif ((isset($_GET['default_left_column'.$this->table]) || isset($_GET['default_left_column'])) && Tools::getValue($this->identifier)) {
3365 if ($this->tabAccess['edit'] === '1') {
3366 $this->action = 'defaultleftcolumn';
3367 } else {
3368 $this->errors[] = Tools::displayError('You do not have permission to edit this.');
3369 }
3370 } elseif ((isset($_GET['default_right_column'.$this->table]) || isset($_GET['default_right_column'])) && Tools::getValue($this->identifier)) {
3371 if ($this->tabAccess['edit'] === '1') {
3372 $this->action = 'defaultrightcolumn';
3373 } else {
3374 $this->errors[] = Tools::displayError('You do not have permission to edit this.');
3375 }
3376 } elseif (Tools::getIsset('id_theme_meta') && Tools::getIsset('leftmeta')) {
3377 if ($this->tabAccess['edit'] === '1') {
3378 $this->action = 'leftmeta';
3379 } else {
3380 $this->errors[] = Tools::displayError('You do not have permission to edit this.');
3381 }
3382 } elseif (Tools::getIsset('id_theme_meta') && Tools::getIsset('rightmeta')) {
3383 if ($this->tabAccess['edit'] === '1') {
3384 $this->action = 'rightmeta';
3385 } else {
3386 $this->errors[] = Tools::displayError('You do not have permission to edit this.');
3387 }
3388 }
3389
3390 parent::initProcess();
3391 // This is a composite page, we don't want the "options" display mode
3392 if ($this->display == 'options' || $this->display == 'list') {
3393 $this->display = '';
3394 }
3395 }
3396
3397 /**
3398 * Print responsive icon
3399 *
3400 * @param mixed $value
3401 *
3402 * @return string
3403 *
3404 * @since 1.0.0
3405 */
3406 public function printResponsiveIcon($value)
3407 {
3408 return ($value ? '<span class="list-action-enable action-enabled"><i class="icon-check"></i></span>' : '<span class="list-action-enable action-disabled"><i class="icon-remove"></i></span>');
3409 }
3410
3411 /**
3412 * Process responsive
3413 *
3414 * @return false|ObjectModel|Theme
3415 *
3416 * @since 1.0.0
3417 * @throws PrestaShopException
3418 */
3419 public function processResponsive()
3420 {
3421 if (Validate::isLoadedObject($object = $this->loadObject())) {
3422 /** @var Theme $object */
3423 if ($object->toggleResponsive()) {
3424 $this->redirect_after = static::$currentIndex.'&conf=5&token='.$this->token;
3425 } else {
3426 $this->errors[] = Tools::displayError('An error occurred while updating responsive status.');
3427 }
3428 } else {
3429 $this->errors[] = Tools::displayError('An error occurred while updating the responsive status for this object.').' <b>'.$this->table.'</b> '.Tools::displayError('(cannot load object)');
3430 }
3431
3432 return $object;
3433 }
3434
3435 /**
3436 * Process default left column
3437 *
3438 * @return false|ObjectModel|Theme
3439 *
3440 * @since 1.0.0
3441 * @throws PrestaShopException
3442 */
3443 public function processDefaultLeftColumn()
3444 {
3445 if (Validate::isLoadedObject($object = $this->loadObject())) {
3446 /** @var Theme $object */
3447 if ($object->toggleDefaultLeftColumn()) {
3448 $this->redirect_after = static::$currentIndex.'&conf=5&token='.$this->token;
3449 } else {
3450 $this->errors[] = Tools::displayError('An error occurred while updating default left column status.');
3451 }
3452 } else {
3453 $this->errors[] = Tools::displayError('An error occurred while updating the default left column status for this object.').' <b>'.$this->table.'</b> '.Tools::displayError('(cannot load object)');
3454 }
3455
3456 return $object;
3457 }
3458
3459 /**
3460 * Process default right column
3461 *
3462 * @return false|ObjectModel|Theme
3463 *
3464 * @since 1.0.0
3465 * @throws PrestaShopException
3466 */
3467 public function processDefaultRightColumn()
3468 {
3469 if (Validate::isLoadedObject($object = $this->loadObject())) {
3470 /** @var Theme $object */
3471 if ($object->toggleDefaultRightColumn()) {
3472 $this->redirect_after = static::$currentIndex.'&conf=5&token='.$this->token;
3473 } else {
3474 $this->errors[] = Tools::displayError('An error occurred while updating default right column status.');
3475 }
3476 } else {
3477 $this->errors[] = Tools::displayError('An error occurred while updating the default right column status for this object.').' <b>'.$this->table.'</b> '.Tools::displayError('(cannot load object)');
3478 }
3479
3480 return $object;
3481 }
3482
3483 /**
3484 * Ajax process left meta
3485 *
3486 * @return void
3487 *
3488 * @throws PrestaShopDatabaseException
3489 * @throws PrestaShopException
3490 * @since 1.0.0
3491 */
3492 public function ajaxProcessLeftMeta()
3493 {
3494
3495 $result = Db::getInstance()->update(
3496 'theme_meta',
3497 [
3498 'left_column' => ['type' => 'sql', 'value' => 'NOT `left_column`'],
3499 ],
3500 '`id_theme_meta` = '.(int) Tools::getValue('id_theme_meta'),
3501 1
3502 );
3503
3504 if ($result) {
3505 $this->ajaxDie(json_encode(['success' => 1, 'text' => $this->l('The status has been updated successfully.')]));
3506 } else {
3507 $this->ajaxDie(json_encode(['success' => 0, 'text' => $this->l('An error occurred while updating this meta.')]));
3508 }
3509 }
3510
3511 /**
3512 * Process left meta
3513 *
3514 * @return void
3515 *
3516 * @throws PrestaShopDatabaseException
3517 * @throws PrestaShopException
3518 * @since 1.0.0
3519 */
3520 public function processLeftMeta()
3521 {
3522 $result = Db::getInstance()->update(
3523 'theme_meta',
3524 [
3525 'left_column' => ['type' => 'sql', 'value' => 'NOT `left_column`'],
3526 ],
3527 '`id_theme_meta` = '.(int) Tools::getValue('id_theme_meta'),
3528 1
3529 );
3530
3531 if ($result) {
3532 $idTheme = (int) Db::getInstance(_PS_USE_SQL_SLAVE_)->getValue(
3533 (new DbQuery())
3534 ->select('`id_theme`')
3535 ->from('theme_meta')
3536 ->where('`id_theme_meta` = '.(int) Tools::getValue('id_theme_meta'))
3537 );
3538
3539 $this->redirect_after = static::$currentIndex.'&updatetheme&id_theme='.$idTheme.'&conf=5&token='.$this->token;
3540 } else {
3541 $this->errors[] = Tools::displayError('An error occurred while updating this meta.');
3542 }
3543 }
3544
3545 /**
3546 * Ajax process right meta
3547 *
3548 * @return void
3549 *
3550 * @throws PrestaShopDatabaseException
3551 * @throws PrestaShopException
3552 * @sicne 1.0.0
3553 */
3554 public function ajaxProcessRightMeta()
3555 {
3556 $result = Db::getInstance()->update(
3557 'theme_meta',
3558 [
3559 'right_column' => ['type' => 'sql', 'value' => 'NOT `right_column`'],
3560 ],
3561 '`id_theme_meta` = '.(int) Tools::getValue('id_theme_meta'),
3562 1
3563 );
3564
3565 if ($result) {
3566 $this->ajaxDie(json_encode(['success' => 1, 'text' => $this->l('The status has been updated successfully.')]));
3567 } else {
3568 $this->ajaxDie(json_encode(['success' => 0, 'text' => $this->l('An error occurred while updating this meta.')]));
3569 }
3570 }
3571
3572 /**
3573 * Process right meta
3574 *
3575 * @return void
3576 *
3577 * @throws PrestaShopDatabaseException
3578 * @throws PrestaShopException
3579 * @since 1.0.0
3580 */
3581 public function processRightMeta()
3582 {
3583 $result = Db::getInstance()->update(
3584 'theme_meta',
3585 [
3586 'right_column' => ['type' => 'sql', 'value' => 'NOT `right_column`'],
3587 ],
3588 '`id_theme_meta` = '.(int) Tools::getValue('id_theme_meta'),
3589 1
3590 );
3591
3592 if ($result) {
3593 $idTheme = (int) Db::getInstance(_PS_USE_SQL_SLAVE_)->getValue(
3594 (new DbQuery())
3595 ->select('`id_theme`')
3596 ->from('theme_meta')
3597 ->where('`id_theme_meta` = '.(int) Tools::getValue('id_theme_meta'))
3598 );
3599
3600 $this->redirect_after = static::$currentIndex.'&updatetheme&id_theme='.$idTheme.'&conf=5&token='.$this->token;
3601 } else {
3602 $this->errors[] = Tools::displayError('An error occurred while updating this meta.');
3603 }
3604 }
3605
3606 /**
3607 * Function used to render the options for this controller
3608 *
3609 * @return string
3610 *
3611 * @throws Exception
3612 * @throws HTMLPurifier_Exception
3613 * @throws PrestaShopDatabaseException
3614 * @throws PrestaShopException
3615 * @throws SmartyException
3616 * @since 1.0.0
3617 */
3618 public function renderOptions()
3619 {
3620 if (isset($this->display) && method_exists($this, 'render'.$this->display)) {
3621 return $this->{'render'.$this->display}();
3622 }
3623
3624 if ($this->fields_options && is_array($this->fields_options)) {
3625 $helper = new HelperOptions($this);
3626 $this->setHelperDisplay($helper);
3627 $helper->toolbar_scroll = true;
3628 $helper->title = $this->l('Theme appearance');
3629 $helper->toolbar_btn = [
3630 'save' => [
3631 'href' => '#',
3632 'desc' => $this->l('Save'),
3633 ],
3634 ];
3635 $helper->id = $this->id;
3636 $helper->tpl_vars = $this->tpl_option_vars;
3637 $options = $helper->generateOptions($this->fields_options);
3638
3639 return $options;
3640 }
3641
3642 return '';
3643 }
3644
3645 /**
3646 * Set media
3647 *
3648 * @return void
3649 *
3650 * @throws PrestaShopDatabaseException
3651 * @throws PrestaShopException
3652 * @since 1.0.0
3653 */
3654 public function setMedia()
3655 {
3656 parent::setMedia();
3657 $this->addJS(_PS_JS_DIR_.'admin/themes.js');
3658 }
3659
3660 /**
3661 * Process update options
3662 *
3663 * @return void
3664 *
3665 * @since 1.0.0
3666 * @throws PrestaShopException
3667 */
3668 protected function processUpdateOptions()
3669 {
3670 if (!($this->tabAccess['add'] && $this->tabAccess['edit'] && $this->tabAccess['delete']) || _PS_MODE_DEMO_) {
3671 $this->errors[] = Tools::displayError('You do not have permission to edit here.');
3672 } else {
3673 parent::processUpdateOptions();
3674 }
3675
3676 if (!count($this->errors)) {
3677 Tools::redirectAdmin($this->context->link->getAdminLink('AdminThemes').'&conf=6');
3678 }
3679 }
3680
3681 /**
3682 * Recursive copy
3683 *
3684 * @param string $src
3685 * @param string $dst
3686 *
3687 * @return void
3688 *
3689 * @since 1.0.0
3690 */
3691 protected function recurseCopy($src, $dst)
3692 {
3693 if (!$dir = opendir($src)) {
3694 return;
3695 }
3696 if (!file_exists($dst)) {
3697 mkdir($dst);
3698 }
3699 while (($file = readdir($dir)) !== false) {
3700 if (strncmp($file, '.', 1) != 0) {
3701 if (is_dir($src.'/'.$file)) {
3702 static::recurseCopy($src.'/'.$file, $dst.'/'.$file);
3703 } elseif (is_readable($src.'/'.$file) && $file != 'Thumbs.db' && $file != '.DS_Store' && substr($file, -1) != '~') {
3704 copy($src.'/'.$file, $dst.'/'.$file);
3705 }
3706 }
3707 }
3708 closedir($dir);
3709 }
3710
3711 /**
3712 * Generate a cached thumbnail for object lists (eg. carrier, order statuses...etc)
3713 *
3714 * @param string $image Real image filename
3715 * @param string $cacheImage Cached filename
3716 * @param int $size Desired size
3717 * @param string $imageType Image type
3718 * @param bool $disableCache When turned on a timestamp will be added to the image URI to disable the HTTP cache
3719 * @param bool $regenerate When turned on and the file already exist, the file will be regenerated
3720 *
3721 * @return string
3722 *
3723 * @since 1.0.4
3724 */
3725 protected function thumbnail($image, $cacheImage, $size, $imageType = 'jpg', $disableCache = true, $regenerate = false)
3726 {
3727 if (!file_exists($image)) {
3728 return '';
3729 }
3730
3731 if (file_exists(_PS_TMP_IMG_DIR_.$cacheImage) && $regenerate) {
3732 @unlink(_PS_TMP_IMG_DIR_.$cacheImage);
3733 }
3734
3735 if ($regenerate || !file_exists(_PS_TMP_IMG_DIR_.$cacheImage)) {
3736 $infos = getimagesize($image);
3737
3738 // Evaluate the memory required to resize the image: if it's too much, you can't resize it.
3739 if (!ImageManager::checkImageMemoryLimit($image)) {
3740 return false;
3741 }
3742
3743 $x = $infos[0];
3744 $y = $infos[1];
3745 $maxX = $size * 3;
3746
3747 // Size is already ok
3748 if ($y < $size && $x <= $maxX) {
3749 copy($image, _PS_TMP_IMG_DIR_.$cacheImage);
3750 } // We need to resize */
3751 else {
3752 $ratio_x = $x / ($y / $size);
3753 if ($ratio_x > $maxX) {
3754 $ratio_x = $maxX;
3755 $size = $y / ($x / $maxX);
3756 }
3757
3758 ImageManager::resize($image, _PS_TMP_IMG_DIR_.$cacheImage, $ratio_x, $size, $imageType);
3759 }
3760 }
3761 // Relative link will always work, whatever the base uri set in the admin
3762 if (Context::getContext()->controller->controller_type == 'admin') {
3763 return '../img/tmp/'.$cacheImage.($disableCache ? '?time='.time() : '');
3764 } else {
3765 return _PS_TMP_IMG_.$cacheImage.($disableCache ? '?time='.time() : '');
3766 }
3767 }
3768}