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
27/**
28 * Class AccessCore.
29 */
30class AccessCore extends ObjectModel
31{
32    /** @var int Profile id which address belongs to */
33    public $id_profile = null;
34
35    /** @var int AuthorizationRole id which address belongs to */
36    public $id_authorization_role = null;
37
38    /**
39     * @see ObjectModel::$definition
40     */
41    public static $definition = [
42        'table' => 'access',
43        'primary' => 'id_profile',
44        'fields' => [
45            'id_profile' => ['type' => self::TYPE_INT, 'validate' => 'isNullOrUnsignedId', 'copy_post' => false],
46            'id_authorization_role' => ['type' => self::TYPE_INT, 'validate' => 'isNullOrUnsignedId', 'copy_post' => false],
47        ],
48    ];
49
50    /**
51     * Is access granted to this Role?
52     *
53     * @param string $role Role name ("Superadministrator", "sales", "translator", etc.)
54     * @param int $idProfile Profile ID
55     *
56     * @return bool Whether access is granted
57     *
58     * @throws Exception
59     */
60    public static function isGranted($role, $idProfile)
61    {
62        foreach ((array) $role as $currentRole) {
63            preg_match(
64                '/ROLE_MOD_(?P<type>[A-Z]+)_(?P<name>[A-Z0-9_]+)_(?P<auth>[A-Z]+)/',
65                $currentRole,
66                $matches
67            );
68
69            if (isset($matches['type']) && $matches['type'] == 'TAB') {
70                $joinTable = _DB_PREFIX_ . 'access';
71            } elseif (isset($matches['type']) && $matches['type'] == 'MODULE') {
72                $joinTable = _DB_PREFIX_ . 'module_access';
73            } else {
74                throw new Exception('The slug ' . $currentRole . ' is invalid');
75            }
76
77            $currentRole = Db::getInstance()->escape($currentRole);
78
79            $isCurrentGranted = (bool) Db::getInstance()->getRow('
80                SELECT t.`id_authorization_role`
81                FROM `' . _DB_PREFIX_ . 'authorization_role` t
82                LEFT JOIN ' . $joinTable . ' j
83                ON j.`id_authorization_role` = t.`id_authorization_role`
84                WHERE `slug` = "' . $currentRole . '"
85                AND j.`id_profile` = "' . (int) $idProfile . '"
86            ');
87
88            if (!$isCurrentGranted) {
89                return false;
90            }
91        }
92
93        return true;
94    }
95
96    /**
97     * Get all roles for the Profile ID.
98     *
99     * @param int $idProfile Profile ID
100     *
101     * @return array Roles
102     */
103    public static function getRoles($idProfile)
104    {
105        $idProfile = (int) $idProfile;
106
107        $accesses = Db::getInstance()->executeS('
108            SELECT r.`slug`
109            FROM `' . _DB_PREFIX_ . 'authorization_role` r
110            INNER JOIN `' . _DB_PREFIX_ . 'access` a ON a.`id_authorization_role` = r.`id_authorization_role`
111            WHERE a.`id_profile` = "' . $idProfile . '"
112        ');
113
114        $accessesFromModules = Db::getInstance()->executeS('
115            SELECT r.`slug`
116            FROM `' . _DB_PREFIX_ . 'authorization_role` r
117            INNER JOIN `' . _DB_PREFIX_ . 'module_access` ma ON ma.`id_authorization_role` = r.`id_authorization_role`
118            WHERE ma.`id_profile` = "' . $idProfile . '"
119        ');
120
121        $roles = array_merge($accesses, $accessesFromModules);
122
123        foreach ($roles as $key => $role) {
124            $roles[$key] = $role['slug'];
125        }
126
127        return $roles;
128    }
129
130    /**
131     * Find Tab ID by slug.
132     *
133     * @param string $authSlug Slug
134     *
135     * @return string Tab ID
136     * @todo: Find out if we should return an int instead. (breaking change)
137     */
138    public static function findIdTabByAuthSlug($authSlug)
139    {
140        preg_match(
141            '/ROLE_MOD_[A-Z]+_(?P<classname>[A-Z]+)_(?P<auth>[A-Z]+)/',
142            $authSlug,
143            $matches
144        );
145
146        $result = Db::getInstance()->getRow('
147            SELECT `id_tab`
148            FROM `' . _DB_PREFIX_ . 'tab`
149            WHERE UCASE(`class_name`) = "' . $matches['classname'] . '"
150        ');
151
152        return $result['id_tab'];
153    }
154
155    /**
156     * Find slug by Tab ID.
157     *
158     * @param int $idTab Tab ID
159     *
160     * @return string Full module slug
161     */
162    public static function findSlugByIdTab($idTab)
163    {
164        $result = Db::getInstance()->getRow('
165            SELECT `class_name`
166            FROM `' . _DB_PREFIX_ . 'tab`
167            WHERE `id_tab` = "' . (int) $idTab . '"
168        ');
169
170        return self::sluggifyTab($result);
171    }
172
173    /**
174     * Find slug by Parent Tab ID.
175     *
176     * @param int $idParentTab Tab ID
177     *
178     * @return string Full module slug
179     */
180    public static function findSlugByIdParentTab($idParentTab)
181    {
182        return Db::getInstance()->executeS('
183            SELECT `class_name`
184            FROM `' . _DB_PREFIX_ . 'tab`
185            WHERE `id_parent` = "' . (int) $idParentTab . '"
186        ');
187    }
188
189    /**
190     * Find slug by Module ID.
191     *
192     * @param int $idModule Module ID
193     *
194     * @return string Full module slug
195     */
196    public static function findSlugByIdModule($idModule)
197    {
198        $result = Db::getInstance()->getRow('
199            SELECT `name`
200            FROM `' . _DB_PREFIX_ . 'module`
201            WHERE `id_module` = "' . (int) $idModule . '"
202        ');
203
204        return self::sluggifyModule($result);
205    }
206
207    /**
208     * Sluggify tab.
209     *
210     * @param string $tab Tab class name
211     * @param string $authorization 'CREATE'|'READ'|'UPDATE'|'DELETE'
212     *
213     * @return string Full slug for tab
214     */
215    public static function sluggifyTab($tab, $authorization = '')
216    {
217        return sprintf('ROLE_MOD_TAB_%s_%s', strtoupper($tab['class_name']), $authorization);
218    }
219
220    /**
221     * Sluggify module.
222     *
223     * @param string $module Module name
224     * @param string $authorization 'CREATE'|'READ'|'UPDATE'|'DELETE'
225     *
226     * @return string Full slug for module
227     */
228    public static function sluggifyModule($module, $authorization = '')
229    {
230        return sprintf('ROLE_MOD_MODULE_%s_%s', strtoupper($module['name']), $authorization);
231    }
232
233    /**
234     * Get legacy authorization.
235     *
236     * @param string $legacyAuth Legacy authorization
237     *
238     * @return bool|string|array Authorization
239     */
240    public static function getAuthorizationFromLegacy($legacyAuth)
241    {
242        $auth = [
243            'add' => 'CREATE',
244            'view' => 'READ',
245            'edit' => 'UPDATE',
246            'configure' => 'UPDATE',
247            'delete' => 'DELETE',
248            'uninstall' => 'DELETE',
249            'duplicate' => ['CREATE', 'UPDATE'],
250            'all' => ['CREATE', 'READ', 'UPDATE', 'DELETE'],
251        ];
252
253        return isset($auth[$legacyAuth]) ? $auth[$legacyAuth] : false;
254    }
255
256    /**
257     * Add access.
258     *
259     * @param int $idProfile Profile ID
260     * @param int $idRole Role ID
261     *
262     * @return string Whether access has been successfully granted ("ok", "error")
263     */
264    public function addAccess($idProfile, $idRole)
265    {
266        $sql = '
267            INSERT IGNORE INTO `' . _DB_PREFIX_ . 'access` (`id_profile`, `id_authorization_role`)
268            VALUES (' . (int) $idProfile . ',' . (int) $idRole . ')
269        ';
270
271        return Db::getInstance()->execute($sql) ? 'ok' : 'error';
272    }
273
274    /**
275     * Remove access.
276     *
277     * @param int $idProfile Profile ID
278     * @param int $idRole Role ID
279     *
280     * @return string Whether access has been successfully removed ("ok", "error")
281     */
282    public function removeAccess($idProfile, $idRole)
283    {
284        $sql = '
285            DELETE FROM `' . _DB_PREFIX_ . 'access`
286            WHERE `id_profile` = "' . (int) $idProfile . '"
287            AND `id_authorization_role` = "' . (int) $idRole . '"
288        ';
289
290        return Db::getInstance()->execute($sql) ? 'ok' : 'error';
291    }
292
293    /**
294     * Add module access.
295     *
296     * @param int $idProfile Profile ID
297     * @param int $idRole Role ID
298     *
299     * @return string Whether module access has been successfully granted ("ok", "error")
300     */
301    public function addModuleAccess($idProfile, $idRole)
302    {
303        $sql = '
304            INSERT IGNORE INTO `' . _DB_PREFIX_ . 'module_access` (`id_profile`, `id_authorization_role`)
305            VALUES (' . (int) $idProfile . ',' . (int) $idRole . ')
306        ';
307
308        return Db::getInstance()->execute($sql) ? 'ok' : 'error';
309    }
310
311    /**
312     * @param int $idProfile
313     * @param int $idRole
314     *
315     * @return string 'ok'|'error'
316     */
317    public function removeModuleAccess($idProfile, $idRole)
318    {
319        $sql = '
320            DELETE FROM `' . _DB_PREFIX_ . 'module_access`
321            WHERE `id_profile` = "' . (int) $idProfile . '"
322            AND `id_authorization_role` = "' . (int) $idRole . '"
323        ';
324
325        return Db::getInstance()->execute($sql) ? 'ok' : 'error';
326    }
327
328    /**
329     * Update legacy access.
330     *
331     * @param int $idProfile Profile ID
332     * @param int $idTab Tab ID
333     * @param string $lgcAuth Legacy authorization
334     * @param int $enabled Whether access should be granted
335     * @param int $addFromParent Child from parents
336     *
337     * @return string Whether legacy access has been successfully updated ("ok", "error")
338     *
339     * @throws Exception
340     */
341    public function updateLgcAccess($idProfile, $idTab, $lgcAuth, $enabled, $addFromParent = 0)
342    {
343        $idProfile = (int) $idProfile;
344        $idTab = (int) $idTab;
345
346        if ($idTab == -1) {
347            $slug = 'ROLE_MOD_TAB_%_';
348        } else {
349            $slug = self::findSlugByIdTab($idTab);
350        }
351
352        $whereClauses = [];
353
354        foreach ((array) self::getAuthorizationFromLegacy($lgcAuth) as $auth) {
355            $slugLike = Db::getInstance()->escape($slug . $auth);
356            $whereClauses[] = ' `slug` LIKE "' . $slugLike . '"';
357        }
358
359        if ($addFromParent == 1) {
360            foreach (self::findSlugByIdParentTab($idTab) as $child) {
361                $child = self::sluggifyTab($child);
362                foreach ((array) self::getAuthorizationFromLegacy($lgcAuth) as $auth) {
363                    $slugLike = Db::getInstance()->escape($child . $auth);
364                    $whereClauses[] = ' `slug` LIKE "' . $slugLike . '"';
365                }
366            }
367        }
368
369        $roles = Db::getInstance()->executeS('
370            SELECT `id_authorization_role`
371            FROM `' . _DB_PREFIX_ . 'authorization_role` t
372            WHERE ' . implode(' OR ', $whereClauses) . '
373        ');
374
375        if (empty($roles)) {
376            throw new \Exception('Cannot find role slug');
377        }
378
379        $res = [];
380        foreach ($roles as $role) {
381            if ($enabled) {
382                $res[] = $this->addAccess($idProfile, $role['id_authorization_role']);
383            } else {
384                $res[] = $this->removeAccess($idProfile, $role['id_authorization_role']);
385            }
386        }
387
388        return in_array('error', $res) ? 'error' : 'ok';
389    }
390
391    /**
392     * Update (legacy) Module access.
393     *
394     * @param int $idProfile Profile ID
395     * @param int $idModule Module ID
396     * @param string $lgcAuth Legacy authorization
397     * @param int $enabled Whether module access should be granted
398     *
399     * @return string Whether module access has been succesfully changed ("ok", "error")
400     */
401    public function updateLgcModuleAccess($idProfile, $idModule, $lgcAuth, $enabled)
402    {
403        $idProfile = (int) $idProfile;
404        $idModule = (int) $idModule;
405
406        if ($idModule == -1) {
407            $slug = 'ROLE_MOD_MODULE_%_';
408        } else {
409            $slug = self::findSlugByIdModule($idModule);
410        }
411
412        $whereClauses = [];
413
414        foreach ((array) self::getAuthorizationFromLegacy($lgcAuth) as $auth) {
415            $slugLike = Db::getInstance()->escape($slug . $auth);
416            $whereClauses[] = ' `slug` LIKE "' . $slugLike . '"';
417        }
418
419        $roles = Db::getInstance()->executeS('
420            SELECT `id_authorization_role`
421            FROM `' . _DB_PREFIX_ . 'authorization_role` t
422            WHERE ' . implode(' OR ', $whereClauses) . '
423        ');
424
425        $res = [];
426        foreach ($roles as $role) {
427            if ($enabled) {
428                $res[] = $this->addModuleAccess($idProfile, $role['id_authorization_role']);
429            } else {
430                $res[] = $this->removeModuleAccess($idProfile, $role['id_authorization_role']);
431            }
432        }
433
434        return in_array('error', $res) ? 'error' : 'ok';
435    }
436}
437