vendor/pimcore/pimcore/models/Document/Dao.php line 60

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\Document;
  15. use Pimcore\Db\Helper;
  16. use Pimcore\Logger;
  17. use Pimcore\Model;
  18. use Pimcore\Model\User;
  19. use Pimcore\Tool\Serialize;
  20. /**
  21.  * @internal
  22.  *
  23.  * @property \Pimcore\Model\Document $model
  24.  */
  25. class Dao extends Model\Element\Dao
  26. {
  27.     use Model\Element\Traits\ScheduledTasksDaoTrait;
  28.     /**
  29.      * Fetch a row by an id from the database and assign variables to the document model.
  30.      *
  31.      * @param int $id
  32.      *
  33.      * @throws Model\Exception\NotFoundException
  34.      */
  35.     public function getById($id)
  36.     {
  37.         $data $this->db->fetchAssociative("SELECT documents.*, tree_locks.locked FROM documents
  38.             LEFT JOIN tree_locks ON documents.id = tree_locks.id AND tree_locks.type = 'document'
  39.                 WHERE documents.id = ?", [$id]);
  40.         if (!empty($data['id'])) {
  41.             $this->assignVariablesToModel($data);
  42.         } else {
  43.             throw new  Model\Exception\NotFoundException('document with id ' $id ' not found');
  44.         }
  45.     }
  46.     /**
  47.      * Fetch a row by a path from the database and assign variables to the model.
  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 id FROM documents WHERE path = BINARY :path AND `key` = BINARY :key'$params);
  57.         if (!empty($data['id'])) {
  58.             $this->assignVariablesToModel($data);
  59.         } else {
  60.             // try to find a page with a pretty URL (use the original $path)
  61.             $data $this->db->fetchAssociative('SELECT id FROM documents_page WHERE prettyUrl = :prettyUrl', [
  62.                 'prettyUrl' => $path,
  63.             ]);
  64.             if (!empty($data['id'])) {
  65.                 $this->assignVariablesToModel($data);
  66.             } else {
  67.                 throw new Model\Exception\NotFoundException("document with path $path doesn't exist");
  68.             }
  69.         }
  70.     }
  71.     public function create()
  72.     {
  73.         $this->db->insert('documents', [
  74.             'key' => $this->model->getKey(),
  75.             'type' => $this->model->getType(),
  76.             'path' => $this->model->getRealPath(),
  77.             'parentId' => $this->model->getParentId(),
  78.             'index' => 0,
  79.         ]);
  80.         $this->model->setId((int) $this->db->lastInsertId());
  81.         if (!$this->model->getKey()) {
  82.             $this->model->setKey((string) $this->model->getId());
  83.         }
  84.     }
  85.     /**
  86.      * @throws \Exception
  87.      */
  88.     public function update()
  89.     {
  90.         $typeSpecificTable null;
  91.         $validColumnsTypeSpecific = [];
  92.         $documentsConfig \Pimcore\Config::getSystemConfiguration('documents');
  93.         $validTables $documentsConfig['valid_tables'];
  94.         if (in_array($this->model->getType(), $validTables)) {
  95.             $typeSpecificTable 'documents_' $this->model->getType();
  96.             $validColumnsTypeSpecific $this->getValidTableColumns($typeSpecificTable);
  97.         }
  98.         $document $this->model->getObjectVars();
  99.         $dataDocument = [];
  100.         $dataTypeSpecific = [];
  101.         foreach ($document as $key => $value) {
  102.             // check if the getter exists
  103.             $getter 'get' ucfirst($key);
  104.             if (!method_exists($this->model$getter)) {
  105.                 continue;
  106.             }
  107.             // get the value from the getter
  108.             if (in_array($key$this->getValidTableColumns('documents')) || in_array($key$validColumnsTypeSpecific)) {
  109.                 $value $this->model->$getter();
  110.             } else {
  111.                 continue;
  112.             }
  113.             if (is_bool($value)) {
  114.                 $value = (int)$value;
  115.             }
  116.             if (is_array($value)) {
  117.                 $value Serialize::serialize($value);
  118.             }
  119.             if (in_array($key$this->getValidTableColumns('documents'))) {
  120.                 $dataDocument[$key] = $value;
  121.             }
  122.             if (in_array($key$validColumnsTypeSpecific)) {
  123.                 $dataTypeSpecific[$key] = $value;
  124.             }
  125.         }
  126.         // use the real document path, just for the case that a documents gets saved in the frontend
  127.         // and the page is within a site. see also: PIMCORE-2684
  128.         $dataDocument['path'] = $this->model->getRealPath();
  129.         // update the values in the database
  130.         Helper::insertOrUpdate($this->db'documents'$dataDocument);
  131.         if ($typeSpecificTable) {
  132.             Helper::insertOrUpdate($this->db$typeSpecificTable$dataTypeSpecific);
  133.         }
  134.         $this->updateLocks();
  135.     }
  136.     /**
  137.      * Delete the row from the database. (based on the model id)
  138.      *
  139.      * @throws \Exception
  140.      */
  141.     public function delete()
  142.     {
  143.         $this->db->delete('documents', ['id' => $this->model->getId()]);
  144.     }
  145.     /**
  146.      * Update document workspaces..
  147.      *
  148.      * @throws \Exception
  149.      */
  150.     public function updateWorkspaces()
  151.     {
  152.         $this->db->update('users_workspaces_document', [
  153.             'cpath' => $this->model->getRealFullPath(),
  154.         ], [
  155.             'cid' => $this->model->getId(),
  156.         ]);
  157.     }
  158.     /**
  159.      * Updates children path in order to the old document path specified in the $oldPath parameter.
  160.      *
  161.      * @internal
  162.      *
  163.      * @param string $oldPath
  164.      *
  165.      * @return array
  166.      */
  167.     public function updateChildPaths($oldPath)
  168.     {
  169.         //get documents to empty their cache
  170.         $documents $this->db->fetchAllAssociative('SELECT id, CONCAT(path,`key`) AS path FROM documents WHERE path LIKE ?', [Helper::escapeLike($oldPath) . '%']);
  171.         $userId '0';
  172.         if ($user \Pimcore\Tool\Admin::getCurrentUser()) {
  173.             $userId $user->getId();
  174.         }
  175.         //update documents child paths
  176.         // we don't update the modification date here, as this can have side-effects when there's an unpublished version for an element
  177.         $this->db->executeQuery('update documents set path = replace(path,' $this->db->quote($oldPath '/') . ',' $this->db->quote($this->model->getRealFullPath() . '/') . "), userModification = '" $userId "' where path like " $this->db->quote(Helper::escapeLike($oldPath) . '/%') . ';');
  178.         //update documents child permission paths
  179.         $this->db->executeQuery('update users_workspaces_document set cpath = replace(cpath,' $this->db->quote($oldPath '/') . ',' $this->db->quote($this->model->getRealFullPath() . '/') . ') where cpath like ' $this->db->quote(Helper::escapeLike($oldPath) . '/%') . ';');
  180.         //update documents child properties paths
  181.         $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) . '/%') . ';');
  182.         return $documents;
  183.     }
  184.     /**
  185.      * Returns the current full document path from the database.
  186.      *
  187.      * @return string
  188.      */
  189.     public function getCurrentFullPath()
  190.     {
  191.         $path null;
  192.         try {
  193.             $path $this->db->fetchOne('SELECT CONCAT(path,`key`) as path FROM documents WHERE id = ?', [$this->model->getId()]);
  194.         } catch (\Exception $e) {
  195.             Logger::error('could not  get current document path from DB');
  196.         }
  197.         return $path;
  198.     }
  199.     /**
  200.      * @return int
  201.      */
  202.     public function getVersionCountForUpdate(): int
  203.     {
  204.         if (!$this->model->getId()) {
  205.             return 0;
  206.         }
  207.         $versionCount = (int) $this->db->fetchOne('SELECT versionCount FROM documents WHERE id = ? FOR UPDATE', [$this->model->getId()]);
  208.         if ($this->model instanceof PageSnippet) {
  209.             $versionCount2 = (int) $this->db->fetchOne("SELECT MAX(versionCount) FROM versions WHERE cid = ? AND ctype = 'document'", [$this->model->getId()]);
  210.             $versionCount max($versionCount$versionCount2);
  211.         }
  212.         return (int) $versionCount;
  213.     }
  214.     /**
  215.      * Returns properties for the object from the database and assigns these.
  216.      *
  217.      * @param bool $onlyInherited
  218.      * @param bool $onlyDirect
  219.      *
  220.      * @return array
  221.      */
  222.     public function getProperties($onlyInherited false$onlyDirect false)
  223.     {
  224.         $properties = [];
  225.         if ($onlyDirect) {
  226.             $propertiesRaw $this->db->fetchAllAssociative("SELECT * FROM properties WHERE cid = ? AND ctype='document'", [$this->model->getId()]);
  227.         } else {
  228.             $parentIds $this->getParentIds();
  229.             $propertiesRaw $this->db->fetchAllAssociative('SELECT * FROM properties WHERE ((cid IN (' implode(','$parentIds) . ") AND inheritable = 1) OR cid = ? )  AND ctype='document'", [$this->model->getId()]);
  230.         }
  231.         // because this should be faster than mysql
  232.         usort($propertiesRaw, function ($left$right) {
  233.             return strcmp($left['cpath'], $right['cpath']);
  234.         });
  235.         foreach ($propertiesRaw as $propertyRaw) {
  236.             try {
  237.                 $property = new Model\Property();
  238.                 $property->setType($propertyRaw['type']);
  239.                 $property->setCid($this->model->getId());
  240.                 $property->setName($propertyRaw['name']);
  241.                 $property->setCtype('document');
  242.                 $property->setDataFromResource($propertyRaw['data']);
  243.                 $property->setInherited(true);
  244.                 if ($propertyRaw['cid'] == $this->model->getId()) {
  245.                     $property->setInherited(false);
  246.                 }
  247.                 $property->setInheritable(false);
  248.                 if ($propertyRaw['inheritable']) {
  249.                     $property->setInheritable(true);
  250.                 }
  251.                 if ($onlyInherited && !$property->getInherited()) {
  252.                     continue;
  253.                 }
  254.                 $properties[$propertyRaw['name']] = $property;
  255.             } catch (\Exception $e) {
  256.                 Logger::error("can't add property " $propertyRaw['name'] . ' to document ' $this->model->getRealFullPath());
  257.             }
  258.         }
  259.         // if only inherited then only return it and dont call the setter in the model
  260.         if ($onlyInherited || $onlyDirect) {
  261.             return $properties;
  262.         }
  263.         $this->model->setProperties($properties);
  264.         return $properties;
  265.     }
  266.     /**
  267.      * Deletes all object properties from the database.
  268.      */
  269.     public function deleteAllProperties()
  270.     {
  271.         $this->db->delete('properties', ['cid' => $this->model->getId(), 'ctype' => 'document']);
  272.     }
  273.     /**
  274.      * Quick check if there are children.
  275.      *
  276.      * @param bool|null $includingUnpublished
  277.      * @param Model\User $user
  278.      *
  279.      * @return bool
  280.      */
  281.     public function hasChildren($includingUnpublished null$user null)
  282.     {
  283.         if (!$this->model->getId()) {
  284.             return false;
  285.         }
  286.         $sql 'SELECT id FROM documents d WHERE parentId = ? ';
  287.         if ($user && !$user->isAdmin()) {
  288.             $userIds $user->getRoles();
  289.             $currentUserId $user->getId();
  290.             $userIds[] = $currentUserId;
  291.             $inheritedPermission $this->isInheritingPermission('list'$userIds);
  292.             $anyAllowedRowOrChildren 'EXISTS(SELECT list FROM users_workspaces_document uwd WHERE userId IN (' implode(','$userIds) . ') AND list=1 AND LOCATE(CONCAT(d.path,d.`key`),cpath)=1 AND
  293.                 NOT EXISTS(SELECT list FROM users_workspaces_document WHERE userId =' $currentUserId '  AND list=0 AND cpath = uwd.cpath))';
  294.             $isDisallowedCurrentRow 'EXISTS(SELECT list FROM users_workspaces_document WHERE userId IN (' implode(','$userIds) . ')  AND cid = id AND list=0)';
  295.             $sql .= ' AND IF(' $anyAllowedRowOrChildren ',1,IF(' $inheritedPermission ', ' $isDisallowedCurrentRow ' = 0, 0)) = 1';
  296.         }
  297.         if ((isset($includingUnpublished) && !$includingUnpublished) || (!isset($includingUnpublished) && Model\Document::doHideUnpublished())) {
  298.             $sql .= ' AND published = 1';
  299.         }
  300.         $sql .= ' LIMIT 1';
  301.         $c $this->db->fetchOne($sql, [$this->model->getId()]);
  302.         return (bool)$c;
  303.     }
  304.     /**
  305.      * Returns the amount of children (not recursively),
  306.      *
  307.      * @param Model\User $user
  308.      *
  309.      * @return int
  310.      */
  311.     public function getChildAmount($user null)
  312.     {
  313.         if (!$this->model->getId()) {
  314.             return 0;
  315.         }
  316.         $sql 'SELECT count(*) FROM documents d WHERE parentId = ? ';
  317.         if ($user && !$user->isAdmin()) {
  318.             $userIds $user->getRoles();
  319.             $currentUserId $user->getId();
  320.             $userIds[] = $currentUserId;
  321.             $inheritedPermission $this->isInheritingPermission('list'$userIds);
  322.             $anyAllowedRowOrChildren 'EXISTS(SELECT list FROM users_workspaces_document uwd WHERE userId IN (' implode(','$userIds) . ') AND list=1 AND LOCATE(CONCAT(d.path,d.`key`),cpath)=1 AND
  323.                 NOT EXISTS(SELECT list FROM users_workspaces_document WHERE userId =' $currentUserId '  AND list=0 AND cpath = uwd.cpath))';
  324.             $isDisallowedCurrentRow 'EXISTS(SELECT list FROM users_workspaces_document WHERE userId IN (' implode(','$userIds) . ')  AND cid = id AND list=0)';
  325.             $sql .= ' AND IF(' $anyAllowedRowOrChildren ',1,IF(' $inheritedPermission ', ' $isDisallowedCurrentRow ' = 0, 0)) = 1';
  326.         }
  327.         return (int) $this->db->fetchOne($sql, [$this->model->getId()]);
  328.     }
  329.     /**
  330.      * Checks if the document has siblings
  331.      *
  332.      * @param bool|null $includingUnpublished
  333.      *
  334.      * @return bool
  335.      */
  336.     public function hasSiblings($includingUnpublished null)
  337.     {
  338.         if (!$this->model->getParentId()) {
  339.             return false;
  340.         }
  341.         $sql 'SELECT id FROM documents WHERE parentId = ?';
  342.         $params = [$this->model->getParentId()];
  343.         if ($this->model->getId()) {
  344.             $sql .= ' AND id != ?';
  345.             $params[] = $this->model->getId();
  346.         }
  347.         if ((isset($includingUnpublished) && !$includingUnpublished) || (!isset($includingUnpublished) && Model\Document::doHideUnpublished())) {
  348.             $sql .= ' AND published = 1';
  349.         }
  350.         $sql .= ' LIMIT 1';
  351.         $c $this->db->fetchOne($sql$params);
  352.         return (bool)$c;
  353.     }
  354.     /**
  355.      * Checks if the document is locked.
  356.      *
  357.      * @return bool
  358.      *
  359.      * @throws \Exception
  360.      */
  361.     public function isLocked()
  362.     {
  363.         // check for an locked element below this element
  364.         $belowLocks $this->db->fetchOne("SELECT tree_locks.id FROM tree_locks
  365.             INNER JOIN documents ON tree_locks.id = documents.id
  366.                 WHERE documents.path LIKE ? AND tree_locks.type = 'document' 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='document' AND locked = 'propagate' LIMIT 1");
  372.         if ($inhertitedLocks 0) {
  373.             return true;
  374.         }
  375.         return false;
  376.     }
  377.     /**
  378.      * Update the lock value for the document.
  379.      *
  380.      * @throws \Exception
  381.      */
  382.     public function updateLocks()
  383.     {
  384.         $this->db->delete('tree_locks', ['id' => $this->model->getId(), 'type' => 'document']);
  385.         if ($this->model->getLocked()) {
  386.             $this->db->insert('tree_locks', [
  387.                 'id' => $this->model->getId(),
  388.                 'type' => 'document',
  389.                 'locked' => $this->model->getLocked(),
  390.             ]);
  391.         }
  392.     }
  393.     /**
  394.      * Deletes locks from the document and its children.
  395.      *
  396.      * @return array
  397.      */
  398.     public function unlockPropagate()
  399.     {
  400.         $lockIds $this->db->fetchFirstColumn('SELECT id from documents WHERE path LIKE ' $this->db->quote(Helper::escapeLike($this->model->getRealFullPath()) . '/%') . ' OR id = ' $this->model->getId());
  401.         $this->db->executeStatement("DELETE FROM tree_locks WHERE type = 'document' AND id IN (" implode(','$lockIds) . ')');
  402.         return $lockIds;
  403.     }
  404.     /**
  405.      * @param string $type
  406.      * @param array $userIds
  407.      *
  408.      * @return int
  409.      *
  410.      * @throws \Doctrine\DBAL\Exception
  411.      */
  412.     public function isInheritingPermission(string $type, array $userIds)
  413.     {
  414.         return $this->InheritingPermission($type$userIds'document');
  415.     }
  416.     /**
  417.      * Checks if the action is allowed.
  418.      *
  419.      * @param string $type
  420.      * @param Model\User $user
  421.      *
  422.      * @return bool
  423.      */
  424.     public function isAllowed($type$user)
  425.     {
  426.         // collect properties via parent - ids
  427.         $parentIds = [1];
  428.         $obj $this->model->getParent();
  429.         if ($obj) {
  430.             while ($obj) {
  431.                 $parentIds[] = $obj->getId();
  432.                 $obj $obj->getParent();
  433.             }
  434.         }
  435.         if ($id $this->model->getId()) {
  436.             $parentIds[] = $id;
  437.         }
  438.         $userIds $user->getRoles();
  439.         $userIds[] = $user->getId();
  440.         try {
  441.             $permissionsParent $this->db->fetchOne('SELECT ' $this->db->quoteIdentifier($type) . ' FROM users_workspaces_document 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');
  442.             if ($permissionsParent) {
  443.                 return true;
  444.             }
  445.             // exception for list permission
  446.             if (empty($permissionsParent) && $type == 'list') {
  447.                 // check for children with permissions
  448.                 $path $this->model->getRealFullPath() . '/';
  449.                 if ($this->model->getId() == 1) {
  450.                     $path '/';
  451.                 }
  452.                 $permissionsChildren $this->db->fetchOne('SELECT list FROM users_workspaces_document WHERE cpath LIKE ? AND userId IN (' implode(','$userIds) . ') AND list = 1 LIMIT 1', [Helper::escapeLike($path) . '%']);
  453.                 if ($permissionsChildren) {
  454.                     return true;
  455.                 }
  456.             }
  457.         } catch (\Exception $e) {
  458.             Logger::warn('Unable to get permission ' $type ' for document ' $this->model->getId());
  459.         }
  460.         return false;
  461.     }
  462.     /**
  463.      * @param array $columns
  464.      * @param User $user
  465.      *
  466.      * @return array<string, int>
  467.      *
  468.      */
  469.     public function areAllowed(array $columnsUser $user)
  470.     {
  471.         return $this->permissionByTypes($columns$user'document');
  472.     }
  473.     /**
  474.      * Save the document index.
  475.      *
  476.      * @param int $index
  477.      */
  478.     public function saveIndex($index)
  479.     {
  480.         $this->db->update('documents', [
  481.             'index' => $index,
  482.         ], [
  483.             'id' => $this->model->getId(),
  484.         ]);
  485.     }
  486.     /**
  487.      * Fetches the maximum index value from siblings.
  488.      *
  489.      * @return int
  490.      */
  491.     public function getNextIndex()
  492.     {
  493.         $index $this->db->fetchOne('SELECT MAX(`index`) FROM documents WHERE parentId = ?', [$this->model->getParentId()]);
  494.         $index++;
  495.         return $index;
  496.     }
  497.     /**
  498.      * @return bool
  499.      */
  500.     public function __isBasedOnLatestData()
  501.     {
  502.         $data $this->db->fetchAssociative('SELECT modificationDate,versionCount from documents WHERE id = ?', [$this->model->getId()]);
  503.         if ($data['modificationDate'] == $this->model->__getDataVersionTimestamp() && $data['versionCount'] == $this->model->getVersionCount()) {
  504.             return true;
  505.         }
  506.         return false;
  507.     }
  508. }