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

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