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 AdminShopControllerCore
34 *
35 * @since 1.0.0
36 */
37class AdminShopControllerCore extends AdminController
38{
39    /**
40     * AdminShopControllerCore constructor.
41     *
42     * @since 1.0.0
43     */
44    public function __construct()
45    {
46        $this->bootstrap = true;
47        $this->context = Context::getContext();
48        $this->table = 'shop';
49        $this->className = 'Shop';
50        $this->multishop_context = Shop::CONTEXT_ALL;
51
52        $this->id_shop_group = (int) Tools::getValue('id_shop_group');
53
54        /* if $_GET['id_shop'] is transmitted, virtual url can be loaded in config.php, so we wether transmit shop_id in herfs */
55        if ($this->id_shop = (int) Tools::getValue('shop_id')) {
56            $_GET['id_shop'] = $this->id_shop;
57        }
58
59        $this->list_skip_actions['delete'] = [(int) Configuration::get('PS_SHOP_DEFAULT')];
60        $this->fields_list = [
61            'id_shop'         => [
62                'title' => $this->l('Shop ID'),
63                'align' => 'center',
64                'class' => 'fixed-width-xs',
65            ],
66            'name'            => [
67                'title'      => $this->l('Shop name'),
68                'filter_key' => 'a!name',
69                'width'      => 200,
70            ],
71            'shop_group_name' => [
72                'title'      => $this->l('Shop group'),
73                'width'      => 150,
74                'filter_key' => 'gs!name',
75            ],
76            'category_name'   => [
77                'title'      => $this->l('Root category'),
78                'width'      => 150,
79                'filter_key' => 'cl!name',
80            ],
81            'url'             => [
82                'title'        => $this->l('Main URL for this shop'),
83                'havingFilter' => 'url',
84            ],
85        ];
86
87        parent::__construct();
88    }
89
90    /**
91     * @param bool $disable
92     *
93     * @return string
94     *
95     * @since 1.0.0
96     */
97    public function viewAccess($disable = false)
98    {
99        return Configuration::get('PS_MULTISHOP_FEATURE_ACTIVE');
100    }
101
102    /**
103     * Initialize page header toolbar
104     *
105     * @return void
106     *
107     * @since 1.0.0
108     */
109    public function initPageHeaderToolbar()
110    {
111        parent::initPageHeaderToolbar();
112
113        if (!$this->display && $this->id_shop_group) {
114            if ($this->id_object) {
115                $this->loadObject();
116            }
117
118            if (!$this->id_shop_group && $this->object && $this->object->id_shop_group) {
119                $this->id_shop_group = $this->object->id_shop_group;
120            }
121
122            $this->page_header_toolbar_btn['edit'] = [
123                'desc' => $this->l('Edit this shop group'),
124                'href' => $this->context->link->getAdminLink('AdminShopGroup').'&updateshop_group&id_shop_group='.$this->id_shop_group,
125            ];
126
127            $this->page_header_toolbar_btn['new'] = [
128                'desc' => $this->l('Add new shop'),
129                'href' => $this->context->link->getAdminLink('AdminShop').'&add'.$this->table.'&id_shop_group='.$this->id_shop_group,
130            ];
131        }
132    }
133
134    /**
135     * Initialize toolbar
136     *
137     * @return void
138     *
139     * @since 1.0.0
140     */
141    public function initToolbar()
142    {
143        parent::initToolbar();
144
145        if ($this->display != 'edit' && $this->display != 'add') {
146            if ($this->id_object) {
147                $this->loadObject();
148            }
149
150            if (!$this->id_shop_group && $this->object && $this->object->id_shop_group) {
151                $this->id_shop_group = $this->object->id_shop_group;
152            }
153
154            $this->toolbar_btn['new'] = [
155                'desc' => $this->l('Add new shop'),
156                'href' => $this->context->link->getAdminLink('AdminShop').'&add'.$this->table.'&id_shop_group='
157                    .$this->id_shop_group,
158            ];
159        }
160    }
161
162    /**
163     * Initialize content
164     *
165     * @return void
166     *
167     * @since 1.0.0
168     */
169    public function initContent()
170    {
171        parent::initContent();
172
173        $this->addJqueryPlugin('cooki-plugin');
174        $data = Shop::getTree();
175
176        foreach ($data as $keyGroup => &$group) {
177            foreach ($group['shops'] as $keyShop => &$shop) {
178                $currentShop = new Shop($shop['id_shop']);
179                $urls = $currentShop->getUrls();
180
181                foreach ($urls as $keyUrl => &$url) {
182                    $title = $url['domain'].$url['physical_uri'].$url['virtual_uri'];
183                    if (strlen($title) > 23) {
184                        $title = substr($title, 0, 23).'...';
185                    }
186
187                    $url['name'] = $title;
188                    $shop['urls'][$url['id_shop_url']] = $url;
189                }
190            }
191        }
192
193        $shopsTree = new HelperTreeShops('shops-tree', $this->l('Multistore tree'));
194        $shopsTree->setNodeFolderTemplate('shop_tree_node_folder.tpl')->setNodeItemTemplate('shop_tree_node_item.tpl')
195            ->setHeaderTemplate('shop_tree_header.tpl')->setActions(
196                [
197                    new TreeToolbarLink(
198                        'Collapse All',
199                        '#',
200                        '$(\'#'.$shopsTree->getId().'\').tree(\'collapseAll\'); return false;',
201                        'icon-collapse-alt'
202                    ),
203                    new TreeToolbarLink(
204                        'Expand All',
205                        '#',
206                        '$(\'#'.$shopsTree->getId().'\').tree(\'expandAll\'); return false;',
207                        'icon-expand-alt'
208                    ),
209                ]
210            )
211            ->setAttribute('url_shop_group', $this->context->link->getAdminLink('AdminShopGroup'))
212            ->setAttribute('url_shop', $this->context->link->getAdminLink('AdminShop'))
213            ->setAttribute('url_shop_url', $this->context->link->getAdminLink('AdminShopUrl'))
214            ->setData($data);
215        $shopsTree = $shopsTree->render(null, false, false);
216
217        if ($this->display == 'edit') {
218            $this->toolbar_title[] = $this->object->name;
219        } elseif (!$this->display && $this->id_shop_group) {
220            $group = new ShopGroup($this->id_shop_group);
221            $this->toolbar_title[] = $group->name;
222        }
223
224        $this->context->smarty->assign(
225            [
226                'toolbar_scroll' => 1,
227                'toolbar_btn'    => $this->toolbar_btn,
228                'title'          => $this->toolbar_title,
229                'shops_tree'     => $shopsTree,
230            ]
231        );
232    }
233
234    /**
235     * Render list
236     *
237     * @return false|string
238     *
239     * @since 1.0.0
240     */
241    public function renderList()
242    {
243        $this->addRowAction('edit');
244        $this->addRowAction('delete');
245
246        $this->_select = 'gs.name shop_group_name, cl.name category_name, CONCAT(\'http://\', su.domain, su.physical_uri, su.virtual_uri) AS url';
247        $this->_join = '
248			LEFT JOIN `'._DB_PREFIX_.'shop_group` gs
249				ON (a.id_shop_group = gs.id_shop_group)
250			LEFT JOIN `'._DB_PREFIX_.'category_lang` cl
251				ON (a.id_category = cl.id_category AND cl.id_lang='.(int) $this->context->language->id.')
252			LEFT JOIN '._DB_PREFIX_.'shop_url su
253				ON a.id_shop = su.id_shop AND su.main = 1
254		';
255        $this->_group = 'GROUP BY a.id_shop';
256
257        if ($idShopGroup = (int) Tools::getValue('id_shop_group')) {
258            $this->_where = 'AND a.id_shop_group = '.$idShopGroup;
259        }
260
261        return parent::renderList();
262    }
263
264    /**
265     * @return void
266     *
267     * @since 1.0.0
268     */
269    public function displayAjaxGetCategoriesFromRootCategory()
270    {
271        if (Tools::isSubmit('id_category')) {
272            $selectedCat = [(int) Tools::getValue('id_category')];
273            $children = Category::getChildren((int) Tools::getValue('id_category'), $this->context->language->id);
274            foreach ($children as $child) {
275                $selectedCat[] = $child['id_category'];
276            }
277
278            $helper = new HelperTreeCategories('categories-tree', null, (int) Tools::getValue('id_category'), null, false);
279            $this->content = $helper->setSelectedCategories($selectedCat)->setUseSearch(true)->setUseCheckBox(true)
280                ->render();
281        }
282        parent::displayAjax();
283    }
284
285    /**
286     * Post processing
287     *
288     * @return bool|Shop
289     *
290     * @since 1.0.0
291     */
292    public function postProcess()
293    {
294        if (Tools::isSubmit('id_category_default')) {
295            $_POST['id_category'] = Tools::getValue('id_category_default');
296        }
297
298        if (Tools::isSubmit('submitAddshopAndStay') || Tools::isSubmit('submitAddshop')) {
299            $shopGroup = new ShopGroup((int) Tools::getValue('id_shop_group'));
300            if ($shopGroup->shopNameExists(Tools::getValue('name'), (int) Tools::getValue('id_shop'))) {
301                $this->errors[] = Tools::displayError('You cannot have two shops with the same name in the same group.');
302            }
303        }
304
305        if (count($this->errors)) {
306            return false;
307        }
308
309        /** @var Shop|bool $result */
310        $result = parent::postProcess();
311
312        if ($result != false && (Tools::isSubmit('submitAddshopAndStay') || Tools::isSubmit('submitAddshop')) && (int) $result->id_category != (int) Configuration::get('PS_HOME_CATEGORY', null, null, (int) $result->id)) {
313            Configuration::updateValue('PS_HOME_CATEGORY', (int) $result->id_category, false, null, (int) $result->id);
314        }
315
316        if ($this->redirect_after) {
317            $this->redirect_after .= '&id_shop_group='.$this->id_shop_group;
318        }
319
320        return $result;
321    }
322
323    /**
324     * Process delete
325     *
326     * @return bool
327     *
328     * @since 1.0.0
329     */
330    public function processDelete()
331    {
332        if (!Validate::isLoadedObject($object = $this->loadObject())) {
333            $this->errors[] = Tools::displayError('Unable to load this shop.');
334        } elseif (!Shop::hasDependency($object->id)) {
335            $result = Category::deleteCategoriesFromShop($object->id) && parent::processDelete();
336            Tools::generateHtaccess();
337
338            return $result;
339        } else {
340            $this->errors[] = Tools::displayError('You can\'t delete this shop (customer and/or order dependency).');
341        }
342
343        return false;
344    }
345
346    /**
347     * Get list
348     *
349     * @param int         $idLang
350     * @param string|null $orderBy
351     * @param string|null $orderWay
352     * @param int         $start
353     * @param int|null    $limit
354     * @param int|bool    $idLangShop
355     *
356     * @since 1.0.0
357     */
358    public function getList($idLang, $orderBy = null, $orderWay = null, $start = 0, $limit = null, $idLangShop = false)
359    {
360        if (Shop::getContext() == Shop::CONTEXT_GROUP) {
361            $this->_where .= ' AND a.id_shop_group = '.(int) Shop::getContextShopGroupID();
362        }
363
364        parent::getList($idLang, $orderBy, $orderWay, $start, $limit, $idLangShop);
365        $shopDeleteList = [];
366
367        // don't allow to remove shop which have dependencies (customers / orders / ... )
368        foreach ($this->_list as &$shop) {
369            if (Shop::hasDependency($shop['id_shop'])) {
370                $shopDeleteList[] = $shop['id_shop'];
371            }
372        }
373        $this->context->smarty->assign('shops_having_dependencies', $shopDeleteList);
374    }
375
376    /**
377     * Render form
378     *
379     * @return string
380     *
381     * @since 1.0.0
382     */
383    public function renderForm()
384    {
385        /** @var Shop $obj */
386        if (!($obj = $this->loadObject(true))) {
387            return '';
388        }
389
390        $this->fields_form = [
391            'legend'     => [
392                'title' => $this->l('Shop'),
393                'icon'  => 'icon-shopping-cart',
394            ],
395            'identifier' => 'shop_id',
396            'input'      => [
397                [
398                    'type'     => 'text',
399                    'label'    => $this->l('Shop name'),
400                    'desc'     => [
401                        $this->l('This field does not refer to the shop name visible in the front office.'),
402                        sprintf($this->l('Follow %sthis link%s to edit the shop name used on the front office.'), '<a href="'.$this->context->link->getAdminLink('AdminStores').'#store_fieldset_general">', '</a>'),
403                    ],
404                    'name'     => 'name',
405                    'required' => true,
406                ],
407            ],
408        ];
409
410        $displayGroupList = true;
411        if ($this->display == 'edit') {
412            $group = new ShopGroup($obj->id_shop_group);
413            if ($group->share_customer || $group->share_order || $group->share_stock) {
414                $displayGroupList = false;
415            }
416        }
417
418        if ($displayGroupList) {
419            $options = [];
420            foreach (ShopGroup::getShopGroups() as $group) {
421                /** @var ShopGroup $group */
422                if ($this->display == 'edit' && ($group->share_customer || $group->share_order || $group->share_stock) && ShopGroup::hasDependency($group->id)) {
423                    continue;
424                }
425
426                $options[] = [
427                    'id_shop_group' => $group->id,
428                    'name'          => $group->name,
429                ];
430            }
431
432            if ($this->display == 'add') {
433                $groupDesc = $this->l('Warning: You won\'t be able to change the group of this shop if this shop belongs to a group with one of these options activated: Share Customers, Share Quantities or Share Orders.');
434            } else {
435                $groupDesc = $this->l('You can only move your shop to a shop group with all "share" options disabled -- or to a shop group with no customers/orders.');
436            }
437
438            $this->fields_form['input'][] = [
439                'type'    => 'select',
440                'label'   => $this->l('Shop group'),
441                'desc'    => $groupDesc,
442                'name'    => 'id_shop_group',
443                'options' => [
444                    'query' => $options,
445                    'id'    => 'id_shop_group',
446                    'name'  => 'name',
447                ],
448            ];
449        } else {
450            $this->fields_form['input'][] = [
451                'type'    => 'hidden',
452                'name'    => 'id_shop_group',
453                'default' => $group->name,
454            ];
455            $this->fields_form['input'][] = [
456                'type'  => 'textShopGroup',
457                'label' => $this->l('Shop group'),
458                'desc'  => $this->l('You can\'t edit the shop group because the current shop belongs to a group with the "share" option enabled.'),
459                'name'  => 'id_shop_group',
460                'value' => $group->name,
461            ];
462        }
463
464        $categories = Category::getRootCategories($this->context->language->id);
465        $this->fields_form['input'][] = [
466            'type'    => 'select',
467            'label'   => $this->l('Category root'),
468            'desc'    => sprintf($this->l('This is the root category of the store that you\'ve created. To define a new root category for your store, %splease click here%s.'), '<a href="'.$this->context->link->getAdminLink('AdminCategories').'&addcategoryroot" target="_blank">', '</a>'),
469            'name'    => 'id_category',
470            'options' => [
471                'query' => $categories,
472                'id'    => 'id_category',
473                'name'  => 'name',
474            ],
475        ];
476
477        if (Tools::isSubmit('id_shop')) {
478            $shop = new Shop((int) Tools::getValue('id_shop'));
479            $idRoot = $shop->id_category;
480        } else {
481            $idRoot = $categories[0]['id_category'];
482        }
483
484        $idShop = (int) Tools::getValue('id_shop');
485        static::$currentIndex = static::$currentIndex.'&id_shop_group='.(int) (Tools::getValue('id_shop_group') ?
486                Tools::getValue('id_shop_group') : (isset($obj->id_shop_group) ? $obj->id_shop_group : Shop::getContextShopGroupID()));
487        $shop = new Shop($idShop);
488        $selectedCat = Shop::getCategories($idShop);
489
490        if (empty($selectedCat)) {
491            // get first category root and preselect all these children
492            $rootCategories = Category::getRootCategories();
493            $rootCategory = new Category($rootCategories[0]['id_category']);
494            $children = $rootCategory->getAllChildren($this->context->language->id);
495            $selectedCat[] = $rootCategories[0]['id_category'];
496
497            foreach ($children as $child) {
498                $selectedCat[] = $child->id;
499            }
500        }
501
502        if (Shop::getContext() == Shop::CONTEXT_SHOP && Tools::isSubmit('id_shop')) {
503            $rootCategory = new Category($shop->id_category);
504        } else {
505            $rootCategory = new Category($idRoot);
506        }
507
508        $this->fields_form['input'][] = [
509            'type'  => 'categories',
510            'name'  => 'categoryBox',
511            'label' => $this->l('Associated categories'),
512            'tree'  => [
513                'id'                  => 'categories-tree',
514                'selected_categories' => $selectedCat,
515                'root_category'       => $rootCategory->id,
516                'use_search'          => true,
517                'use_checkbox'        => true,
518            ],
519            'desc'  => $this->l('By selecting associated categories, you are choosing to share the categories between shops. Once associated between shops, any alteration of this category will impact every shop.'),
520        ];
521
522        $themes = Theme::getThemes();
523        if (!isset($obj->id_theme)) {
524            foreach ($themes as $theme) {
525                if (isset($theme->id)) {
526                    $idTheme = $theme->id;
527                    break;
528                }
529            }
530        }
531
532        $this->fields_form['input'][] = [
533            'type'   => 'theme',
534            'label'  => $this->l('Theme'),
535            'name'   => 'theme',
536            'values' => $themes,
537        ];
538
539        $this->fields_form['submit'] = [
540            'title' => $this->l('Save'),
541        ];
542
543        if (Shop::getTotalShops() > 1 && $obj->id) {
544            $disabled = ['active' => false];
545        } else {
546            $disabled = false;
547        }
548
549        $importData = [
550            'carrier'            => $this->l('Carriers'),
551            'cms'                => $this->l('CMS pages'),
552            'contact'            => $this->l('Contact information'),
553            'country'            => $this->l('Countries'),
554            'currency'           => $this->l('Currencies'),
555            'discount'           => $this->l('Discount prices'),
556            'employee'           => $this->l('Employees'),
557            'image'              => $this->l('Images'),
558            'lang'               => $this->l('Languages'),
559            'manufacturer'       => $this->l('Manufacturers'),
560            'module'             => $this->l('Modules'),
561            'hook_module'        => $this->l('Module hooks'),
562            'meta_lang'          => $this->l('Meta information'),
563            'product'            => $this->l('Products'),
564            'product_attribute'  => $this->l('Product combinations'),
565            'scene'              => $this->l('Scenes'),
566            'stock_available'    => $this->l('Available quantities for sale'),
567            'store'              => $this->l('Stores'),
568            'warehouse'          => $this->l('Warehouses'),
569            'webservice_account' => $this->l('Webservice accounts'),
570            'attribute_group'    => $this->l('Attribute groups'),
571            'feature'            => $this->l('Features'),
572            'group'              => $this->l('Customer groups'),
573            'tax_rules_group'    => $this->l('Tax rules groups'),
574            'supplier'           => $this->l('Suppliers'),
575            'referrer'           => $this->l('Referrers/affiliates'),
576            'zone'               => $this->l('Zones'),
577            'cart_rule'          => $this->l('Cart rules'),
578        ];
579
580        // Hook for duplication of shop data
581        $modulesList = Hook::getHookModuleExecList('actionShopDataDuplication');
582        if (is_array($modulesList) && count($modulesList) > 0) {
583            foreach ($modulesList as $m) {
584                $importData['Module'.ucfirst($m['module'])] = Module::getModuleName($m['module']);
585            }
586        }
587
588        asort($importData);
589
590        if (!$this->object->id) {
591            $this->fields_import_form = [
592                'radio'       => [
593                    'type'  => 'radio',
594                    'label' => $this->l('Import data'),
595                    'name'  => 'useImportData',
596                    'value' => 1,
597                ],
598                'select'      => [
599                    'type'    => 'select',
600                    'name'    => 'importFromShop',
601                    'label'   => $this->l('Choose the source shop'),
602                    'options' => [
603                        'query' => Shop::getShops(false),
604                        'name'  => 'name',
605                    ],
606                ],
607                'allcheckbox' => [
608                    'type'   => 'checkbox',
609                    'label'  => $this->l('Choose data to import'),
610                    'values' => $importData,
611                ],
612                'desc'        => $this->l('Use this option to associate data (products, modules, etc.) the same way for each selected shop.'),
613            ];
614        }
615
616        $this->fields_value = [
617            'id_shop_group'    => (Tools::getValue('id_shop_group') ? Tools::getValue('id_shop_group') :
618                (isset($obj->id_shop_group)) ? $obj->id_shop_group : Shop::getContextShopGroupID()),
619            'id_category'      => (Tools::getValue('id_category') ? Tools::getValue('id_category') :
620                (isset($obj->id_category)) ? $obj->id_category : (int) Configuration::get('PS_HOME_CATEGORY')),
621            'id_theme_checked' => (isset($obj->id_theme) ? $obj->id_theme : $idTheme),
622        ];
623
624        $idsCategory = [];
625        $shops = Shop::getShops(false);
626        foreach ($shops as $shop) {
627            $idsCategory[$shop['id_shop']] = $shop['id_category'];
628        }
629
630        $this->tpl_form_vars = [
631            'disabled'     => $disabled,
632            'checked'      => (Tools::getValue('addshop') !== false) ? true : false,
633            'defaultShop'  => (int) Configuration::get('PS_SHOP_DEFAULT'),
634            'ids_category' => $idsCategory,
635        ];
636        if (isset($this->fields_import_form)) {
637            $this->tpl_form_vars = array_merge($this->tpl_form_vars, ['form_import' => $this->fields_import_form]);
638        }
639
640        return parent::renderForm();
641    }
642
643    /**
644     * Object creation
645     *
646     * @return void
647     *
648     * @since 1.0.0
649     */
650    public function processAdd()
651    {
652        if (!Tools::getValue('categoryBox') || !in_array(Tools::getValue('id_category'), Tools::getValue('categoryBox'))) {
653            $this->errors[] = $this->l('You need to select at least the root category.');
654        }
655
656        if (Tools::isSubmit('id_category_default')) {
657            $_POST['id_category'] = (int) Tools::getValue('id_category_default');
658        }
659
660        /* Checking fields validity */
661        $this->validateRules();
662
663        if (!count($this->errors)) {
664            /** @var Shop $object */
665            $object = new $this->className();
666            $this->copyFromPost($object, $this->table);
667            $this->beforeAdd($object);
668            if (!$object->add()) {
669                $this->errors[] = Tools::displayError('An error occurred while creating an object.').
670                    ' <b>'.$this->table.' ('.Db::getInstance()->getMsgError().')</b>';
671            } /* voluntary do affectation here */
672            elseif (($_POST[$this->identifier] = $object->id) && $this->postImage($object->id) && !count($this->errors) && $this->_redirect) {
673                $parentId = (int) Tools::getValue('id_parent', 1);
674                $this->afterAdd($object);
675                $this->updateAssoShop($object->id);
676                // Save and stay on same form
677                if (Tools::isSubmit('submitAdd'.$this->table.'AndStay')) {
678                    $this->redirect_after = static::$currentIndex.'&shop_id='.(int) $object->id.'&conf=3&update'.$this->table.'&token='.$this->token;
679                }
680                // Save and back to parent
681                if (Tools::isSubmit('submitAdd'.$this->table.'AndBackToParent')) {
682                    $this->redirect_after = static::$currentIndex.'&shop_id='.(int) $parentId.'&conf=3&token='.$this->token;
683                }
684                // Default behavior (save and back)
685                if (empty($this->redirect_after)) {
686                    $this->redirect_after = static::$currentIndex.($parentId ? '&shop_id='.$object->id : '').'&conf=3&token='.$this->token;
687                }
688            }
689        }
690
691        $this->errors = array_unique($this->errors);
692        if (count($this->errors) > 0) {
693            $this->display = 'add';
694
695            return;
696        }
697
698        $object->associateSuperAdmins();
699
700        $categories = Tools::getValue('categoryBox');
701        array_unshift($categories, Configuration::get('PS_ROOT_CATEGORY'));
702        Category::updateFromShop($categories, $object->id);
703        if (Tools::getValue('useImportData') && ($importData = Tools::getValue('importData')) && is_array($importData) && isset($importData['product'])) {
704            ini_set('max_execution_time', 7200); // like searchcron.php
705            Search::indexation(true);
706        }
707
708        return $object;
709    }
710
711    /**
712     * @param Shop $newShop
713     *
714     * @return bool
715     *
716     * @since 1.0.0
717     */
718    protected function afterAdd($newShop)
719    {
720        $importData = Tools::getValue('importData', []);
721
722        // The root category should be at least imported
723        $newShop->copyShopData((int) Tools::getValue('importFromShop'), $importData);
724
725        // copy default data
726        if (!Tools::getValue('useImportData') || (is_array($importData) && !isset($importData['group']))) {
727            $sql = 'INSERT INTO `'._DB_PREFIX_.'group_shop` (`id_shop`, `id_group`)
728					VALUES
729					('.(int) $newShop->id.', '.(int) Configuration::get('PS_UNIDENTIFIED_GROUP').'),
730					('.(int) $newShop->id.', '.(int) Configuration::get('PS_GUEST_GROUP').'),
731					('.(int) $newShop->id.', '.(int) Configuration::get('PS_CUSTOMER_GROUP').')
732				';
733            Db::getInstance()->execute($sql);
734        }
735
736        return parent::afterAdd($newShop);
737    }
738
739    /**
740     * Display edit link
741     *
742     * @param null $token
743     * @param int  $id
744     * @param null $name
745     *
746     * @return string|void
747     *
748     * @since 1.0.0
749     */
750    public function displayEditLink($token = null, $id, $name = null)
751    {
752        if ($this->tabAccess['edit'] == 1) {
753            $tpl = $this->createTemplate('helpers/list/list_action_edit.tpl');
754            if (!array_key_exists('Edit', static::$cache_lang)) {
755                static::$cache_lang['Edit'] = $this->l('Edit', 'Helper');
756            }
757
758            $tpl->assign(
759                [
760                    'href'   => $this->context->link->getAdminLink('AdminShop').'&shop_id='.(int) $id.'&update'.$this->table,
761                    'action' => static::$cache_lang['Edit'],
762                    'id'     => $id,
763                ]
764            );
765
766            return $tpl->fetch();
767        } else {
768            return;
769        }
770    }
771
772    /**
773     * Initialize categories association
774     *
775     * @param null $idRoot
776     *
777     * @return string
778     *
779     * @since 1.0.0
780     */
781    public function initCategoriesAssociation($idRoot = null)
782    {
783        if (is_null($idRoot)) {
784            $idRoot = Configuration::get('PS_ROOT_CATEGORY');
785        }
786        $idShop = (int) Tools::getValue('id_shop');
787        $shop = new Shop($idShop);
788        $selectedCat = Shop::getCategories($idShop);
789        if (empty($selectedCat)) {
790            // get first category root and preselect all these children
791            $rootCategories = Category::getRootCategories();
792            $rootCategory = new Category($rootCategories[0]['id_category']);
793            $children = $rootCategory->getAllChildren($this->context->language->id);
794            $selectedCat[] = $rootCategories[0]['id_category'];
795
796            foreach ($children as $child) {
797                $selectedCat[] = $child->id;
798            }
799        }
800        if (Shop::getContext() == Shop::CONTEXT_SHOP && Tools::isSubmit('id_shop')) {
801            $rootCategory = new Category($shop->id_category);
802        } else {
803            $rootCategory = new Category($idRoot);
804        }
805        $rootCategory = ['id_category' => $rootCategory->id, 'name' => $rootCategory->name[$this->context->language->id]];
806
807        $helper = new Helper();
808
809        return $helper->renderCategoryTree($rootCategory, $selectedCat, 'categoryBox', false, true);
810    }
811
812    /**
813     * Ajax process tree
814     *
815     * @return void
816     *
817     * @since 1.0.0
818     */
819    public function ajaxProcessTree()
820    {
821        $tree = [];
822        $sql = 'SELECT g.id_shop_group, g.name as group_name, s.id_shop, s.name as shop_name, u.id_shop_url, u.domain, u.physical_uri, u.virtual_uri
823				FROM '._DB_PREFIX_.'shop_group g
824				LEFT JOIN  '._DB_PREFIX_.'shop s ON g.id_shop_group = s.id_shop_group
825				LEFT JOIN  '._DB_PREFIX_.'shop_url u ON u.id_shop = s.id_shop
826				ORDER BY g.name, s.name, u.domain';
827        $results = Db::getInstance(_PS_USE_SQL_SLAVE_)->executeS($sql);
828        foreach ($results as $row) {
829            $idShopGroup = $row['id_shop_group'];
830            $idShop = $row['id_shop'];
831            $idShopUrl = $row['id_shop_url'];
832
833            // Group list
834            if (!isset($tree[$idShopGroup])) {
835                $tree[$idShopGroup] = [
836                    'data'     => [
837                        'title' => '<b>'.$this->l('Group').'</b> '.$row['group_name'],
838                        'icon'  => 'themes/'.$this->context->employee->bo_theme.'/img/tree-multishop-groups.png',
839                        'attr'  => [
840                            'href'  => $this->context->link->getAdminLink('AdminShop').'&id_shop_group='.$idShopGroup,
841                            'title' => sprintf($this->l('Click here to display the shops in the %s shop group', 'AdminShop', false, false), $row['group_name']),
842                        ],
843                    ],
844                    'attr'     => [
845                        'id' => 'tree-group-'.$idShopGroup,
846                    ],
847                    'children' => [],
848                ];
849            }
850
851            // Shop list
852            if (!$idShop) {
853                continue;
854            }
855
856            if (!isset($tree[$idShopGroup]['children'][$idShop])) {
857                $tree[$idShopGroup]['children'][$idShop] = [
858                    'data'     => [
859                        'title' => $row['shop_name'],
860                        'icon'  => 'themes/'.$this->context->employee->bo_theme.'/img/tree-multishop-shop.png',
861                        'attr'  => [
862                            'href'  => $this->context->link->getAdminLink('AdminShopUrl').'&shop_id='.(int) $idShop,
863                            'title' => sprintf($this->l('Click here to display the URLs of the %s shop', 'AdminShop', false, false), $row['shop_name']),
864                        ],
865                    ],
866                    'attr'     => [
867                        'id' => 'tree-shop-'.$idShop,
868                    ],
869                    'children' => [],
870                ];
871            }
872            // Url list
873            if (!$idShopUrl) {
874                continue;
875            }
876
877            if (!isset($tree[$idShopGroup]['children'][$idShop]['children'][$idShopUrl])) {
878                $url = $row['domain'].$row['physical_uri'].$row['virtual_uri'];
879                if (strlen($url) > 23) {
880                    $url = substr($url, 0, 23).'...';
881                }
882
883                $tree[$idShopGroup]['children'][$idShop]['children'][$idShopUrl] = [
884                    'data' => [
885                        'title' => $url,
886                        'icon'  => 'themes/'.$this->context->employee->bo_theme.'/img/tree-multishop-url.png',
887                        'attr'  => [
888                            'href'  => $this->context->link->getAdminLink('AdminShopUrl').'&updateshop_url&id_shop_url='.$idShopUrl,
889                            'title' => $row['domain'].$row['physical_uri'].$row['virtual_uri'],
890                        ],
891                    ],
892                    'attr' => [
893                        'id' => 'tree-url-'.$idShopUrl,
894                    ],
895                ];
896            }
897        }
898
899        // jstree need to have children as array and not object, so we use sort to get clean keys
900        // DO NOT REMOVE this code, even if it seems really strange ;)
901        sort($tree);
902        foreach ($tree as &$groups) {
903            sort($groups['children']);
904            foreach ($groups['children'] as &$shops) {
905                sort($shops['children']);
906            }
907        }
908
909        $tree = [
910            [
911                'data'     => [
912                    'title' => '<b>'.$this->l('Shop groups list').'</b>',
913                    'icon'  => 'themes/'.$this->context->employee->bo_theme.'/img/tree-multishop-root.png',
914                    'attr'  => [
915                        'href'  => $this->context->link->getAdminLink('AdminShopGroup'),
916                        'title' => $this->l('Click here to display the list of shop groups', 'AdminShop', false, false),
917                    ],
918                ],
919                'attr'     => [
920                    'id' => 'tree-root',
921                ],
922                'state'    => 'open',
923                'children' => $tree,
924            ],
925        ];
926
927        $this->ajaxDie(json_encode($tree));
928    }
929
930    /**
931     * @param Shop $newShop
932     *
933     * @return bool
934     *
935     * @since 1.0.0
936     */
937    protected function afterUpdate($newShop)
938    {
939        $categories = Tools::getValue('categoryBox');
940
941        if (!is_array($categories)) {
942            $this->errors[] = $this->l('Please create some sub-categories for this root category.');
943
944            return false;
945        }
946
947        array_unshift($categories, Configuration::get('PS_ROOT_CATEGORY'));
948
949        if (!Category::updateFromShop($categories, $newShop->id)) {
950            $this->errors[] = $this->l('You need to select at least the root category.');
951        }
952        if (Tools::getValue('useImportData') && ($importData = Tools::getValue('importData')) && is_array($importData)) {
953            $newShop->copyShopData((int) Tools::getValue('importFromShop'), $importData);
954        }
955
956        if (Tools::isSubmit('submitAddshopAndStay') || Tools::isSubmit('submitAddshop')) {
957            $this->redirect_after = static::$currentIndex.'&shop_id='.(int) $newShop->id.'&conf=4&token='.$this->token;
958        }
959
960        return parent::afterUpdate($newShop);
961    }
962}
963