1<?php
2/**
3 * Copyright since 2007 PrestaShop SA and Contributors
4 * PrestaShop is an International Registered Trademark & Property of PrestaShop SA
5 *
6 * NOTICE OF LICENSE
7 *
8 * This source file is subject to the Open Software License (OSL 3.0)
9 * that is bundled with this package in the file LICENSE.md.
10 * It is also available through the world-wide-web at this URL:
11 * https://opensource.org/licenses/OSL-3.0
12 * If you did not receive a copy of the license and are unable to
13 * obtain it through the world-wide-web, please send an email
14 * to license@prestashop.com so we can send you a copy immediately.
15 *
16 * DISCLAIMER
17 *
18 * Do not edit or add to this file if you wish to upgrade PrestaShop to newer
19 * versions in the future. If you wish to customize PrestaShop for your
20 * needs please refer to https://devdocs.prestashop.com/ for more information.
21 *
22 * @author    PrestaShop SA and Contributors <contact@prestashop.com>
23 * @copyright Since 2007 PrestaShop SA and Contributors
24 * @license   https://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0)
25 */
26
27namespace PrestaShop\PrestaShop\Core\Module;
28
29use Db;
30use Exception;
31use PrestaShop\PrestaShop\Adapter\Hook\HookInformationProvider;
32use Shop;
33
34class HookRepository
35{
36    private $hookInfo;
37    private $shop;
38    private $db;
39    private $db_prefix;
40
41    public function __construct(
42        HookInformationProvider $hookInfo,
43        Shop $shop,
44        Db $db
45    ) {
46        $this->hookInfo = $hookInfo;
47        $this->shop = $shop;
48        $this->db = $db;
49        $this->db_prefix = $db->getPrefix();
50    }
51
52    public function getIdByName($hook_name)
53    {
54        $escaped_hook_name = $this->db->escape($hook_name);
55
56        $id_hook = $this->db->getValue(
57            "SELECT id_hook FROM {$this->db_prefix}hook WHERE name = '$escaped_hook_name'"
58        );
59
60        return (int) $id_hook;
61    }
62
63    public function createHook($hook_name, $title = '', $description = '', $position = 1)
64    {
65        $this->db->insert('hook', [
66            'name' => $this->db->escape($hook_name),
67            'title' => $this->db->escape($title),
68            'description' => $this->db->escape($description),
69            'position' => $this->db->escape($position),
70        ], false, true, Db::REPLACE);
71
72        return $this->getIdByName($hook_name);
73    }
74
75    private function getIdModule($module_name)
76    {
77        $escaped_module_name = $this->db->escape($module_name);
78
79        $id_module = $this->db->getValue(
80            "SELECT id_module FROM {$this->db_prefix}module WHERE name = '$escaped_module_name'"
81        );
82
83        return (int) $id_module;
84    }
85
86    public function unHookModulesFromHook($hook_name)
87    {
88        $id_hook = $this->getIdByName($hook_name);
89        $id_shop = (int) $this->shop->id;
90
91        $this->db->execute("DELETE FROM {$this->db_prefix}hook_module
92             WHERE id_hook = $id_hook AND id_shop = $id_shop
93        ");
94
95        $this->db->execute("DELETE FROM {$this->db_prefix}hook_module_exceptions
96            WHERE id_hook = $id_hook AND id_shop = $id_shop
97        ");
98
99        return $this;
100    }
101
102    /**
103     * Saves hook settings for a list of hooks.
104     * The $hooks array should have this format:
105     * [
106     *     "hookName" => [
107     *         "module1",
108     *         "module2",
109     *         "module3" => [
110     *             "except_pages" => [
111     *                 "page1",
112     *                 "page2",
113     *                 "page3"
114     *             ]
115     *         ]
116     *     ]
117     * ]
118     * Only hooks present as keys in the $hooks array are affected and all changes
119     * are only done for the shop this Repository belongs to.
120     */
121    public function persistHooksConfiguration(array $hooks)
122    {
123        foreach ($hooks as $hook_name => $module_names) {
124            $id_hook = $this->getIdByName($hook_name);
125            if (!$id_hook) {
126                $id_hook = $this->createHook($hook_name);
127            }
128            if (!$id_hook) {
129                throw new Exception(sprintf('Could not create hook `%1$s`.', $hook_name));
130            }
131
132            $this->unHookModulesFromHook($hook_name);
133
134            $position = 0;
135            foreach ($module_names as $key => $module) {
136                if (is_array($module)) {
137                    $module_name = key($module);
138                    $extra_data = current($module);
139                } else {
140                    $module_name = $module;
141                    $extra_data = [];
142                }
143
144                ++$position;
145                $id_module = $this->getIdModule($module_name);
146                if (!$id_module) {
147                    continue;
148                }
149
150                $row = [
151                    'id_module' => $id_module,
152                    'id_shop' => (int) $this->shop->id,
153                    'id_hook' => $id_hook,
154                    'position' => $position,
155                ];
156
157                $this->db->insert('hook_module', $row);
158
159                if (!empty($extra_data['except_pages'])) {
160                    $this->setModuleHookExceptions(
161                        $id_module,
162                        $id_hook,
163                        $extra_data['except_pages']
164                    );
165                }
166            }
167        }
168
169        return $this;
170    }
171
172    private function setModuleHookExceptions($id_module, $id_hook, array $pages)
173    {
174        $id_shop = (int) $this->shop->id;
175        $id_module = (int) $id_module;
176        $id_hook = (int) $id_hook;
177
178        $this->db->execute("DELETE FROM {$this->db_prefix}hook_module_exceptions
179            WHERE id_shop = $id_shop
180            AND id_module = $id_module
181            AND id_hook = $id_hook
182        ");
183
184        foreach ($pages as $page) {
185            $this->db->insert('hook_module_exceptions', [
186                'id_shop' => $id_shop,
187                'id_module' => $id_module,
188                'id_hook' => $id_hook,
189                'file_name' => $page,
190            ]);
191        }
192
193        return $this;
194    }
195
196    private function getModuleHookExceptions($id_module, $id_hook)
197    {
198        $id_shop = (int) $this->shop->id;
199        $id_module = (int) $id_module;
200        $id_hook = (int) $id_hook;
201
202        $rows = $this->db->executeS("SELECT file_name
203            FROM {$this->db_prefix}hook_module_exceptions
204            WHERE id_shop = $id_shop
205            AND id_module = $id_module
206            AND id_hook = $id_hook
207            ORDER BY file_name ASC
208        ");
209
210        return array_map(function ($row) {
211            return $row['file_name'];
212        }, $rows);
213    }
214
215    public function getHooksWithModules()
216    {
217        $id_shop = (int) $this->shop->id;
218
219        $sql = "SELECT h.name as hook_name, h.id_hook, m.name as module_name, m.id_module
220            FROM {$this->db_prefix}hook_module hm
221            INNER JOIN {$this->db_prefix}hook h
222                ON h.id_hook = hm.id_hook
223            INNER JOIN {$this->db_prefix}module m
224                ON m.id_module = hm.id_module
225            WHERE hm.id_shop = $id_shop
226            ORDER BY h.name ASC, hm.position ASC
227        ";
228
229        $rows = $this->db->executeS($sql);
230
231        $hooks = [];
232
233        foreach ($rows as $row) {
234            $exceptions = $this->getModuleHookExceptions(
235                $row['id_module'],
236                $row['id_hook']
237            );
238
239            if (empty($exceptions)) {
240                $hooks[$row['hook_name']][] = $row['module_name'];
241            } else {
242                $hooks[$row['hook_name']][$row['module_name']] = [
243                    'except_pages' => $exceptions,
244                ];
245            }
246        }
247
248        return $hooks;
249    }
250
251    public function getDisplayHooksWithModules()
252    {
253        $hooks = [];
254        foreach ($this->getHooksWithModules() as $hook_name => $modules) {
255            if ($this->hookInfo->isDisplayHookName($hook_name)) {
256                $hooks[$hook_name] = $modules;
257            }
258        }
259
260        return $hooks;
261    }
262}
263