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 ShopCore
34 *
35 * @since 1.0.0
36 */
37class ShopCore extends ObjectModel
38{
39    // @codingStandardsIgnoreStart
40    /**
41     * @see ObjectModel::$definition
42     */
43    public static $definition = [
44        'table'   => 'shop',
45        'primary' => 'id_shop',
46        'fields'  => [
47            'active'        => ['type' => self::TYPE_BOOL,   'validate' => 'isBool'                                         ],
48            'deleted'       => ['type' => self::TYPE_BOOL,   'validate' => 'isBool'                                         ],
49            'name'          => ['type' => self::TYPE_STRING, 'validate' => 'isGenericName', 'required' => true, 'size' => 64],
50            'id_theme'      => ['type' => self::TYPE_INT,                                   'required' => true              ],
51            'id_category'   => ['type' => self::TYPE_INT,                                   'required' => true              ],
52            'id_shop_group' => ['type' => self::TYPE_INT,                                   'required' => true              ],
53        ],
54    ];
55    protected $webserviceParameters = [
56        'fields' => [
57            'id_shop_group' => ['xlink_resource' => 'shop_groups'],
58            'id_category'   => [],
59            'id_theme'      => [],
60        ],
61    ];
62    /** @var int ID of shop group */
63    public $id_shop_group;
64    /** @var int ID of shop category */
65    public $id_category;
66    /** @var int ID of shop theme */
67    public $id_theme;
68    /** @var string Shop name */
69    public $name;
70    /** @var bool $active */
71    public $active = true;
72    /** @var bool $deleted */
73    public $deleted;
74    /** @var string Shop theme name (read only) */
75    public $theme_name;
76    /** @var string Shop theme directory (read only) */
77    public $theme_directory;
78    /** @var string Physical uri of main url (read only) */
79    public $physical_uri;
80    /** @var string Virtual uri of main url (read only) */
81    public $virtual_uri;
82    /** @var string Domain of main url (read only) */
83    public $domain;
84    /** @var string Domain SSL of main url (read only) */
85    public $domain_ssl;
86    /** @var ShopGroup Shop group object */
87    protected $group;
88    /** @var array List of shops cached */
89    protected static $shops;
90    /** @var array $asso_tables */
91    protected static $asso_tables = [];
92    /** @var array $id_shop_default_tables */
93    protected static $id_shop_default_tables = [];
94    /** @var bool $initialized */
95    protected static $initialized = false;
96    /**
97     * Store the current context of shop (CONTEXT_ALL, CONTEXT_GROUP, CONTEXT_SHOP)
98     *
99     * @var int $context ;
100     */
101    protected static $context;
102    /**
103     * ID shop in the current context (will be empty if context is not CONTEXT_SHOP)
104     *
105     * @var int $context_id_shop
106     */
107    protected static $context_id_shop;
108    /**
109     * ID shop group in the current context (will be empty if context is CONTEXT_ALL)
110     *
111     * @var int $context_id_shop_group
112     */
113    protected static $context_id_shop_group;
114    // @codingStandardsIgnoreEnd
115
116    /**
117     * There are 3 kinds of shop context : shop, group shop and general
118     */
119    const CONTEXT_SHOP = 1;
120    const CONTEXT_GROUP = 2;
121    const CONTEXT_ALL = 4;
122
123    /**
124     * Some data can be shared between shops, like customers or orders
125     */
126    const SHARE_CUSTOMER = 'share_customer';
127    const SHARE_ORDER = 'share_order';
128    const SHARE_STOCK = 'share_stock';
129
130    /**
131     * On shop instance, get its theme and URL data too
132     *
133     * @param int $id
134     * @param int $idLang
135     * @param int $idShop
136     *
137     *
138     * @throws PrestaShopDatabaseException
139     * @throws PrestaShopException
140     * @since   1.0.0
141     * @version 1.0.0 Initial version
142     */
143    public function __construct($id = null, $idLang = null, $idShop = null)
144    {
145        parent::__construct($id, $idLang, $idShop);
146        if ($this->id) {
147            $this->setUrl();
148        }
149    }
150
151    /**
152     * @return bool
153     *
154     * @throws PrestaShopDatabaseException
155     * @throws PrestaShopException
156     * @since   1.0.0
157     * @version 1.0.0 Initial version
158     */
159    public function setUrl()
160    {
161        $cacheId = 'Shop::setUrl_'.(int) $this->id;
162        if (!Cache::isStored($cacheId)) {
163            $row = Db::getInstance(_PS_USE_SQL_SLAVE_)->getRow(
164                (new DbQuery())
165                    ->select('su.physical_uri, su.virtual_uri, su.domain, su.domain_ssl, t.id_theme, t.name, t.directory')
166                    ->from('shop', 's')
167                    ->leftJoin('shop_url', 'su', 's.`id_shop` = su.`id_shop`')
168                    ->leftJoin('theme', 't', 't.`id_theme` = s.`id_theme`')
169                    ->where('s.`id_shop` = '.(int) $this->id)
170                    ->where('s.`active` = 1')
171                    ->where('s.`deleted` = 0')
172                    ->where('su.`main` = 1')
173            );
174            Cache::store($cacheId, $row);
175        } else {
176            $row = Cache::retrieve($cacheId);
177        }
178        if (!$row) {
179            return false;
180        }
181
182        $this->theme_id = $row['id_theme'];
183        $this->theme_name = $row['name'];
184        $this->theme_directory = $row['directory'];
185        $this->physical_uri = $row['physical_uri'];
186        $this->virtual_uri = $row['virtual_uri'];
187        $this->domain = $row['domain'];
188        $this->domain_ssl = $row['domain_ssl'];
189
190        return true;
191    }
192
193    /**
194     * Add a shop, and clear the cache
195     *
196     * @param bool $autoDate
197     * @param bool $nullValues
198     *
199     * @return bool
200     * @throws PrestaShopDatabaseException
201     * @throws PrestaShopException
202     */
203    public function add($autoDate = true, $nullValues = false)
204    {
205        $res = parent::add($autoDate, $nullValues);
206        // Set default language routes
207        $langs = Language::getLanguages(false, $this->id, true);
208        // @codingStandardsIgnoreStart
209        Configuration::updateValue('PS_ROUTE_product_rule', array_map(function() {return '{categories:/}{rewrite}';}, $langs));
210        Configuration::updateValue('PS_ROUTE_category_rule', array_map(function() {return '{rewrite}';}, $langs));
211        Configuration::updateValue('PS_ROUTE_supplier_rule', array_map(function() {return '{rewrite}';}, $langs));
212        Configuration::updateValue('PS_ROUTE_manufacturer_rule', array_map(function() {return '{rewrite}';}, $langs));
213        Configuration::updateValue('PS_ROUTE_cms_rule', array_map(function() {return '{categories:/}{rewrite}';}, $langs));
214        Configuration::updateValue('PS_ROUTE_cms_category_rule', array_map(function() {return '{categories:/}{rewrite}';}, $langs));
215        // @codingStandardsIgnoreEnd
216
217        static::cacheShops(true);
218
219        return $res;
220    }
221
222    /**
223     * @since   1.0.0
224     * @version 1.0.0 Initial version
225     */
226    public function associateSuperAdmins()
227    {
228        $superAdmins = Employee::getEmployeesByProfile(_PS_ADMIN_PROFILE_);
229        foreach ($superAdmins as $superAdmin) {
230            $employee = new Employee((int) $superAdmin['id_employee']);
231            $employee->associateTo((int) $this->id);
232        }
233    }
234
235    /**
236     * Remove a shop only if it has no dependencies, and remove its associations
237     *
238     * @return bool
239     *
240     * @throws PrestaShopDatabaseException
241     * @throws PrestaShopException
242     * @since   1.0.0
243     * @version 1.0.0 Initial version
244     */
245    public function delete()
246    {
247        if (static::hasDependency($this->id) || !$res = parent::delete()) {
248            return false;
249        }
250
251        foreach (static::getAssoTables() as $tableName => $row) {
252            $id = 'id_'.$row['type'];
253            if ($row['type'] == 'fk_shop') {
254                $id = 'id_shop';
255            } else {
256                $tableName .= '_'.$row['type'];
257            }
258            $res &= Db::getInstance()->delete(bqSQL($tableName), '`'.bqSQL($id).'`='.(int) $this->id);
259        }
260
261        // removes stock available
262        $res &= Db::getInstance()->delete('stock_available', '`id_shop` = '.(int) $this->id);
263
264        // Remove urls
265        $res &= Db::getInstance()->delete('shop_url', '`id_shop` = '.(int) $this->id);
266
267        // Remove currency restrictions
268        $res &= Db::getInstance()->delete('module_currency', '`id_shop` = '.(int) $this->id);
269
270        // Remove group restrictions
271        $res &= Db::getInstance()->delete('module_group', '`id_shop` = '.(int) $this->id);
272
273        // Remove country restrictions
274        $res &= Db::getInstance()->delete('module_country', '`id_shop` = '.(int) $this->id);
275
276        // Remove carrier restrictions
277        $res &= Db::getInstance()->delete('module_carrier', '`id_shop` = '.(int) $this->id);
278
279        static::cacheShops(true);
280
281        return $res;
282    }
283
284    /**
285     * Detect dependency with customer or orders
286     *
287     * @param int $idShop
288     *
289     * @return bool
290     *
291     * @since   1.0.0
292     * @version 1.0.0 Initial version
293     * @throws PrestaShopException
294     */
295    public static function hasDependency($idShop)
296    {
297        $hasDependency = false;
298        $nbrCustomer = (int) Db::getInstance(_PS_USE_SQL_SLAVE_)->getValue(
299            (new DbQuery())
300                ->select('COUNT(*)')
301                ->from('customer')
302                ->where('`id_shop` = '.(int) $idShop)
303        );
304        if ($nbrCustomer) {
305            $hasDependency = true;
306        } else {
307            $nbrOrder = (int) Db::getInstance(_PS_USE_SQL_SLAVE_)->getValue(
308                (new DbQuery())
309                    ->select('COUNT(*)')
310                    ->from('orders')
311                    ->where('`id_shop` = '.(int) $idShop)
312            );
313            if ($nbrOrder) {
314                $hasDependency = true;
315            }
316        }
317
318        return $hasDependency;
319    }
320
321    /**
322     * Find the shop from current domain / uri and get an instance of this shop
323     *
324     * @return Shop
325     * @throws PrestaShopException
326     * @since   1.0.0
327     * @version 1.0.0 Initial version
328     */
329    public static function initialize()
330    {
331        // Find current shop from URL
332        if (!($idShop = Tools::getValue('id_shop')) || defined('_PS_ADMIN_DIR_')) {
333            $foundUri = '';
334            $isMainUri = false;
335            $host = Tools::getHttpHost();
336            $requestUri = rawurldecode($_SERVER['REQUEST_URI']);
337
338            $result = Db::getInstance(_PS_USE_SQL_SLAVE_)->executeS(
339                (new DbQuery())
340                    ->select('s.`id_shop`, CONCAT(su.`physical_uri`, su.`virtual_uri`) AS `uri`, su.`domain`, su.`main`')
341                    ->from('shop_url', 'su')
342                    ->leftJoin('shop', 's', 's.`id_shop` = su.`id_shop`')
343                    ->where('su.domain = \''.pSQL($host).'\' OR su.domain_ssl = \''.pSQL($host).'\'')
344                    ->where('s.`active` = 1')
345                    ->where('s.`deleted` = 0')
346                    ->orderBy('LENGTH(CONCAT(su.`physical_uri`, su.`virtual_uri`)) DESC')
347            );
348
349            $through = false;
350            foreach ($result as $row) {
351                // An URL matching current shop was found
352                if (preg_match('#^'.preg_quote($row['uri'], '#').'#i', $requestUri)) {
353                    $through = true;
354                    $idShop = $row['id_shop'];
355                    $foundUri = $row['uri'];
356                    if ($row['main']) {
357                        $isMainUri = true;
358                    }
359                    break;
360                }
361            }
362
363            // If an URL was found but is not the main URL, redirect to main URL
364            if ($through && $idShop && !$isMainUri) {
365                foreach ($result as $row) {
366                    if ($row['id_shop'] == $idShop && $row['main']) {
367                        $requestUri = substr($requestUri, strlen($foundUri));
368                        $url = str_replace('//', '/', $row['domain'].$row['uri'].$requestUri);
369                        $redirectType = Configuration::get('PS_CANONICAL_REDIRECT');
370                        $redirectCode = ($redirectType == 1 ? '302' : '301');
371                        $redirectHeader = ($redirectType == 1 ? 'Found' : 'Moved Permanently');
372                        header('HTTP/1.0 '.$redirectCode.' '.$redirectHeader);
373                        header('Cache-Control: no-cache');
374                        header('Location: '.Tools::getShopProtocol().$url);
375                        exit;
376                    }
377                }
378            }
379        }
380
381        $httpHost = Tools::getHttpHost();
382        $allMedia = array_merge(Configuration::getMultiShopValues('PS_MEDIA_SERVER_1'), Configuration::getMultiShopValues('PS_MEDIA_SERVER_2'), Configuration::getMultiShopValues('PS_MEDIA_SERVER_3'));
383
384        if ((!$idShop && defined('_PS_ADMIN_DIR_')) || Tools::isPHPCLI() || in_array($httpHost, $allMedia)) {
385            // If in admin, we can access to the shop without right URL
386            if ((!$idShop && Tools::isPHPCLI()) || defined('_PS_ADMIN_DIR_')) {
387                $idShop = (int) Configuration::get('PS_SHOP_DEFAULT');
388            }
389
390            $shop = new Shop((int) $idShop);
391            if (!Validate::isLoadedObject($shop)) {
392                $shop = new Shop((int) Configuration::get('PS_SHOP_DEFAULT'));
393            }
394
395            $shop->virtual_uri = '';
396
397            // Define some $_SERVER variables like HTTP_HOST if PHP is launched with php-cli
398            if (Tools::isPHPCLI()) {
399                if (!isset($_SERVER['HTTP_HOST']) || empty($_SERVER['HTTP_HOST'])) {
400                    $_SERVER['HTTP_HOST'] = $shop->domain;
401                }
402                if (!isset($_SERVER['SERVER_NAME']) || empty($_SERVER['SERVER_NAME'])) {
403                    $_SERVER['SERVER_NAME'] = $shop->domain;
404                }
405                if (!isset($_SERVER['REMOTE_ADDR']) || empty($_SERVER['REMOTE_ADDR'])) {
406                    $_SERVER['REMOTE_ADDR'] = '127.0.0.1';
407                }
408            }
409        } else {
410            $shop = new Shop($idShop);
411            if (!Validate::isLoadedObject($shop) || !$shop->active) {
412                // No shop found ... too bad, let's redirect to default shop
413                $defaultShop = new Shop(Configuration::get('PS_SHOP_DEFAULT'));
414
415                // Hmm there is something really bad in your Prestashop !
416                if (!Validate::isLoadedObject($defaultShop)) {
417                    throw new PrestaShopException('Shop not found');
418                }
419
420                $params = $_GET;
421                unset($params['id_shop']);
422                $url = $defaultShop->domain;
423                if (!Configuration::get('PS_REWRITING_SETTINGS')) {
424                    $url .= $defaultShop->getBaseURI().'index.php?'.http_build_query($params);
425                } else {
426                    // Catch url with subdomain "www"
427                    if (strpos($url, 'www.') === 0 && 'www.'.$_SERVER['HTTP_HOST'] === $url || $_SERVER['HTTP_HOST'] === 'www.'.$url) {
428                        $url .= $_SERVER['REQUEST_URI'];
429                    } else {
430                        $url .= $defaultShop->getBaseURI();
431                    }
432
433                    if (count($params)) {
434                        $url .= '?'.http_build_query($params);
435                    }
436                }
437
438                $redirectType = Configuration::get('PS_CANONICAL_REDIRECT');
439                $redirectCode = ($redirectType == 1 ? '302' : '301');
440                $redirectHeader = ($redirectType == 1 ? 'Found' : 'Moved Permanently');
441                header('HTTP/1.0 '.$redirectCode.' '.$redirectHeader);
442                header('Location: '.Tools::getShopProtocol().$url);
443                exit;
444            } elseif (defined('_PS_ADMIN_DIR_') && empty($shop->physical_uri)) {
445                $shopDefault = new Shop((int) Configuration::get('PS_SHOP_DEFAULT'));
446                $shop->physical_uri = $shopDefault->physical_uri;
447                $shop->virtual_uri = $shopDefault->virtual_uri;
448            }
449        }
450
451        // @codingStandardsIgnoreStart
452        static::$context_id_shop = $shop->id;
453        static::$context_id_shop_group = $shop->id_shop_group;
454        static::$context = static::CONTEXT_SHOP;
455        // @codingStandardsIgnoreEnd
456
457        return $shop;
458    }
459
460    /**
461     * @return Address the current shop address
462     *
463     * @since   1.0.0
464     * @version 1.0.0 Initial version
465     * @throws PrestaShopException
466     */
467    public function getAddress()
468    {
469        if (!isset($this->address)) {
470            $address = new Address();
471            $address->company = Configuration::get('PS_SHOP_NAME');
472            $address->id_country = Configuration::get('PS_SHOP_COUNTRY_ID') ? Configuration::get('PS_SHOP_COUNTRY_ID') : Configuration::get('PS_COUNTRY_DEFAULT');
473            $address->id_state = Configuration::get('PS_SHOP_STATE_ID');
474            $address->address1 = Configuration::get('PS_SHOP_ADDR1');
475            $address->address2 = Configuration::get('PS_SHOP_ADDR2');
476            $address->postcode = Configuration::get('PS_SHOP_CODE');
477            $address->city = Configuration::get('PS_SHOP_CITY');
478
479            $this->address = $address;
480        }
481
482        return $this->address;
483    }
484
485    /**
486     * Get shop theme name
487     *
488     * @return string
489     *
490     * @since   1.0.0
491     * @version 1.0.0 Initial version
492     */
493    public function getTheme()
494    {
495        return $this->theme_directory;
496    }
497
498    /**
499     * Get shop URI
500     *
501     * @return string
502     *
503     * @since   1.0.0
504     * @version 1.0.0 Initial version
505     */
506    public function getBaseURI()
507    {
508        return $this->physical_uri.$this->virtual_uri;
509    }
510
511    /**
512     * Get shop URL
513     *
514     * @param bool|string $autoSecureMode if set to true, secure mode will be checked
515     * @param bool|string $addBaseUri     if set to true, shop base uri will be added
516     *
517     * @return string complete base url of current shop
518     *
519     * @since   1.0.0
520     * @version 1.0.0 Initial version
521     */
522    public function getBaseURL($autoSecureMode = false, $addBaseUri = true)
523    {
524        if (($autoSecureMode && Tools::usingSecureMode() && !$this->domain_ssl) || !$this->domain) {
525            return false;
526        }
527
528        $url = [];
529        $url['protocol'] = $autoSecureMode && Tools::usingSecureMode() ? 'https://' : 'http://';
530        $url['domain'] = $autoSecureMode && Tools::usingSecureMode() ? $this->domain_ssl : $this->domain;
531
532        if ($addBaseUri) {
533            $url['base_uri'] = $this->getBaseURI();
534        }
535
536        return implode('', $url);
537    }
538
539    /**
540     * Get group of current shop
541     *
542     * @return ShopGroup
543     *
544     * @since   1.0.0
545     * @version 1.0.0 Initial version
546     */
547    public function getGroup()
548    {
549        if (!$this->group) {
550            $this->group = new ShopGroup($this->id_shop_group);
551        }
552
553        return $this->group;
554    }
555
556    /**
557     * Get root category of current shop
558     *
559     * @return int
560     *
561     * @since   1.0.0
562     * @version 1.0.0 Initial version
563     * @throws PrestaShopException
564     */
565    public function getCategory()
566    {
567        return (int) ($this->id_category ? $this->id_category : Configuration::get('PS_ROOT_CATEGORY'));
568    }
569
570    /**
571     * Get list of shop's urls
572     *
573     * @return array
574     *
575     * @throws PrestaShopDatabaseException
576     * @throws PrestaShopException
577     * @since   1.0.0
578     * @version 1.0.0 Initial version
579     */
580    public function getUrls()
581    {
582        return Db::getInstance(_PS_USE_SQL_SLAVE_)->executeS(
583            (new DbQuery())
584                ->select('*')
585                ->from('shop_url')
586                ->where('`active` = 1')
587                ->where('`id_shop` = '.(int) $this->id)
588        );
589    }
590
591    /**
592     * Check if current shop ID is the same as default shop in configuration
593     *
594     * @return bool
595     *
596     * @since   1.0.0
597     * @version 1.0.0 Initial version
598     * @throws PrestaShopException
599     */
600    public function isDefaultShop()
601    {
602        return $this->id == Configuration::get('PS_SHOP_DEFAULT');
603    }
604
605    /**
606     * Get the associated table if available
607     *
608     * @param string $table
609     *
610     * @return false|array
611     * @since   1.0.0
612     * @version 1.0.0 Initial version
613     */
614    public static function getAssoTable($table)
615    {
616        if (!static::$initialized) {
617            static::init();
618        }
619
620        // @codingStandardsIgnoreStart
621        return (isset(static::$asso_tables[$table]) ? static::$asso_tables[$table] : false);
622        // @codingStandardsIgnoreEnd
623    }
624
625    /**
626     * check if the table has an id_shop_default
627     *
628     * @param string $table
629     *
630     * @return bool
631     * @since   1.0.0
632     * @version 1.0.0 Initial version
633     */
634    public static function checkIdShopDefault($table)
635    {
636        if (!static::$initialized) {
637            static::init();
638        }
639
640        // @codingStandardsIgnoreStart
641        return in_array($table, static::$id_shop_default_tables);
642        // @codingStandardsIgnoreEnd
643    }
644
645    /**
646     * Get list of associated tables to shop
647     *
648     * @return array
649     *
650     * @since   1.0.0
651     * @version 1.0.0 Initial version
652     */
653    public static function getAssoTables()
654    {
655        if (!static::$initialized) {
656            static::init();
657        }
658
659        // @codingStandardsIgnoreStart
660        return static::$asso_tables;
661        // @codingStandardsIgnoreEnd
662    }
663
664    /**
665     * Add table associated to shop
666     *
667     * @param string $tableName
668     * @param array  $tableDetails
669     *
670     * @return bool
671     *
672     * @since   1.0.0
673     * @version 1.0.0 Initial version
674     */
675    public static function addTableAssociation($tableName, $tableDetails)
676    {
677        // @codingStandardsIgnoreStart
678        if (!isset(static::$asso_tables[$tableName])) {
679            static::$asso_tables[$tableName] = $tableDetails;
680        } else {
681            return false;
682        }
683        // @codingStandardsIgnoreEnd
684
685        return true;
686    }
687
688    /**
689     * Check if given table is associated to shop
690     *
691     * @param string $table
692     *
693     * @return bool
694     *
695     * @since   1.0.0
696     * @version 1.0.0 Initial version
697     */
698    public static function isTableAssociated($table)
699    {
700        if (!static::$initialized) {
701            static::init();
702        }
703
704        // @codingStandardsIgnoreStart
705        return isset(static::$asso_tables[$table]) && static::$asso_tables[$table]['type'] == 'shop';
706        // @codingStandardsIgnoreEnd
707    }
708
709    /**
710     * Load list of groups and shops, and cache it
711     *
712     * @param bool $refresh
713     *
714     * @throws PrestaShopDatabaseException
715     * @throws PrestaShopException
716     * @since   1.0.0
717     * @version 1.0.0 Initial version
718     */
719    public static function cacheShops($refresh = false)
720    {
721        if (!is_null(static::$shops) && !$refresh) {
722            return;
723        }
724
725        static::$shops = [];
726
727        $employee = Context::getContext()->employee;
728
729        $sql = (new DbQuery())
730            ->select('gs.*, s.*, gs.`name` AS `group_name`, s.`name` AS `shop_name`, s.`active`')
731            ->select('su.`domain`, su.`domain_ssl`, su.`physical_uri`, su.`virtual_uri`')
732            ->from('shop_group', 'gs')
733            ->leftJoin('shop', 's', 's.`id_shop_group` = gs.`id_shop_group`')
734            ->leftJoin('shop_url', 'su', 's.`id_shop` = su.`id_shop` AND su.`main` = 1')
735            ->where('s.`deleted` = 0')
736            ->where('gs.`deleted` = 0')
737            ->orderBy('gs.`name`, s.`name`')
738        ;
739
740        // If the profile isn't a superAdmin
741        if (Validate::isLoadedObject($employee) && $employee->id_profile != _PS_ADMIN_PROFILE_) {
742            $sql->leftJoin('employee_shop', 'es', 'es.`id_shop` = s.`id_shop`');
743            $sql->where('es.`id_employee` = '.(int) $employee->id);
744        }
745
746        if ($results = Db::getInstance(_PS_USE_SQL_SLAVE_)->executeS($sql)) {
747            foreach ($results as $row) {
748                if (!isset(static::$shops[$row['id_shop_group']])) {
749                    static::$shops[$row['id_shop_group']] = [
750                        'id'             => $row['id_shop_group'],
751                        'name'           => $row['group_name'],
752                        'share_customer' => $row['share_customer'],
753                        'share_order'    => $row['share_order'],
754                        'share_stock'    => $row['share_stock'],
755                        'shops'          => [],
756                    ];
757                }
758
759                static::$shops[$row['id_shop_group']]['shops'][$row['id_shop']] = [
760                    'id_shop'       => $row['id_shop'],
761                    'id_shop_group' => $row['id_shop_group'],
762                    'name'          => $row['shop_name'],
763                    'id_theme'      => $row['id_theme'],
764                    'id_category'   => $row['id_category'],
765                    'domain'        => $row['domain'],
766                    'domain_ssl'    => $row['domain_ssl'],
767                    'uri'           => $row['physical_uri'].$row['virtual_uri'],
768                    'active'        => $row['active'],
769                ];
770            }
771        }
772    }
773
774    /**
775     * @return array|null
776     *
777     * @throws PrestaShopDatabaseException
778     * @throws PrestaShopException
779     * @since   1.0.0
780     * @version 1.0.0 Initial version
781     */
782    public static function getCompleteListOfShopsID()
783    {
784        $cacheId = 'Shop::getCompleteListOfShopsID';
785        if (!Cache::isStored($cacheId)) {
786            $list = [];
787            foreach (Db::getInstance(_PS_USE_SQL_SLAVE_)->executeS((new DbQuery())->select('`id_shop`')->from('shop')) as $row) {
788                $list[] = $row['id_shop'];
789            }
790
791            Cache::store($cacheId, $list);
792
793            return $list;
794        }
795
796        return Cache::retrieve($cacheId);
797    }
798
799    /**
800     * Get shops list
801     *
802     * @param bool $active
803     * @param int  $idShopGroup
804     * @param bool $getAsListId
805     *
806     * @return array
807     *
808     * @throws PrestaShopDatabaseException
809     * @throws PrestaShopException
810     * @since   1.0.0
811     * @version 1.0.0 Initial version
812     */
813    public static function getShops($active = true, $idShopGroup = null, $getAsListId = false)
814    {
815        static::cacheShops();
816
817        $results = [];
818        foreach (static::$shops as $idGroup => $groupData) {
819            foreach ($groupData['shops'] as $id => $shopData) {
820                if ((!$active || $shopData['active']) && (!$idShopGroup || $idShopGroup == $idGroup)) {
821                    if ($getAsListId) {
822                        $results[$id] = $id;
823                    } else {
824                        $results[$id] = $shopData;
825                    }
826                }
827            }
828        }
829
830        return $results;
831    }
832
833    /**
834     * @return array|bool
835     *
836     * @throws PrestaShopDatabaseException
837     * @throws PrestaShopException
838     * @since   1.0.0
839     * @version 1.0.0 Initial version
840     */
841    public function getUrlsSharedCart()
842    {
843        if (!$this->getGroup()->share_order) {
844            return false;
845        }
846
847        $query = new DbQuery();
848        $query->select('domain');
849        $query->from('shop_url');
850        $query->where('main = 1');
851        $query->where('active = 1');
852        $query .= $this->addSqlRestriction(self::SHARE_ORDER);
853        $domains = [];
854        foreach (Db::getInstance()->executeS($query) as $row) {
855            $domains[] = $row['domain'];
856        }
857
858        return $domains;
859    }
860
861    /**
862     * Get a collection of shops
863     *
864     * @param bool $active
865     * @param int  $idShopGroup
866     *
867     * @return PrestaShopCollection Collection of Shop
868     *
869     * @since   1.0.0
870     * @version 1.0.0 Initial version
871     * @throws PrestaShopException
872     */
873    public static function getShopsCollection($active = true, $idShopGroup = null)
874    {
875        $shops = new PrestaShopCollection('Shop');
876        if ($active) {
877            $shops->where('active', '=', 1);
878        }
879
880        if ($idShopGroup) {
881            $shops->where('id_shop_group', '=', (int) $idShopGroup);
882        }
883
884        return $shops;
885    }
886
887    /**
888     * Return some informations cached for one shop
889     *
890     * @param int $shopId
891     *
892     * @return false|array
893     *
894     * @throws PrestaShopDatabaseException
895     * @throws PrestaShopException
896     * @since   1.0.0
897     * @version 1.0.0 Initial version
898     */
899    public static function getShop($shopId)
900    {
901        static::cacheShops();
902        foreach (static::$shops as $idGroup => $groupData) {
903            if (array_key_exists($shopId, $groupData['shops'])) {
904                return $groupData['shops'][$shopId];
905            }
906        }
907
908        return false;
909    }
910
911    /**
912     * Return a shop ID from shop name
913     *
914     * @param string $name
915     *
916     * @return int
917     *
918     * @throws PrestaShopDatabaseException
919     * @throws PrestaShopException
920     * @since   1.0.0
921     * @version 1.0.0 Initial version
922     */
923    public static function getIdByName($name)
924    {
925        static::cacheShops();
926        foreach (static::$shops as $groupData) {
927            foreach ($groupData['shops'] as $idShop => $shopData) {
928                if (mb_strtolower($shopData['name']) == mb_strtolower($name)) {
929                    return $idShop;
930                }
931            }
932        }
933
934        return false;
935    }
936
937    /**
938     * @param bool $active
939     * @param int  $idShopGroup
940     *
941     * @return int Total of shops
942     *
943     * @since   1.0.0
944     * @version 1.0.0 Initial version
945     */
946    public static function getTotalShops($active = true, $idShopGroup = null)
947    {
948        return count(static::getShops($active, $idShopGroup));
949    }
950
951    /**
952     * Retrieve group ID of a shop
953     *
954     * @param int  $shopId Shop ID
955     * @param bool $asId
956     *
957     * @return int Group ID
958     *
959     * @throws PrestaShopDatabaseException
960     * @throws PrestaShopException
961     * @since   1.0.0
962     * @version 1.0.0 Initial version
963     */
964    public static function getGroupFromShop($shopId, $asId = true)
965    {
966        static::cacheShops();
967        foreach (static::$shops as $groupId => $groupData) {
968            if (array_key_exists($shopId, $groupData['shops'])) {
969                return ($asId) ? $groupId : $groupData;
970            }
971        }
972
973        return false;
974    }
975
976    /**
977     * If the shop group has the option $type activated, get all shops ID of this group, else get current shop ID
978     *
979     * @param int $shopId
980     * @param int $type   self::SHARE_CUSTOMER | self::SHARE_ORDER
981     *
982     * @return array
983     * @throws PrestaShopException
984     * @since   1.0.0
985     * @version 1.0.0 Initial version
986     */
987    public static function getSharedShops($shopId, $type)
988    {
989        if (!in_array($type, [self::SHARE_CUSTOMER, self::SHARE_ORDER, self::SHARE_STOCK])) {
990            throw new PrestaShopException('Wrong argument ($type) in Shop::getSharedShops() method');
991        }
992
993        static::cacheShops();
994        foreach (static::$shops as $groupData) {
995            if (array_key_exists($shopId, $groupData['shops']) && $groupData[$type]) {
996                return array_keys($groupData['shops']);
997            }
998        }
999
1000        return [$shopId];
1001    }
1002
1003    /**
1004     * Get a list of ID concerned by the shop context (E.g. if context is shop group, get list of children shop ID)
1005     *
1006     * @param bool|string $share If false, dont check share datas from group. Else can take a Shop::SHARE_* constant value
1007     *
1008     * @return array
1009     *
1010     * @since   1.0.0
1011     * @version 1.0.0 Initial version
1012     * @throws PrestaShopException
1013     */
1014    public static function getContextListShopID($share = false)
1015    {
1016        if (static::getContext() == self::CONTEXT_SHOP) {
1017            $list = ($share) ? static::getSharedShops(static::getContextShopID(), $share) : [static::getContextShopID()];
1018        } elseif (static::getContext() == self::CONTEXT_GROUP) {
1019            $list = static::getShops(true, static::getContextShopGroupID(), true);
1020        } else {
1021            $list = static::getShops(true, null, true);
1022        }
1023
1024        return $list;
1025    }
1026
1027    /**
1028     * Return the list of shop by id
1029     *
1030     * @param int    $id
1031     * @param string $identifier
1032     * @param string $table
1033     *
1034     * @return array
1035     *
1036     * @throws PrestaShopDatabaseException
1037     * @throws PrestaShopException
1038     * @since   1.0.0
1039     * @version 1.0.0 Initial version
1040     */
1041    public static function getShopById($id, $identifier, $table)
1042    {
1043        return Db::getInstance(_PS_USE_SQL_SLAVE_)->executeS(
1044            (new DbQuery())
1045                ->select('`id_shop`, `'.bqSQL($identifier).'`')
1046                ->from(bqSQL($table).'_shop')
1047                ->where('`'.bqSQL($identifier).'` = '.(int) $id)
1048        );
1049    }
1050
1051    /**
1052     * Change the current shop context
1053     *
1054     * @param int $type Shop::CONTEXT_ALL | Shop::CONTEXT_GROUP | Shop::CONTEXT_SHOP
1055     * @param int $id   ID shop if CONTEXT_SHOP or id shop group if CONTEXT_GROUP
1056     *
1057     * @throws PrestaShopException
1058     * @since   1.0.0
1059     * @version 1.0.0 Initial version
1060     */
1061    public static function setContext($type, $id = null)
1062    {
1063        // @codingStandardsIgnoreStart
1064        switch ($type) {
1065            case static::CONTEXT_ALL :
1066                static::$context_id_shop = null;
1067                static::$context_id_shop_group = null;
1068                break;
1069
1070            case static::CONTEXT_GROUP :
1071                static::$context_id_shop = null;
1072                static::$context_id_shop_group = (int) $id;
1073                break;
1074
1075            case static::CONTEXT_SHOP :
1076                static::$context_id_shop = (int) $id;
1077                static::$context_id_shop_group = static::getGroupFromShop($id);
1078                break;
1079
1080            default :
1081                throw new PrestaShopException('Unknown context for shop');
1082        }
1083        // @codingStandardsIgnoreEnd
1084
1085        static::$context = $type;
1086    }
1087
1088    /**
1089     * Get current context of shop
1090     *
1091     * @return int
1092     *
1093     * @since   1.0.0
1094     * @version 1.0.0 Initial version
1095     */
1096    public static function getContext()
1097    {
1098        return static::$context;
1099    }
1100
1101    /**
1102     * Get current ID of shop if context is CONTEXT_SHOP
1103     *
1104     * @param bool $nullValueWithoutMultishop
1105     *
1106     * @return int
1107     * @since   1.0.0
1108     * @version 1.0.0 Initial version
1109     */
1110    public static function getContextShopID($nullValueWithoutMultishop = false)
1111    {
1112        if ($nullValueWithoutMultishop && !static::isFeatureActive()) {
1113            return null;
1114        }
1115
1116        // @codingStandardsIgnoreStart
1117        return static::$context_id_shop;
1118        // @codingStandardsIgnoreEnd
1119    }
1120
1121    /**
1122     * Get current ID of shop group if context is CONTEXT_SHOP or CONTEXT_GROUP
1123     *
1124     * @param bool $nullValueWithoutMultishop
1125     *
1126     * @return int
1127     * @since   1.0.0
1128     * @version 1.0.0 Initial version
1129     */
1130    public static function getContextShopGroupID($nullValueWithoutMultishop = false)
1131    {
1132        if ($nullValueWithoutMultishop && !static::isFeatureActive()) {
1133            return null;
1134        }
1135
1136        // @codingStandardsIgnoreStart
1137        return static::$context_id_shop_group;
1138        // @codingStandardsIgnoreEnd
1139    }
1140
1141    public static function getContextShopGroup()
1142    {
1143        static $contextShopGroup = null;
1144        if ($contextShopGroup === null) {
1145            // @codingStandardsIgnoreStart
1146            $contextShopGroup = new ShopGroup((int) static::$context_id_shop_group);
1147            // @codingStandardsIgnoreEnd
1148        }
1149
1150        return $contextShopGroup;
1151    }
1152
1153    /**
1154     * Add an sql restriction for shops fields
1155     *
1156     * @param bool   $share If false, dont check share datas from group. Else can take a Shop::SHARE_* constant value
1157     * @param string $alias
1158     *
1159     * @return string
1160     * @throws PrestaShopDatabaseException
1161     * @throws PrestaShopException
1162     * @since   1.0.0
1163     * @version 1.0.0 Initial version
1164     */
1165    public static function addSqlRestriction($share = false, $alias = null)
1166    {
1167        if ($alias) {
1168            $alias .= '.';
1169        }
1170
1171        $group = static::getGroupFromShop(static::getContextShopID(), false);
1172        if ($share == self::SHARE_CUSTOMER && static::getContext() == self::CONTEXT_SHOP && $group['share_customer']) {
1173            $restriction = ' AND '.$alias.'id_shop_group = '.(int) static::getContextShopGroupID().' ';
1174        } else {
1175            $restriction = ' AND '.$alias.'id_shop IN ('.implode(', ', static::getContextListShopID($share)).') ';
1176        }
1177
1178        return $restriction;
1179    }
1180
1181    /**
1182     * Add an SQL JOIN in query between a table and its associated table in multishop
1183     *
1184     * @param string $table     Table name (E.g. product, module, etc.)
1185     * @param string $alias     Alias of table
1186     * @param bool   $innerJoin Use or not INNER JOIN
1187     * @param string $on
1188     *
1189     * @return string
1190     *
1191     * @since   1.0.0
1192     * @version 1.0.0 Initial version
1193     * @throws PrestaShopException
1194     */
1195    public static function addSqlAssociation($table, $alias, $innerJoin = true, $on = null, $forceNotDefault = false)
1196    {
1197        $tableAlias = $table.'_shop';
1198        if (strpos($table, '.') !== false) {
1199            list($tableAlias, $table) = explode('.', $table);
1200        }
1201
1202        $assoTable = static::getAssoTable($table);
1203        if ($assoTable === false || $assoTable['type'] != 'shop') {
1204            return '';
1205        }
1206        $sql = (($innerJoin) ? ' INNER' : ' LEFT').' JOIN '._DB_PREFIX_.$table.'_shop '.$tableAlias.'
1207		ON ('.$tableAlias.'.id_'.$table.' = '.$alias.'.id_'.$table;
1208        // @codingStandardsIgnoreStart
1209        if ((int) static::$context_id_shop) {$sql .= ' AND '.$tableAlias.'.id_shop = '.(int) static::$context_id_shop;
1210        } elseif (static::checkIdShopDefault($table) && !$forceNotDefault) {
1211            $sql .= ' AND '.$tableAlias.'.id_shop = '.$alias.'.id_shop_default';
1212        } else {
1213            $sql .= ' AND '.$tableAlias.'.id_shop IN ('.implode(', ', static::getContextListShopID()).')';
1214        }
1215        $sql .= (($on) ? ' AND '.$on : '').')';
1216        // @codingStandardsIgnoreEnd
1217
1218        return $sql;
1219    }
1220
1221    /**
1222     * Add a restriction on id_shop for multishop lang table
1223     *
1224     * @param string $alias
1225     * @param null   $idShop
1226     *
1227     * @return string
1228     *
1229     * @since    1.0.0
1230     * @version  1.0.0 Initial version
1231     * @throws PrestaShopException
1232     */
1233    public static function addSqlRestrictionOnLang($alias = null, $idShop = null)
1234    {
1235        if (isset(Context::getContext()->shop) && is_null($idShop)) {
1236            $idShop = (int) Context::getContext()->shop->id;
1237        }
1238        if (!$idShop) {
1239            $idShop = (int) Configuration::get('PS_SHOP_DEFAULT');
1240        }
1241
1242        return ' AND '.(($alias) ? $alias.'.' : '').'id_shop = '.$idShop.' ';
1243    }
1244
1245    /**
1246     * Get all groups and associated shops as subarrays
1247     *
1248     * @return array
1249     *
1250     * @throws PrestaShopDatabaseException
1251     * @throws PrestaShopException
1252     * @since   1.0.0
1253     * @version 1.0.0 Initial version
1254     */
1255    public static function getTree()
1256    {
1257        static::cacheShops();
1258
1259        return static::$shops;
1260    }
1261
1262    /**
1263     * @return bool Return true if multishop feature is active and at last 2 shops have been created
1264     *
1265     * @since   1.0.0
1266     * @version 1.0.0 Initial version
1267     * @throws PrestaShopException
1268     */
1269    public static function isFeatureActive()
1270    {
1271        static $featureActive = null;
1272
1273        if ($featureActive === null) {
1274            $featureActive = (bool) Db::getInstance()->getValue('SELECT value FROM `'._DB_PREFIX_.'configuration` WHERE `name` = "PS_MULTISHOP_FEATURE_ACTIVE"')
1275                && (Db::getInstance()->getValue('SELECT COUNT(*) FROM '._DB_PREFIX_.'shop') > 1);
1276        }
1277
1278        return $featureActive;
1279    }
1280
1281    /**
1282     * @param int  $oldId
1283     * @param bool $tablesImport
1284     * @param bool $deleted
1285     *
1286     * @throws PrestaShopDatabaseException
1287     * @throws PrestaShopException
1288     * @since   1.0.0
1289     * @version 1.0.0 Initial version
1290     */
1291    public function copyShopData($oldId, $tablesImport = false, $deleted = false)
1292    {
1293        // If we duplicate some specific data, automatically duplicate other data linked to the first
1294        // E.g. if carriers are duplicated for the shop, duplicate carriers langs too
1295
1296        if (!$oldId) {
1297            $oldId = Configuration::get('PS_SHOP_DEFAULT');
1298        }
1299
1300        if (isset($tablesImport['carrier'])) {
1301            $tablesImport['carrier_tax_rules_group_shop'] = true;
1302            $tablesImport['carrier_lang'] = true;
1303        }
1304
1305        if (isset($tablesImport['cms'])) {
1306            $tablesImport['cms_lang'] = true;
1307            $tablesImport['cms_category'] = true;
1308            $tablesImport['cms_category_lang'] = true;
1309        }
1310
1311        $tablesImport['category_lang'] = true;
1312        if (isset($tablesImport['product'])) {
1313            $tablesImport['product_lang'] = true;
1314        }
1315
1316        if (isset($tablesImport['module'])) {
1317            $tablesImport['module_currency'] = true;
1318            $tablesImport['module_country'] = true;
1319            $tablesImport['module_group'] = true;
1320        }
1321
1322        if (isset($tablesImport['hook_module'])) {
1323            $tablesImport['hook_module_exceptions'] = true;
1324        }
1325
1326        if (isset($tablesImport['attribute_group'])) {
1327            $tablesImport['attribute'] = true;
1328        }
1329
1330        // Browse and duplicate data
1331        foreach (static::getAssoTables() as $tableName => $row) {
1332            if ($tablesImport && !isset($tablesImport[$tableName])) {
1333                continue;
1334            }
1335
1336            // Special case for stock_available if current shop is in a share stock group
1337            if ($tableName == 'stock_available') {
1338                $group = new ShopGroup($this->id_shop_group);
1339                if ($group->share_stock && $group->haveShops()) {
1340                    continue;
1341                }
1342            }
1343
1344            $id = 'id_'.$row['type'];
1345            if ($row['type'] == 'fk_shop') {
1346                $id = 'id_shop';
1347            } else {
1348                $tableName .= '_'.$row['type'];
1349            }
1350
1351            if (!$deleted) {
1352                $res = Db::getInstance()->getRow('SELECT * FROM `'._DB_PREFIX_.$tableName.'` WHERE `'.$id.'` = '.(int) $oldId);
1353                if ($res) {
1354                    unset($res[$id]);
1355                    if (isset($row['primary'])) {
1356                        unset($res[$row['primary']]);
1357                    }
1358
1359                    $categories = Tools::getValue('categoryBox');
1360                    if ($tableName == 'product_shop' && count($categories) == 1) {
1361                        unset($res['id_category_default']);
1362                        $keys = implode('`, `', array_keys($res));
1363                        $sql = 'INSERT IGNORE INTO `'._DB_PREFIX_.$tableName.'` (`'.$keys.'`, `id_category_default`, '.$id.')
1364								(SELECT `'.$keys.'`, '.(int) $categories[0].', '.(int) $this->id.' FROM '._DB_PREFIX_.$tableName.'
1365								WHERE `'.$id.'` = '.(int) $oldId.')';
1366                    } else {
1367                        $keys = implode('`, `', array_keys($res));
1368                        $sql = 'INSERT IGNORE INTO `'._DB_PREFIX_.$tableName.'` (`'.$keys.'`, '.$id.')
1369								(SELECT `'.$keys.'`, '.(int) $this->id.' FROM '._DB_PREFIX_.$tableName.'
1370								WHERE `'.$id.'` = '.(int) $oldId.')';
1371                    }
1372                    Db::getInstance()->execute($sql);
1373                }
1374            }
1375        }
1376
1377        // Hook for duplication of shop data
1378        $modulesList = Hook::getHookModuleExecList('actionShopDataDuplication');
1379        if (is_array($modulesList) && count($modulesList) > 0) {
1380            foreach ($modulesList as $m) {
1381                if (!$tablesImport || isset($tablesImport['Module'.ucfirst($m['module'])])) {
1382                    Hook::exec(
1383                        'actionShopDataDuplication',
1384                        [
1385                            'old_id_shop' => (int) $oldId,
1386                            'new_id_shop' => (int) $this->id,
1387                        ],
1388                        $m['id_module']
1389                    );
1390                }
1391            }
1392        }
1393    }
1394
1395    /**
1396     * @param int  $id
1397     * @param bool $onlyId
1398     *
1399     * @return array
1400     * @throws PrestaShopDatabaseException
1401     * @throws PrestaShopException
1402     * @since   1.0.0
1403     * @version 1.0.0 Initial version
1404     */
1405    public static function getCategories($id = 0, $onlyId = true)
1406    {
1407        // build query
1408        $query = new DbQuery();
1409        if ($onlyId) {
1410            $query->select('cs.`id_category`');
1411        } else {
1412            $query->select('DISTINCT cs.`id_category`, cl.`name`, cl.`link_rewrite`');
1413        }
1414        $query->from('category_shop', 'cs');
1415        $query->leftJoin('category_lang', 'cl', 'cl.`id_category` = cs.`id_category` AND cl.`id_lang` = '.(int) Context::getContext()->language->id);
1416        $query->where('cs.`id_shop` = '.(int) $id);
1417        $result = Db::getInstance(_PS_USE_SQL_SLAVE_)->executeS($query);
1418
1419        if ($onlyId) {
1420            $array = [];
1421            foreach ($result as $row) {
1422                $array[] = $row['id_category'];
1423            }
1424            $array = array_unique($array);
1425        } else {
1426            return $result;
1427        }
1428
1429        return $array;
1430    }
1431
1432    /**
1433     * @deprecated 2.0.0 Use shop->id
1434     */
1435    public static function getCurrentShop()
1436    {
1437        Tools::displayAsDeprecated();
1438
1439        return Context::getContext()->shop->id;
1440    }
1441
1442    /**
1443     * @param string $entity
1444     * @param int    $idShop
1445     * @param bool   $active
1446     * @param bool   $delete
1447     *
1448     * @return array|bool
1449     * @throws PrestaShopDatabaseException
1450     * @throws PrestaShopException
1451     * @since   1.0.0
1452     * @version 1.0.0 Initial version
1453     */
1454    public static function getEntityIds($entity, $idShop, $active = false, $delete = false)
1455    {
1456        if (!static::isTableAssociated($entity)) {
1457            return false;
1458        }
1459
1460        return Db::getInstance(_PS_USE_SQL_SLAVE_)->executeS(
1461            (new DbQuery())
1462                ->select('entity.`id_'.bqSQL($entity).'`')
1463                ->from(bqSQL($entity).'_shop', 'es')
1464                ->leftJoin(bqSQL($entity), 'entity', 'entity.`id_'.bqSQL($entity).'` = es.`id_'.bqSQL($entity).'`')
1465                ->where('es.`id_shop` = '.(int) $idShop)
1466                ->where($active ? 'entity.`active` = 1' : '')
1467                ->where($delete ? 'entity.deleted = 0' : '')
1468        );
1469    }
1470
1471    /**
1472     * Initialize an array with all the multistore associations in the database
1473     *
1474     * @since   1.0.0
1475     * @version 1.0.0 Initial version
1476     */
1477    protected static function init()
1478    {
1479        // @codingStandardsIgnoreStart
1480        static::$id_shop_default_tables = ['product', 'category'];
1481        // @codingStandardsIgnoreEnd
1482
1483        $assoTables = [
1484            'carrier'                      => ['type' => 'shop'],
1485            'carrier_lang'                 => ['type' => 'fk_shop'],
1486            'category'                     => ['type' => 'shop'],
1487            'category_lang'                => ['type' => 'fk_shop'],
1488            'cms'                          => ['type' => 'shop'],
1489            'cms_lang'                     => ['type' => 'fk_shop'],
1490            'cms_category'                 => ['type' => 'shop'],
1491            'cms_category_lang'            => ['type' => 'fk_shop'],
1492            'contact'                      => ['type' => 'shop'],
1493            'country'                      => ['type' => 'shop'],
1494            'currency'                     => ['type' => 'shop'],
1495            'employee'                     => ['type' => 'shop'],
1496            'hook_module'                  => ['type' => 'fk_shop'],
1497            'hook_module_exceptions'       => ['type' => 'fk_shop', 'primary' => 'id_hook_module_exceptions'],
1498            'image'                        => ['type' => 'shop'],
1499            'lang'                         => ['type' => 'shop'],
1500            'meta_lang'                    => ['type' => 'fk_shop'],
1501            'module'                       => ['type' => 'shop'],
1502            'module_currency'              => ['type' => 'fk_shop'],
1503            'module_country'               => ['type' => 'fk_shop'],
1504            'module_group'                 => ['type' => 'fk_shop'],
1505            'product'                      => ['type' => 'shop'],
1506            'product_attribute'            => ['type' => 'shop'],
1507            'product_lang'                 => ['type' => 'fk_shop'],
1508            'referrer'                     => ['type' => 'shop'],
1509            'scene'                        => ['type' => 'shop'],
1510            'store'                        => ['type' => 'shop'],
1511            'webservice_account'           => ['type' => 'shop'],
1512            'warehouse'                    => ['type' => 'shop'],
1513            'stock_available'              => ['type' => 'fk_shop', 'primary' => 'id_stock_available'],
1514            'carrier_tax_rules_group_shop' => ['type' => 'fk_shop'],
1515            'attribute'                    => ['type' => 'shop'],
1516            'feature'                      => ['type' => 'shop'],
1517            'group'                        => ['type' => 'shop'],
1518            'attribute_group'              => ['type' => 'shop'],
1519            'tax_rules_group'              => ['type' => 'shop'],
1520            'zone'                         => ['type' => 'shop'],
1521            'manufacturer'                 => ['type' => 'shop'],
1522            'supplier'                     => ['type' => 'shop'],
1523        ];
1524
1525        foreach ($assoTables as $tableName => $tableDetails) {
1526            static::addTableAssociation($tableName, $tableDetails);
1527        }
1528
1529        static::$initialized = true;
1530    }
1531}
1532