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

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