vendor/pimcore/pimcore/models/DataObject/AbstractObject/Dao.php line 39

Open in your IDE?
  1. <?php
  2. /**
  3.  * Pimcore
  4.  *
  5.  * This source file is available under two different licenses:
  6.  * - GNU General Public License version 3 (GPLv3)
  7.  * - Pimcore Commercial License (PCL)
  8.  * Full copyright and license information is available in
  9.  * LICENSE.md which is distributed with this source code.
  10.  *
  11.  *  @copyright  Copyright (c) Pimcore GmbH (http://www.pimcore.org)
  12.  *  @license    http://www.pimcore.org/license     GPLv3 and PCL
  13.  */
  14. namespace Pimcore\Model\DataObject\AbstractObject;
  15. use Pimcore\Db;
  16. use Pimcore\Db\Helper;
  17. use Pimcore\Logger;
  18. use Pimcore\Model;
  19. use Pimcore\Model\DataObject;
  20. use Pimcore\Model\User;
  21. /**
  22.  * @internal
  23.  *
  24.  * @property \Pimcore\Model\DataObject\AbstractObject $model
  25.  */
  26. class Dao extends Model\Element\Dao
  27. {
  28.     /**
  29.      * Get the data for the object from database for the given id
  30.      *
  31.      * @param int $id
  32.      *
  33.      * @throws Model\Exception\NotFoundException
  34.      */
  35.     public function getById($id)
  36.     {
  37.         $data $this->db->fetchAssociative("SELECT objects.*, tree_locks.locked as o_locked FROM objects
  38.             LEFT JOIN tree_locks ON objects.o_id = tree_locks.id AND tree_locks.type = 'object'
  39.                 WHERE o_id = ?", [$id]);
  40.         if (!empty($data['o_id'])) {
  41.             $this->assignVariablesToModel($data);
  42.         } else {
  43.             throw new Model\Exception\NotFoundException('Object with the ID ' $id " doesn't exists");
  44.         }
  45.     }
  46.     /**
  47.      * Get the data for the object from database for the given path
  48.      *
  49.      * @param string $path
  50.      *
  51.      * @throws Model\Exception\NotFoundException
  52.      */
  53.     public function getByPath($path)
  54.     {
  55.         $params $this->extractKeyAndPath($path);
  56.         $data $this->db->fetchAssociative('SELECT o_id FROM objects WHERE o_path = :path AND `o_key` = :key'$params);
  57.         if (!empty($data['o_id'])) {
  58.             $this->assignVariablesToModel($data);
  59.         } else {
  60.             throw new Model\Exception\NotFoundException("object doesn't exist");
  61.         }
  62.     }
  63.     /**
  64.      * Create a new record for the object in database
  65.      */
  66.     public function create()
  67.     {
  68.         $this->db->insert('objects', [
  69.             'o_key' => $this->model->getKey(),
  70.             'o_path' => $this->model->getRealPath(),
  71.         ]);
  72.         $this->model->setId((int) $this->db->lastInsertId());
  73.         if (!$this->model->getKey() && !is_numeric($this->model->getKey())) {
  74.             $this->model->setKey($this->db->lastInsertId());
  75.         }
  76.     }
  77.     /**
  78.      * @param bool|null $isUpdate
  79.      *
  80.      * @throws \Exception
  81.      */
  82.     public function update($isUpdate null)
  83.     {
  84.         $object $this->model->getObjectVars();
  85.         $data = [];
  86.         $validTableColumns $this->getValidTableColumns('objects');
  87.         foreach ($object as $key => $value) {
  88.             if (in_array($key$validTableColumns)) {
  89.                 if (is_bool($value)) {
  90.                     $value = (int)$value;
  91.                 }
  92.                 $data[$key] = $value;
  93.             }
  94.         }
  95.         // check the type before updating, changing the type or class of an object is not possible
  96.         $checkColumns = ['o_type''o_classId''o_className'];
  97.         $existingData $this->db->fetchAssociative('SELECT ' implode(','$checkColumns) . ' FROM objects WHERE o_id = ?', [$this->model->getId()]);
  98.         foreach ($checkColumns as $column) {
  99.             if ($column == 'o_type' && in_array($data[$column], [DataObject::OBJECT_TYPE_VARIANTDataObject::OBJECT_TYPE_OBJECT]) && (isset($existingData[$column]) && in_array($existingData[$column], [DataObject::OBJECT_TYPE_VARIANTDataObject::OBJECT_TYPE_OBJECT]))) {
  100.                 // type conversion variant <=> object should be possible
  101.                 continue;
  102.             }
  103.             if (!empty($existingData[$column]) && $data[$column] != $existingData[$column]) {
  104.                 throw new \Exception('Unable to save object: type, classId or className mismatch');
  105.             }
  106.         }
  107.         Helper::insertOrUpdate($this->db'objects'$data);
  108.         // tree_locks
  109.         $this->db->delete('tree_locks', ['id' => $this->model->getId(), 'type' => 'object']);
  110.         if ($this->model->getLocked()) {
  111.             $this->db->insert('tree_locks', [
  112.                 'id' => $this->model->getId(),
  113.                 'type' => 'object',
  114.                 'locked' => $this->model->getLocked(),
  115.             ]);
  116.         }
  117.     }
  118.     /**
  119.      * Deletes object from database
  120.      *
  121.      * @return void
  122.      */
  123.     public function delete()
  124.     {
  125.         $this->db->delete('objects', ['o_id' => $this->model->getId()]);
  126.     }
  127.     public function updateWorkspaces()
  128.     {
  129.         $this->db->update('users_workspaces_object', [
  130.             'cpath' => $this->model->getRealFullPath(),
  131.         ], [
  132.             'cid' => $this->model->getId(),
  133.         ]);
  134.     }
  135.     /**
  136.      * Updates the paths for children, children's properties and children's permissions in the database
  137.      *
  138.      * @internal
  139.      *
  140.      * @param string $oldPath
  141.      *
  142.      * @return null|array
  143.      */
  144.     public function updateChildPaths($oldPath)
  145.     {
  146.         if ($this->hasChildren(DataObject::$typestrue)) {
  147.             //get objects to empty their cache
  148.             $objects $this->db->fetchFirstColumn('SELECT o_id FROM objects WHERE o_path LIKE ?', [Helper::escapeLike($oldPath) . '%']);
  149.             $userId '0';
  150.             if ($user \Pimcore\Tool\Admin::getCurrentUser()) {
  151.                 $userId $user->getId();
  152.             }
  153.             //update object child paths
  154.             // we don't update the modification date here, as this can have side-effects when there's an unpublished version for an element
  155.             $this->db->executeQuery('update objects set o_path = replace(o_path,' $this->db->quote($oldPath '/') . ',' $this->db->quote($this->model->getRealFullPath() . '/') . "), o_userModification = '" $userId "' where o_path like " $this->db->quote(Helper::escapeLike($oldPath) . '/%') . ';');
  156.             //update object child permission paths
  157.             $this->db->executeQuery('update users_workspaces_object set cpath = replace(cpath,' $this->db->quote($oldPath '/') . ',' $this->db->quote($this->model->getRealFullPath() . '/') . ') where cpath like ' $this->db->quote(Helper::escapeLike($oldPath) . '/%') . ';');
  158.             //update object child properties paths
  159.             $this->db->executeQuery('update properties set cpath = replace(cpath,' $this->db->quote($oldPath '/') . ',' $this->db->quote($this->model->getRealFullPath() . '/') . ') where cpath like ' $this->db->quote(Helper::escapeLike($oldPath) . '/%') . ';');
  160.             return $objects;
  161.         }
  162.         return null;
  163.     }
  164.     /**
  165.      * deletes all properties for the object from database
  166.      *
  167.      * @return void
  168.      */
  169.     public function deleteAllProperties()
  170.     {
  171.         $this->db->delete('properties', ['cid' => $this->model->getId(), 'ctype' => 'object']);
  172.     }
  173.     /**
  174.      * @return string retrieves the current full object path from DB
  175.      */
  176.     public function getCurrentFullPath()
  177.     {
  178.         $path null;
  179.         try {
  180.             $path $this->db->fetchOne('SELECT CONCAT(o_path,`o_key`) as o_path FROM objects WHERE o_id = ?', [$this->model->getId()]);
  181.         } catch (\Exception $e) {
  182.             Logger::error('could not get current object path from DB');
  183.         }
  184.         return $path;
  185.     }
  186.     /**
  187.      * @return int
  188.      */
  189.     public function getVersionCountForUpdate(): int
  190.     {
  191.         if (!$this->model->getId()) {
  192.             return 0;
  193.         }
  194.         $versionCount = (int) $this->db->fetchOne('SELECT o_versionCount FROM objects WHERE o_id = ? FOR UPDATE', [$this->model->getId()]);
  195.         if ($this->model instanceof DataObject\Concrete) {
  196.             $versionCount2 = (int) $this->db->fetchOne("SELECT MAX(versionCount) FROM versions WHERE cid = ? AND ctype = 'object'", [$this->model->getId()]);
  197.             $versionCount max($versionCount$versionCount2);
  198.         }
  199.         return (int) $versionCount;
  200.     }
  201.     /**
  202.      * Get the properties for the object from database and assign it
  203.      *
  204.      * @param bool $onlyInherited
  205.      *
  206.      * @return array
  207.      */
  208.     public function getProperties($onlyInherited false)
  209.     {
  210.         $properties = [];
  211.         // collect properties via parent - ids
  212.         $parentIds $this->getParentIds();
  213.         $propertiesRaw $this->db->fetchAllAssociative('SELECT name, type, data, cid, inheritable, cpath FROM properties WHERE ((cid IN (' implode(','$parentIds) . ") AND inheritable = 1) OR cid = ? ) AND ctype='object'", [$this->model->getId()]);
  214.         // because this should be faster than mysql
  215.         usort($propertiesRaw, function ($left$right) {
  216.             return strcmp($left['cpath'], $right['cpath']);
  217.         });
  218.         foreach ($propertiesRaw as $propertyRaw) {
  219.             try {
  220.                 $property = new Model\Property();
  221.                 $property->setType($propertyRaw['type']);
  222.                 $property->setCid($this->model->getId());
  223.                 $property->setName($propertyRaw['name']);
  224.                 $property->setCtype('object');
  225.                 $property->setDataFromResource($propertyRaw['data']);
  226.                 $property->setInherited(true);
  227.                 if ($propertyRaw['cid'] == $this->model->getId()) {
  228.                     $property->setInherited(false);
  229.                 }
  230.                 $property->setInheritable(false);
  231.                 if ($propertyRaw['inheritable']) {
  232.                     $property->setInheritable(true);
  233.                 }
  234.                 if ($onlyInherited && !$property->getInherited()) {
  235.                     continue;
  236.                 }
  237.                 $properties[$propertyRaw['name']] = $property;
  238.             } catch (\Exception $e) {
  239.                 Logger::error("can't add property " $propertyRaw['name'] . ' to object ' $this->model->getRealFullPath());
  240.             }
  241.         }
  242.         // if only inherited then only return it and dont call the setter in the model
  243.         if ($onlyInherited) {
  244.             return $properties;
  245.         }
  246.         $this->model->setProperties($properties);
  247.         return $properties;
  248.     }
  249.     /**
  250.      * Quick test if there are children
  251.      *
  252.      * @param array $objectTypes
  253.      * @param bool|null $includingUnpublished
  254.      * @param Model\User $user
  255.      *
  256.      * @return bool
  257.      */
  258.     public function hasChildren($objectTypes = [DataObject::OBJECT_TYPE_OBJECTDataObject::OBJECT_TYPE_FOLDER], $includingUnpublished null$user null)
  259.     {
  260.         if (!$this->model->getId()) {
  261.             return false;
  262.         }
  263.         $sql 'SELECT 1 FROM objects o WHERE o_parentId = ? ';
  264.         if ($user && !$user->isAdmin()) {
  265.             $roleIds $user->getRoles();
  266.             $currentUserId $user->getId();
  267.             $permissionIds array_merge($roleIds, [$currentUserId]);
  268.             //gets the permission of the ancestors, since it would be the same for each row with same o_parentId, it is done once outside the query to avoid extra subquery.
  269.             $inheritedPermission $this->isInheritingPermission('list'$permissionIds);
  270.             // $anyAllowedRowOrChildren checks for nested elements that are `list`=1. This is to allow the folders in between from current parent to any nested elements and due the "additive" permission on the element itself, we can simply ignore list=0 children
  271.             // unless for the same rule found is list=0 on user specific level, in that case it nullifies that entry.
  272.             $anyAllowedRowOrChildren 'EXISTS(SELECT list FROM users_workspaces_object uwo WHERE userId IN (' implode(','$permissionIds) . ') AND list=1 AND LOCATE(CONCAT(o.o_path,o.o_key),cpath)=1 AND
  273.             NOT EXISTS(SELECT list FROM users_workspaces_object WHERE userId =' $currentUserId '  AND list=0 AND cpath = uwo.cpath))';
  274.             // $allowedCurrentRow checks if the current row is blocked, if found a match it "removes/ignores" the entry from object table, doesn't need to check if is list=1 on user level, since it is done in $anyAllowedRowOrChildren (NB: equal or longer cpath) so we are safe to deduce that there are no valid list=1 rules
  275.             $isDisallowedCurrentRow 'EXISTS(SELECT list FROM users_workspaces_object uworow WHERE userId IN (' implode(','$permissionIds) . ')  AND cid = o_id AND list=0)';
  276.             //If no children with list=1 (with no user-level list=0) is found, we consider the inherited permission rule
  277.             //if $inheritedPermission=0 then everything is disallowed (or doesn't specify any rule) for that row, we can skip $isDisallowedCurrentRow
  278.             //if $inheritedPermission=1, then we are allowed unless the current row is specifically disabled, already knowing from $anyAllowedRowOrChildren that there are no list=1(without user permission list=0),so this "blocker" is the highest cpath available for this row if found
  279.             $sql .= ' AND IF(' $anyAllowedRowOrChildren ',1,IF(' $inheritedPermission ', ' $isDisallowedCurrentRow ' = 0, 0)) = 1';
  280.         }
  281.         if ((isset($includingUnpublished) && !$includingUnpublished) || (!isset($includingUnpublished) && Model\Document::doHideUnpublished())) {
  282.             $sql .= ' AND o_published = 1';
  283.         }
  284.         if (!empty($objectTypes)) {
  285.             $sql .= " AND o_type IN ('" implode("','"$objectTypes) . "')";
  286.         }
  287.         $sql .= ' LIMIT 1';
  288.         $c $this->db->fetchOne($sql, [$this->model->getId()]);
  289.         return (bool)$c;
  290.     }
  291.     /**
  292.      * Quick test if there are siblings
  293.      *
  294.      * @param array $objectTypes
  295.      * @param bool|null $includingUnpublished
  296.      *
  297.      * @return bool
  298.      */
  299.     public function hasSiblings($objectTypes = [DataObject::OBJECT_TYPE_OBJECTDataObject::OBJECT_TYPE_FOLDER], $includingUnpublished null)
  300.     {
  301.         if (!$this->model->getParentId()) {
  302.             return false;
  303.         }
  304.         $sql 'SELECT 1 FROM objects WHERE o_parentId = ?';
  305.         $params = [$this->model->getParentId()];
  306.         if ($this->model->getId()) {
  307.             $sql .= ' AND o_id != ?';
  308.             $params[] = $this->model->getId();
  309.         }
  310.         if ((isset($includingUnpublished) && !$includingUnpublished) || (!isset($includingUnpublished) && Model\Document::doHideUnpublished())) {
  311.             $sql .= ' AND o_published = 1';
  312.         }
  313.         $sql .= " AND o_type IN ('" implode("','"$objectTypes) . "') LIMIT 1";
  314.         $c $this->db->fetchOne($sql$params);
  315.         return (bool)$c;
  316.     }
  317.     /**
  318.      * returns the amount of directly children (not recursivly)
  319.      *
  320.      * @param array|null $objectTypes
  321.      * @param Model\User $user
  322.      *
  323.      * @return int
  324.      */
  325.     public function getChildAmount($objectTypes = [DataObject::OBJECT_TYPE_OBJECTDataObject::OBJECT_TYPE_FOLDER], $user null)
  326.     {
  327.         if (!$this->model->getId()) {
  328.             return 0;
  329.         }
  330.         $query 'SELECT COUNT(*) AS count FROM objects o WHERE o_parentId = ?';
  331.         if (!empty($objectTypes)) {
  332.             $query .= sprintf(' AND o_type IN (\'%s\')'implode("','"$objectTypes));
  333.         }
  334.         if ($user && !$user->isAdmin()) {
  335.             $roleIds $user->getRoles();
  336.             $currentUserId $user->getId();
  337.             $permissionIds array_merge($roleIds, [$currentUserId]);
  338.             $inheritedPermission $this->isInheritingPermission('list'$permissionIds);
  339.             $anyAllowedRowOrChildren 'EXISTS(SELECT list FROM users_workspaces_object uwo WHERE userId IN (' implode(','$permissionIds) . ') AND list=1 AND LOCATE(CONCAT(o.o_path,o.o_key),cpath)=1 AND
  340.             NOT EXISTS(SELECT list FROM users_workspaces_object WHERE userId ='.$currentUserId.'  AND list=0 AND cpath = uwo.cpath))';
  341.             $isDisallowedCurrentRow 'EXISTS(SELECT list FROM users_workspaces_object uworow WHERE userId IN (' implode(','$permissionIds) . ')  AND cid = o_id AND list=0)';
  342.             $query .= ' AND IF(' $anyAllowedRowOrChildren ',1,IF(' $inheritedPermission ', ' $isDisallowedCurrentRow ' = 0, 0)) = 1';
  343.         }
  344.         return (int) $this->db->fetchOne($query, [$this->model->getId()]);
  345.     }
  346.     /**
  347.      * @param int $id
  348.      *
  349.      * @return array
  350.      *
  351.      * @throws Model\Exception\NotFoundException
  352.      */
  353.     public function getTypeById($id)
  354.     {
  355.         $t $this->db->fetchAssociative('SELECT o_type,o_className,o_classId FROM objects WHERE o_id = ?', [$id]);
  356.         if (!$t) {
  357.             throw new Model\Exception\NotFoundException('object with ID ' $id ' not found');
  358.         }
  359.         return $t;
  360.     }
  361.     /**
  362.      * @return bool
  363.      */
  364.     public function isLocked()
  365.     {
  366.         // check for an locked element below this element
  367.         $belowLocks $this->db->fetchOne("SELECT tree_locks.id FROM tree_locks INNER JOIN objects ON tree_locks.id = objects.o_id WHERE objects.o_path LIKE ? AND tree_locks.type = 'object' AND tree_locks.locked IS NOT NULL AND tree_locks.locked != '' LIMIT 1", [Helper::escapeLike($this->model->getRealFullPath()) . '/%']);
  368.         if ($belowLocks 0) {
  369.             return true;
  370.         }
  371.         $parentIds $this->getParentIds();
  372.         $inhertitedLocks $this->db->fetchOne('SELECT id FROM tree_locks WHERE id IN (' implode(','$parentIds) . ") AND type='object' AND locked = 'propagate' LIMIT 1");
  373.         if ($inhertitedLocks 0) {
  374.             return true;
  375.         }
  376.         return false;
  377.     }
  378.     /**
  379.      * @return array
  380.      */
  381.     public function unlockPropagate()
  382.     {
  383.         $lockIds $this->db->fetchFirstColumn('SELECT o_id from objects WHERE o_path LIKE ' $this->db->quote(Helper::escapeLike($this->model->getRealFullPath()) . '/%') . ' OR o_id = ' $this->model->getId());
  384.         $this->db->executeStatement("DELETE FROM tree_locks WHERE type = 'object' AND id IN (" implode(','$lockIds) . ')');
  385.         return $lockIds;
  386.     }
  387.     /**
  388.      * @return DataObject\ClassDefinition[]
  389.      */
  390.     public function getClasses()
  391.     {
  392.         $path $this->model->getRealFullPath();
  393.         if (!$this->model->getId() || $this->model->getId() == 1) {
  394.             $path '';
  395.         }
  396.         $classIds = [];
  397.         do {
  398.             $classId $this->db->fetchOne(
  399.                 "SELECT o_classId FROM objects WHERE o_path LIKE ? AND o_type = 'object'".($classIds ' AND o_classId NOT IN ('.rtrim(str_repeat('?,'count($classIds)), ',').')' '').' LIMIT 1',
  400.                 array_merge([Helper::escapeLike($path).'/%'], $classIds));
  401.             if ($classId) {
  402.                 $classIds[] = $classId;
  403.             }
  404.         } while ($classId);
  405.         $classes = [];
  406.         foreach ($classIds as $classId) {
  407.             if ($class DataObject\ClassDefinition::getById($classId)) {
  408.                 $classes[] = $class;
  409.             }
  410.         }
  411.         return $classes;
  412.     }
  413.     /**
  414.      * @return int[]
  415.      */
  416.     protected function collectParentIds()
  417.     {
  418.         $parentIds $this->getParentIds();
  419.         if ($id $this->model->getId()) {
  420.             $parentIds[] = $id;
  421.         }
  422.         return $parentIds;
  423.     }
  424.     /**
  425.      * @param string $type
  426.      * @param array $userIds
  427.      *
  428.      * @return int
  429.      *
  430.      * @throws \Doctrine\DBAL\Exception
  431.      */
  432.     public function isInheritingPermission(string $type, array $userIds)
  433.     {
  434.         return $this->InheritingPermission($type$userIds'object');
  435.     }
  436.     /**
  437.      * @param string $type
  438.      * @param Model\User $user
  439.      *
  440.      * @return bool
  441.      */
  442.     public function isAllowed($type$user)
  443.     {
  444.         $parentIds $this->collectParentIds();
  445.         $userIds $user->getRoles();
  446.         $userIds[] = $user->getId();
  447.         try {
  448.             $permissionsParent $this->db->fetchOne('SELECT ' $this->db->quoteIdentifier($type) . ' FROM users_workspaces_object WHERE cid IN (' implode(','$parentIds) . ') AND userId IN (' implode(','$userIds) . ') ORDER BY LENGTH(cpath) DESC, FIELD(userId, ' $user->getId() . ') DESC, ' $this->db->quoteIdentifier($type) . ' DESC LIMIT 1');
  449.             if ($permissionsParent) {
  450.                 return true;
  451.             }
  452.             // exception for list permission
  453.             if (empty($permissionsParent) && $type === 'list') {
  454.                 // check for children with permissions
  455.                 $path $this->model->getRealFullPath() . '/';
  456.                 if ($this->model->getId() == 1) {
  457.                     $path '/';
  458.                 }
  459.                 $permissionsChildren $this->db->fetchOne('SELECT list FROM users_workspaces_object WHERE cpath LIKE ? AND userId IN (' implode(','$userIds) . ') AND list = 1 LIMIT 1', [Helper::escapeLike($path) . '%']);
  460.                 if ($permissionsChildren) {
  461.                     return true;
  462.                 }
  463.             }
  464.         } catch (\Exception $e) {
  465.             Logger::warn('Unable to get permission ' $type ' for object ' $this->model->getId());
  466.         }
  467.         return false;
  468.     }
  469.     /**
  470.      * @param array $columns
  471.      * @param User $user
  472.      *
  473.      * @return array<string, int>
  474.      *
  475.      */
  476.     public function areAllowed(array $columnsUser $user)
  477.     {
  478.         return $this->permissionByTypes($columns$user'object');
  479.     }
  480.     /**
  481.      * @param string|null $type
  482.      * @param Model\User $user
  483.      * @param bool $quote
  484.      *
  485.      * @return array|null
  486.      */
  487.     public function getPermissions($type$user$quote true)
  488.     {
  489.         $parentIds $this->collectParentIds();
  490.         $userIds $user->getRoles();
  491.         $userIds[] = $user->getId();
  492.         try {
  493.             if ($type && $quote) {
  494.                 $queryType '`' $type '`';
  495.             } else {
  496.                 $queryType '*';
  497.             }
  498.             $commaSeparated in_array($type, ['lView''lEdit''layouts']);
  499.             if ($commaSeparated) {
  500.                 $allPermissions $this->db->fetchAllAssociative('SELECT ' $queryType ',cid,cpath FROM users_workspaces_object WHERE cid IN (' implode(','$parentIds) . ') AND userId IN (' implode(','$userIds) . ') ORDER BY LENGTH(cpath) DESC, FIELD(userId, ' $user->getId() . ') DESC, `' $type '` DESC');
  501.                 if (!$allPermissions) {
  502.                     return null;
  503.                 }
  504.                 if (count($allPermissions) == 1) {
  505.                     return $allPermissions[0];
  506.                 }
  507.                 $firstPermission $allPermissions[0];
  508.                 $firstPermissionCid $firstPermission['cid'];
  509.                 $mergedPermissions = [];
  510.                 foreach ($allPermissions as $permission) {
  511.                     $cid $permission['cid'];
  512.                     if ($cid != $firstPermissionCid) {
  513.                         break;
  514.                     }
  515.                     $permissionValues $permission[$type];
  516.                     if (!$permissionValues) {
  517.                         $firstPermission[$type] = null;
  518.                         return $firstPermission;
  519.                     }
  520.                     $permissionValues explode(','$permissionValues);
  521.                     foreach ($permissionValues as $permissionValue) {
  522.                         $mergedPermissions[$permissionValue] = $permissionValue;
  523.                     }
  524.                 }
  525.                 $firstPermission[$type] = implode(','$mergedPermissions);
  526.                 return $firstPermission;
  527.             }
  528.             $orderByType $type ', `' $type '` DESC' '';
  529.             $permissions $this->db->fetchAssociative('SELECT ' $queryType ' FROM users_workspaces_object WHERE cid IN (' implode(','$parentIds) . ') AND userId IN (' implode(','$userIds) . ') ORDER BY LENGTH(cpath) DESC, FIELD(userId, ' $user->getId() . ') DESC' $orderByType ' LIMIT 1');
  530.             return $permissions;
  531.         } catch (\Exception $e) {
  532.             Logger::warn('Unable to get permission ' $type ' for object ' $this->model->getId());
  533.         }
  534.         return null;
  535.     }
  536.     /**
  537.      * @param string|null $type
  538.      * @param Model\User $user
  539.      * @param bool $quote
  540.      *
  541.      * @return array
  542.      */
  543.     public function getChildPermissions($type$user$quote true)
  544.     {
  545.         $userIds $user->getRoles();
  546.         $userIds[] = $user->getId();
  547.         $permissions = [];
  548.         try {
  549.             if ($type && $quote) {
  550.                 $type '`' $type '`';
  551.             } else {
  552.                 $type '*';
  553.             }
  554.             $cid $this->model->getId();
  555.             $sql 'SELECT ' $type ' FROM users_workspaces_object WHERE cid != ' $cid ' AND cpath LIKE ' $this->db->quote(Helper::escapeLike($this->model->getRealFullPath()) . '%') . ' AND userId IN (' implode(','$userIds) . ') ORDER BY LENGTH(cpath) DESC';
  556.             $permissions $this->db->fetchAllAssociative($sql);
  557.         } catch (\Exception $e) {
  558.             Logger::warn('Unable to get permission ' $type ' for object ' $this->model->getId());
  559.         }
  560.         return $permissions;
  561.     }
  562.     /**
  563.      * @param int $index
  564.      */
  565.     public function saveIndex($index)
  566.     {
  567.         $this->db->update('objects', [
  568.             'o_index' => $index,
  569.         ], [
  570.             'o_id' => $this->model->getId(),
  571.         ]);
  572.     }
  573.     /**
  574.      * @return bool
  575.      */
  576.     public function __isBasedOnLatestData()
  577.     {
  578.         $data $this->db->fetchAssociative('SELECT o_modificationDate, o_versionCount  from objects WHERE o_id = ?', [$this->model->getId()]);
  579.         return $data
  580.             && $data['o_modificationDate'] == $this->model->__getDataVersionTimestamp()
  581.             && $data['o_versionCount'] == $this->model->getVersionCount();
  582.     }
  583. }