/** * @return $this * @throws \Exception */ public function save() { $isUpdate = false; if ($this->getId()) { $isUpdate = true; \Pimcore::getEventManager()->trigger("document.preUpdate", $this); } else { \Pimcore::getEventManager()->trigger("document.preAdd", $this); } // we wrap the save actions in a loop here, so that we can restart the database transactions in the case it fails // if a transaction fails it gets restarted $maxRetries times, then the exception is thrown out // this is especially useful to avoid problems with deadlocks in multi-threaded environments (forked workers, ...) $maxRetries = 5; for ($retries = 0; $retries < $maxRetries; $retries++) { $this->beginTransaction(); try { // check for a valid key, home has no key, so omit the check if (!Tool::isValidKey($this->getKey()) && $this->getId() != 1) { throw new \Exception("invalid key for document with id [ " . $this->getId() . " ] key is: [" . $this->getKey() . "]"); } $this->correctPath(); // set date $this->setModificationDate(time()); if (!$this->getCreationDate()) { $this->setCreationDate(time()); } if (!$isUpdate) { $this->getResource()->create(); } // get the old path from the database before the update is done $oldPath = null; if ($isUpdate) { $oldPath = $this->getResource()->getCurrentFullPath(); } $this->update(); // if the old path is different from the new path, update all children $updatedChildren = array(); if ($oldPath && $oldPath != $this->getFullPath()) { $this->getResource()->updateWorkspaces(); $updatedChildren = $this->getResource()->updateChildsPaths($oldPath); } $this->commit(); break; // transaction was successfully completed, so we cancel the loop here -> no restart required } catch (\Exception $e) { try { $this->rollBack(); } catch (\Exception $er) { // PDO adapter throws exceptions if rollback fails \Logger::error($er); } // we try to start the transaction $maxRetries times again (deadlocks, ...) if ($retries < $maxRetries - 1) { $run = $retries + 1; $waitTime = 100000; // microseconds \Logger::warn("Unable to finish transaction (" . $run . ". run) because of the following reason '" . $e->getMessage() . "'. --> Retrying in " . $waitTime . " microseconds ... (" . ($run + 1) . " of " . $maxRetries . ")"); usleep($waitTime); // wait specified time until we restart the transaction } else { // if the transaction still fail after $maxRetries retries, we throw out the exception throw $e; } } } $additionalTags = array(); if (isset($updatedChildren) && is_array($updatedChildren)) { foreach ($updatedChildren as $documentId) { $tag = "document_" . $documentId; $additionalTags[] = $tag; // remove the child also from registry (internal cache) to avoid path inconsistencies during long running scripts, such as CLI \Zend_Registry::set($tag, null); } } $this->clearDependentCache($additionalTags); if ($isUpdate) { \Pimcore::getEventManager()->trigger("document.postUpdate", $this); } else { \Pimcore::getEventManager()->trigger("document.postAdd", $this); } return $this; }
/** * @return $this * @throws \Exception */ public function save() { $isUpdate = false; if ($this->getId()) { $isUpdate = true; \Pimcore::getEventManager()->trigger("object.preUpdate", $this); } else { \Pimcore::getEventManager()->trigger("object.preAdd", $this); } // we wrap the save actions in a loop here, so that we can restart the database transactions in the case it fails // if a transaction fails it gets restarted $maxRetries times, then the exception is thrown out // this is especially useful to avoid problems with deadlocks in multi-threaded environments (forked workers, ...) $maxRetries = 5; for ($retries = 0; $retries < $maxRetries; $retries++) { // be sure that unpublished objects in relations are saved also in frontend mode, eg. in importers, ... $hideUnpublishedBackup = self::getHideUnpublished(); self::setHideUnpublished(false); $this->beginTransaction(); try { if (!Tool::isValidKey($this->getKey()) && $this->getId() != 1) { throw new \Exception("invalid key for object with id [ " . $this->getId() . " ] key is: [" . $this->getKey() . "]"); } if (!in_array($this->getType(), self::$types)) { throw new \Exception("invalid object type given: [" . $this->getType() . "]"); } $this->correctPath(); if (!$isUpdate) { $this->getResource()->create(); } // get the old path from the database before the update is done $oldPath = null; if ($isUpdate) { $oldPath = $this->getResource()->getCurrentFullPath(); } // if the old path is different from the new path, update all children // we need to do the update of the children's path before $this->update() because the // inheritance helper needs the correct paths of the children in InheritanceHelper::buildTree() $updatedChildren = array(); if ($oldPath && $oldPath != $this->getFullPath()) { $this->getResource()->updateWorkspaces(); $updatedChildren = $this->getResource()->updateChildsPaths($oldPath); } $this->update(); self::setHideUnpublished($hideUnpublishedBackup); $this->commit(); break; // transaction was successfully completed, so we cancel the loop here -> no restart required } catch (\Exception $e) { try { $this->rollBack(); } catch (\Exception $er) { // PDO adapter throws exceptions if rollback fails \Logger::info($er); } // set "HideUnpublished" back to the value it was originally self::setHideUnpublished($hideUnpublishedBackup); // we try to start the transaction $maxRetries times again (deadlocks, ...) if ($retries < $maxRetries - 1) { $run = $retries + 1; $waitTime = 100000; // microseconds \Logger::warn("Unable to finish transaction (" . $run . ". run) because of the following reason '" . $e->getMessage() . "'. --> Retrying in " . $waitTime . " microseconds ... (" . ($run + 1) . " of " . $maxRetries . ")"); usleep($waitTime); // wait specified time until we restart the transaction } else { // if the transaction still fail after $maxRetries retries, we throw out the exception \Logger::error("Finally giving up restarting the same transaction again and again, last message: " . $e->getMessage()); throw $e; } } } $additionalTags = array(); if (isset($updatedChildren) && is_array($updatedChildren)) { foreach ($updatedChildren as $objectId) { $tag = "object_" . $objectId; $additionalTags[] = $tag; // remove the child also from registry (internal cache) to avoid path inconsistencies during long running scripts, such as CLI \Zend_Registry::set($tag, null); } } $this->clearDependentCache($additionalTags); if ($isUpdate) { \Pimcore::getEventManager()->trigger("object.postUpdate", $this); } else { \Pimcore::getEventManager()->trigger("object.postAdd", $this); } return $this; }
/** * @throws \Exception */ public function correctPath() { // set path if ($this->getId() != 1) { // not for the root node if ($this->getParentId() == $this->getId()) { throw new \Exception("ParentID and ID is identical, an element can't be the parent of itself."); } if ($this->getFilename() === '..' || $this->getFilename() === '.') { throw new \Exception('Cannot create asset called ".." or "."'); } if (!Tool::isValidKey($this->getKey())) { throw new \Exception("invalid filename '" . $this->getKey() . "' for asset with id [ " . $this->getId() . " ]"); } $parent = Asset::getById($this->getParentId()); if ($parent) { // use the parent's path from the database here (getCurrentFullPath), to ensure the path really exists and does not rely on the path // that is currently in the parent object (in memory), because this might have changed but wasn't not saved $this->setPath(str_replace("//", "/", $parent->getCurrentFullPath() . "/")); } else { // parent document doesn't exist anymore, set the parent to to root $this->setParentId(1); $this->setPath("/"); } } elseif ($this->getId() == 1) { // some data in root node should always be the same $this->setParentId(0); $this->setPath("/"); $this->setFilename(""); $this->setType("folder"); } // do not allow PHP and .htaccess files if (preg_match("@\\.ph(p[345]?|t|tml|ps)\$@i", $this->getFilename()) || $this->getFilename() == ".htaccess") { $this->setFilename($this->getFilename() . ".txt"); } if (Asset\Service::pathExists($this->getRealFullPath())) { $duplicate = Asset::getByPath($this->getRealFullPath()); if ($duplicate instanceof Asset and $duplicate->getId() != $this->getId()) { throw new \Exception("Duplicate full path [ " . $this->getRealFullPath() . " ] - cannot save asset"); } } if (strlen($this->getRealFullPath()) > 765) { throw new \Exception("Full path is limited to 765 characters, reduce the length of your parent's path"); } }