* @copyright Copyright (C) 2002-2005 Markus Westphal, 2006-2008 Malcolm Cowe, * 2010 Matteo Lucarelli, 2010 Uwe Steinmann * @version Release: @package_version@ */ /** * The different states a document can be in */ /* * Document is in review state. A document is in review state when * it needs to be reviewed by a user or group. */ define("S_DRAFT_REV", 0); /* * Document is in approval state. A document is in approval state when * it needs to be approved by a user or group. */ define("S_DRAFT_APP", 1); /* * Document is released. A document is in release state either when * it needs no review or approval after uploaded or has been reviewed * and/or approved. */ define("S_RELEASED", 2); /* * Document is in workflow. A document is in workflow if a workflow * has been started and has not reached a final state. */ define("S_IN_WORKFLOW", 3); /* * Document was rejected. A document is in rejected state when * the review failed or approval was not given. */ define("S_REJECTED", -1); /* * Document is obsolete. A document can be obsoleted once it was * released. */ define("S_OBSOLETE", -2); /* * Document is expired. A document expires when the expiration date * is reached */ define("S_EXPIRED", -3); /** * The different states a workflow log can be in. This is used in * all tables tblDocumentXXXLog */ /* * workflow is in a neutral status waiting for action of user */ define("S_LOG_WAITING", 0); /* * workflow has been successful ended. The document content has been * approved, reviewed, aknowledged or revised */ define("S_LOG_ACCEPTED", 1); /* * workflow has been unsuccessful ended. The document content has been * rejected */ define("S_LOG_REJECTED", -1); /* * user has been removed from workflow. This can be for different reasons * 1. the user has been actively removed from the workflow, 2. the user has * been deleted. */ define("S_LOG_USER_REMOVED", -2); /* * workflow is sleeping until reactivation. The workflow has been set up * but not started. This is only valid for the revision workflow, which * may run over and over again. */ define("S_LOG_SLEEPING", -3); /** * Class to represent a document in the document management system * * A document in SeedDMS is similar to files in a regular file system. * Documents may have any number of content elements * ({@link SeedDMS_Core_DocumentContent}). These content elements are often * called versions ordered in a timely manner. The most recent content element * is the current version. * * Documents can be linked to other documents and can have attached files. * The document content can be anything that can be stored in a regular * file. * * @category DMS * @package SeedDMS_Core * @author Markus Westphal, Malcolm Cowe, Matteo Lucarelli, * Uwe Steinmann * @copyright Copyright (C) 2002-2005 Markus Westphal, 2006-2008 Malcolm Cowe, * 2010 Matteo Lucarelli, 2010 Uwe Steinmann * @version Release: @package_version@ */ class SeedDMS_Core_Document extends SeedDMS_Core_Object { /* {{{ */ /** * @var string name of document */ protected $_name; /** * @var string comment of document */ protected $_comment; /** * @var integer unix timestamp of creation date */ protected $_date; /** * @var integer id of user who is the owner */ protected $_ownerID; /** * @var integer id of folder this document belongs to */ protected $_folderID; /** * @var integer timestamp of expiration date */ protected $_expires; /** * @var boolean true if access is inherited, otherwise false */ protected $_inheritAccess; /** * @var integer default access if access rights are not inherited */ protected $_defaultAccess; /** * @var array list of notifications for users and groups */ protected $_readAccessList; /** * @var array list of notifications for users and groups */ public $_notifyList; /** * @var boolean true if document is locked, otherwise false */ protected $_locked; /** * @var string list of keywords */ protected $_keywords; /** * @var SeedDMS_Core_DocumentCategory[] list of categories */ protected $_categories; /** * @var integer position of document within the parent folder */ protected $_sequence; /** * @var SeedDMS_Core_DocumentContent temp. storage for latestcontent */ protected $_latestContent; /** * @var array temp. storage for content */ protected $_content; /** * @var SeedDMS_Core_Folder */ protected $_folder; /** @var array of SeedDMS_Core_UserAccess and SeedDMS_Core_GroupAccess */ protected $_accessList; function __construct($id, $name, $comment, $date, $expires, $ownerID, $folderID, $inheritAccess, $defaultAccess, $locked, $keywords, $sequence) { /* {{{ */ parent::__construct($id); $this->_name = $name; $this->_comment = $comment; $this->_date = $date; $this->_expires = $expires; $this->_ownerID = $ownerID; $this->_folderID = $folderID; $this->_inheritAccess = $inheritAccess; $this->_defaultAccess = $defaultAccess; $this->_locked = ($locked == null || $locked == '' ? -1 : $locked); $this->_keywords = $keywords; $this->_sequence = $sequence; $this->_categories = array(); $this->_notifyList = array(); $this->_latestContent = null; $this->_content = null; } /* }}} */ /** * Check if this object is of type 'document'. * * @param string $type type of object */ public function isType($type) { /* {{{ */ return $type == 'document'; } /* }}} */ /** * Return an array of database fields which used for searching * a term entered in the database search form * * @param SeedDMS_Core_DMS $dms * @param array $searchin integer list of search scopes (2=name, 3=comment, * 4=attributes) * @return array list of database fields */ public static function getSearchFields($dms, $searchin) { /* {{{ */ $db = $dms->getDB(); $searchFields = array(); if (in_array(1, $searchin)) { $searchFields[] = "`tblDocuments`.`keywords`"; } if (in_array(2, $searchin)) { $searchFields[] = "`tblDocuments`.`name`"; } if (in_array(3, $searchin)) { $searchFields[] = "`tblDocuments`.`comment`"; $searchFields[] = "`tblDocumentContent`.`comment`"; } if (in_array(4, $searchin)) { $searchFields[] = "`tblDocumentAttributes`.`value`"; $searchFields[] = "`tblDocumentContentAttributes`.`value`"; } if (in_array(5, $searchin)) { $searchFields[] = $db->castToText("`tblDocuments`.`id`"); } return $searchFields; } /* }}} */ /** * Return a folder by its database record * * @param array $resArr array of folder data as returned by database * @param SeedDMS_Core_DMS $dms * @return SeedDMS_Core_Folder|bool instance of SeedDMS_Core_Folder if document exists */ public static function getInstanceByData($resArr, $dms) { /* {{{ */ $classname = $dms->getClassname('document'); /** @var SeedDMS_Core_Document $document */ $document = new $classname($resArr["id"], $resArr["name"], $resArr["comment"], $resArr["date"], $resArr["expires"], $resArr["owner"], $resArr["folder"], $resArr["inheritAccess"], $resArr["defaultAccess"], $resArr['lock'], $resArr["keywords"], $resArr["sequence"]); $document->setDMS($dms); $document = $document->applyDecorators(); return $document; } /* }}} */ /** * Return an document by its id * * @param integer $id id of document * @param SeedDMS_Core_DMS $dms * @return bool|SeedDMS_Core_Document instance of SeedDMS_Core_Document if document exists, null * if document does not exist, false in case of error */ public static function getInstance($id, $dms) { /* {{{ */ $db = $dms->getDB(); // $queryStr = "SELECT * FROM `tblDocuments` WHERE `id` = " . (int) $id; $queryStr = "SELECT `tblDocuments`.*, `tblDocumentLocks`.`userID` as `lock` FROM `tblDocuments` LEFT JOIN `tblDocumentLocks` ON `tblDocuments`.`id` = `tblDocumentLocks`.`document` WHERE `id` = " . (int) $id; $resArr = $db->getResultArray($queryStr); if (is_bool($resArr) && $resArr == false) return false; if (count($resArr) != 1) return null; $resArr = $resArr[0]; // New Locking mechanism uses a separate table to track the lock. /* $queryStr = "SELECT * FROM `tblDocumentLocks` WHERE `document` = " . (int) $id; $lockArr = $db->getResultArray($queryStr); if ((is_bool($lockArr) && $lockArr==false) || (count($lockArr)==0)) { // Could not find a lock on the selected document. $resArr['lock'] = -1; } else { // A lock has been identified for this document. $resArr['lock'] = $lockArr[0]["userID"]; } */ $resArr['lock'] = !$resArr['lock'] ? -1 : $resArr['lock']; // print_r($resArr);exit; return self::getInstanceByData($resArr, $dms); $classname = $dms->getClassname('document'); /** @var SeedDMS_Core_Document $document */ $document = new $classname($resArr["id"], $resArr["name"], $resArr["comment"], $resArr["date"], $resArr["expires"], $resArr["owner"], $resArr["folder"], $resArr["inheritAccess"], $resArr["defaultAccess"], $resArr['lock'], $resArr["keywords"], $resArr["sequence"]); $document->setDMS($dms); $document = $document->applyDecorators(); return $document; } /* }}} */ /** * Apply decorators * * @return object final object after all decorators has been applied */ function applyDecorators() { /* {{{ */ if($decorators = $this->_dms->getDecorators('document')) { $s = $this; foreach($decorators as $decorator) { $s = new $decorator($s); } return $s; } else { return $this; } } /* }}} */ /** * Return the directory of the document in the file system relativ * to the contentDir * * @return string directory of document */ function getDir() { /* {{{ */ if($this->_dms->maxDirID) { $dirid = (int) (($this->_id-1) / $this->_dms->maxDirID) + 1; return $dirid."/".$this->_id."/"; } else { return $this->_id."/"; } } /* }}} */ /** * Return the name of the document * * @return string name of document */ function getName() { return $this->_name; } /** * Set the name of the document * * @param $newName string new name of document * @return bool */ function setName($newName) { /* {{{ */ $db = $this->_dms->getDB(); $queryStr = "UPDATE `tblDocuments` SET `name` = ".$db->qstr($newName)." WHERE `id` = ". $this->_id; if (!$db->getResult($queryStr)) return false; $this->_name = $newName; return true; } /* }}} */ /** * Return the comment of the document * * @return string comment of document */ function getComment() { return $this->_comment; } /** * Set the comment of the document * * @param $newComment string new comment of document * @return bool */ function setComment($newComment) { /* {{{ */ $db = $this->_dms->getDB(); $queryStr = "UPDATE `tblDocuments` SET `comment` = ".$db->qstr($newComment)." WHERE `id` = ". $this->_id; if (!$db->getResult($queryStr)) return false; $this->_comment = $newComment; return true; } /* }}} */ /** * @return string */ function getKeywords() { return $this->_keywords; } /** * @param string $newKeywords * @return bool */ function setKeywords($newKeywords) { /* {{{ */ $db = $this->_dms->getDB(); $queryStr = "UPDATE `tblDocuments` SET `keywords` = ".$db->qstr($newKeywords)." WHERE `id` = ". $this->_id; if (!$db->getResult($queryStr)) return false; $this->_keywords = $newKeywords; return true; } /* }}} */ /** * Retrieve a list of all categories this document belongs to * * @return bool|SeedDMS_Core_DocumentCategory[] */ function getCategories() { /* {{{ */ $db = $this->_dms->getDB(); if(!$this->_categories) { $queryStr = "SELECT * FROM `tblCategory` where `id` in (select `categoryID` from `tblDocumentCategory` where `documentID` = ".$this->_id.")"; $resArr = $db->getResultArray($queryStr); if (is_bool($resArr) && !$resArr) return false; foreach ($resArr as $row) { $cat = new SeedDMS_Core_DocumentCategory($row['id'], $row['name']); $cat->setDMS($this->_dms); $this->_categories[] = $cat; } } return $this->_categories; } /* }}} */ /** * Set a list of categories for the document * This function will delete currently assigned categories and sets new * categories. * * @param SeedDMS_Core_DocumentCategory[] $newCategories list of category objects * @return bool */ function setCategories($newCategories) { /* {{{ */ $db = $this->_dms->getDB(); $db->startTransaction(); $queryStr = "DELETE from `tblDocumentCategory` WHERE `documentID` = ". $this->_id; if (!$db->getResult($queryStr)) { $db->rollbackTransaction(); return false; } foreach($newCategories as $cat) { $queryStr = "INSERT INTO `tblDocumentCategory` (`categoryID`, `documentID`) VALUES (". $cat->getId() .", ". $this->_id .")"; if (!$db->getResult($queryStr)) { $db->rollbackTransaction(); return false; } } $db->commitTransaction(); $this->_categories = $newCategories; return true; } /* }}} */ /** * Add a list of categories to the document * This function will add a list of new categories to the document. * * @param array $newCategories list of category objects */ function addCategories($newCategories) { /* {{{ */ $db = $this->_dms->getDB(); if(!$this->_categories) $this->getCategories(); $catids = array(); foreach($this->_categories as $cat) $catids[] = $cat->getID(); $db->startTransaction(); $ncat = array(); // Array containing actually added new categories foreach($newCategories as $cat) { if(!in_array($cat->getID(), $catids)) { $queryStr = "INSERT INTO `tblDocumentCategory` (`categoryID`, `documentID`) VALUES (". $cat->getId() .", ". $this->_id .")"; if (!$db->getResult($queryStr)) { $db->rollbackTransaction(); return false; } $ncat[] = $cat; } } $db->commitTransaction(); $this->_categories = array_merge($this->_categories, $ncat); return true; } /* }}} */ /** * Remove a list of categories from the document * This function will remove a list of assigned categories to the document. * * @param array $newCategories list of category objects */ function removeCategories($categories) { /* {{{ */ $db = $this->_dms->getDB(); $catids = array(); foreach($categories as $cat) $catids[] = $cat->getID(); $queryStr = "DELETE from `tblDocumentCategory` WHERE `documentID` = ". $this->_id ." AND `categoryID` IN (".implode(',', $catids).")"; if (!$db->getResult($queryStr)) { return false; } $this->_categories = null; return true; } /* }}} */ /** * Return creation date of the document * * @return integer unix timestamp of creation date */ function getDate() { /* {{{ */ return $this->_date; } /* }}} */ /** * Set creation date of the document * * @param integer $date timestamp of creation date. If false then set it * to the current timestamp * @return boolean true on success */ function setDate($date) { /* {{{ */ $db = $this->_dms->getDB(); if(!$date) $date = time(); else { if(!is_numeric($date)) return false; } $queryStr = "UPDATE `tblDocuments` SET `date` = " . (int) $date . " WHERE `id` = ". $this->_id; if (!$db->getResult($queryStr)) return false; $this->_date = $date; return true; } /* }}} */ /** * Check, if this document is below of a given folder * * @param object $folder parent folder * @return boolean true if folder is a subfolder */ function isDescendant($folder) { /* {{{ */ /* First check if the parent folder is folder looking for */ if ($this->getFolder()->getID() == $folder->getID()) return true; /* Second, check for the parent folder of this document to be * below the given folder */ if($this->getFolder()->isDescendant($folder)) return true; return false; } /* }}} */ /** * Return the parent folder of the document * * @return SeedDMS_Core_Folder parent folder */ function getParent() { /* {{{ */ return $this->getFolder(); } /* }}} */ function getFolder() { /* {{{ */ if (!isset($this->_folder)) $this->_folder = $this->_dms->getFolder($this->_folderID); return $this->_folder; } /* }}} */ /** * Set folder of a document * * This function basically moves a document from a folder to another * folder. * * @param SeedDMS_Core_Folder $newFolder * @return boolean false in case of an error, otherwise true */ function setParent($newFolder) { /* {{{ */ return $this->setFolder($newFolder); } /* }}} */ /** * Set folder of a document * * This function basically moves a document from a folder to another * folder. * * @param SeedDMS_Core_Folder $newFolder * @return boolean false in case of an error, otherwise true */ function setFolder($newFolder) { /* {{{ */ $db = $this->_dms->getDB(); $queryStr = "UPDATE `tblDocuments` SET `folder` = " . $newFolder->getID() . " WHERE `id` = ". $this->_id; if (!$db->getResult($queryStr)) return false; $this->_folderID = $newFolder->getID(); /** @noinspection PhpUndefinedFieldInspection */ $this->_folder = $newFolder; // Make sure that the folder search path is also updated. $path = $newFolder->getPath(); $flist = ""; /** @var SeedDMS_Core_Folder[] $path */ foreach ($path as $f) { $flist .= ":".$f->getID(); } if (strlen($flist)>1) { $flist .= ":"; } $queryStr = "UPDATE `tblDocuments` SET `folderList` = '" . $flist . "' WHERE `id` = ". $this->_id; if (!$db->getResult($queryStr)) return false; return true; } /* }}} */ /** * Return owner of document * * @return SeedDMS_Core_User owner of document as an instance of {@link SeedDMS_Core_User} */ function getOwner() { /* {{{ */ if (!isset($this->_owner)) $this->_owner = $this->_dms->getUser($this->_ownerID); return $this->_owner; } /* }}} */ /** * Set owner of a document * * @param SeedDMS_Core_User $newOwner new owner * @return boolean true if successful otherwise false */ function setOwner($newOwner) { /* {{{ */ $db = $this->_dms->getDB(); $queryStr = "UPDATE `tblDocuments` set `owner` = " . $newOwner->getID() . " WHERE `id` = " . $this->_id; if (!$db->getResult($queryStr)) return false; $this->_ownerID = $newOwner->getID(); /** @noinspection PhpUndefinedFieldInspection */ $this->_owner = $newOwner; return true; } /* }}} */ /** * @return bool|int */ function getDefaultAccess() { /* {{{ */ if ($this->inheritsAccess()) { $res = $this->getFolder(); if (!$res) return false; return $this->_folder->getDefaultAccess(); } return $this->_defaultAccess; } /* }}} */ /** * Set default access mode * * This method sets the default access mode and also removes all notifiers which * will not have read access anymore. * * @param integer $mode access mode * @param bool|string $noclean set to true if notifier list shall not be clean up * @return bool */ function setDefaultAccess($mode, $noclean="false") { /* {{{ */ $db = $this->_dms->getDB(); $queryStr = "UPDATE `tblDocuments` set `defaultAccess` = " . (int) $mode . " WHERE `id` = " . $this->_id; if (!$db->getResult($queryStr)) return false; $this->_defaultAccess = $mode; if(!$noclean) $this->cleanNotifyList(); return true; } /* }}} */ /** * @return bool */ function inheritsAccess() { return $this->_inheritAccess; } /** * Set inherited access mode * Setting inherited access mode will set or unset the internal flag which * controls if the access mode is inherited from the parent folder or not. * It will not modify the * access control list for the current object. It will remove all * notifications of users which do not even have read access anymore * after setting or unsetting inherited access. * * @param boolean $inheritAccess set to true for setting and false for * unsetting inherited access mode * @param boolean $noclean set to true if notifier list shall not be clean up * @return boolean true if operation was successful otherwise false */ function setInheritAccess($inheritAccess, $noclean=false) { /* {{{ */ $db = $this->_dms->getDB(); $queryStr = "UPDATE `tblDocuments` SET `inheritAccess` = " . ($inheritAccess ? "1" : "0") . " WHERE `id` = " . $this->_id; if (!$db->getResult($queryStr)) return false; $this->_inheritAccess = ($inheritAccess ? "1" : "0"); if(!$noclean) $this->cleanNotifyList(); return true; } /* }}} */ /** * Check if document expires * * @return boolean true if document has expiration date set, otherwise false */ function expires() { /* {{{ */ if (intval($this->_expires) == 0) return false; else return true; } /* }}} */ /** * Get expiration time of document * * @return integer/boolean expiration date as unix timestamp or false */ function getExpires() { /* {{{ */ if (intval($this->_expires) == 0) return false; else return $this->_expires; } /* }}} */ /** * Set expiration date as unix timestamp * * @param integer $expires unix timestamp of expiration date * @return bool */ function setExpires($expires) { /* {{{ */ $db = $this->_dms->getDB(); $expires = (!$expires) ? 0 : $expires; if ($expires == $this->_expires) { // No change is necessary. return true; } $queryStr = "UPDATE `tblDocuments` SET `expires` = " . (int) $expires . " WHERE `id` = " . $this->_id; if (!$db->getResult($queryStr)) return false; $this->_expires = $expires; return true; } /* }}} */ /** * Check if the document has expired * * The method expects to database field 'expired' to hold the timestamp * of the start of day at which end the document expires. The document will * expire if that day is over. Hence, a document will *not* * be expired during the day of expiration but at the end of that day * * @return boolean true if document has expired otherwise false */ function hasExpired() { /* {{{ */ if (intval($this->_expires) == 0) return false; if (time()>=$this->_expires+24*60*60) return true; return false; } /* }}} */ /** * Check if the document has expired and set the status accordingly * It will also recalculate the status if the current status is * set to S_EXPIRED but the document isn't actually expired. * The method will update the document status log database table * if needed. * FIXME: some left over reviewers/approvers are in the way if * no workflow is set and traditional workflow mode is on. In that * case the status is set to S_DRAFT_REV or S_DRAFT_APP * * @return boolean true if status has changed */ function verifyLastestContentExpriry(){ /* {{{ */ $lc=$this->getLatestContent(); if($lc) { $st=$lc->getStatus(); if (($st["status"]==S_DRAFT_REV || $st["status"]==S_DRAFT_APP || $st["status"]==S_IN_WORKFLOW || $st["status"]==S_RELEASED) && $this->hasExpired()){ return $lc->setStatus(S_EXPIRED,"", $this->getOwner()); } elseif ($st["status"]==S_EXPIRED && !$this->hasExpired() ){ $lc->verifyStatus(true, $this->getOwner()); return true; } } return false; } /* }}} */ /** * Check if document is locked * * @return boolean true if locked otherwise false */ function isLocked() { return $this->_locked != -1; } /** * Lock or unlock document * * @param SeedDMS_Core_User|bool $falseOrUser user object for locking or false for unlocking * @return boolean true if operation was successful otherwise false */ function setLocked($falseOrUser) { /* {{{ */ $db = $this->_dms->getDB(); $lockUserID = -1; if (is_bool($falseOrUser) && !$falseOrUser) { $queryStr = "DELETE FROM `tblDocumentLocks` WHERE `document` = ".$this->_id; } else if (is_object($falseOrUser)) { $queryStr = "INSERT INTO `tblDocumentLocks` (`document`, `userID`) VALUES (".$this->_id.", ".$falseOrUser->getID().")"; $lockUserID = $falseOrUser->getID(); } else { return false; } if (!$db->getResult($queryStr)) { return false; } unset($this->_lockingUser); $this->_locked = $lockUserID; return true; } /* }}} */ /** * Get the user currently locking the document * * @return SeedDMS_Core_User|bool user have a lock */ function getLockingUser() { /* {{{ */ if (!$this->isLocked()) return false; if (!isset($this->_lockingUser)) $this->_lockingUser = $this->_dms->getUser($this->_locked); return $this->_lockingUser; } /* }}} */ /** * @return float */ function getSequence() { return $this->_sequence; } /** * @param float $seq * @return bool */ function setSequence($seq) { /* {{{ */ $db = $this->_dms->getDB(); $queryStr = "UPDATE `tblDocuments` SET `sequence` = " . $seq . " WHERE `id` = " . $this->_id; if (!$db->getResult($queryStr)) return false; $this->_sequence = $seq; return true; } /* }}} */ /** * Delete all entries for this document from the access control list * * @param boolean $noclean set to true if notifier list shall not be clean up * @return boolean true if operation was successful otherwise false */ function clearAccessList($noclean=false) { /* {{{ */ $db = $this->_dms->getDB(); $queryStr = "DELETE FROM `tblACLs` WHERE `targetType` = " . T_DOCUMENT . " AND `target` = " . $this->_id; if (!$db->getResult($queryStr)) return false; unset($this->_accessList); if(!$noclean) $this->cleanNotifyList(); return true; } /* }}} */ /** * Returns a list of access privileges * * If the document inherits the access privileges from the parent folder * those will be returned. * $mode and $op can be set to restrict the list of returned access * privileges. If $mode is set to M_ANY no restriction will apply * regardless of the value of $op. The returned array contains a list * of {@link SeedDMS_Core_UserAccess} and * {@link SeedDMS_Core_GroupAccess} objects. Even if the document * has no access list the returned array contains the two elements * 'users' and 'groups' which are than empty. The methode returns false * if the function fails. * * @param int $mode access mode (defaults to M_ANY) * @param int|string $op operation (defaults to O_EQ) * @return bool|array */ function getAccessList($mode = M_ANY, $op = O_EQ) { /* {{{ */ $db = $this->_dms->getDB(); if ($this->inheritsAccess()) { $res = $this->getFolder(); if (!$res) return false; return $this->_folder->getAccessList($mode, $op); } if (!isset($this->_accessList[$mode])) { if ($op!=O_GTEQ && $op!=O_LTEQ && $op!=O_EQ) { return false; } $modeStr = ""; if ($mode!=M_ANY) { $modeStr = " AND mode".$op.(int)$mode; } $queryStr = "SELECT * FROM `tblACLs` WHERE `targetType` = ".T_DOCUMENT. " AND target = " . $this->_id . $modeStr . " ORDER BY `targetType`"; $resArr = $db->getResultArray($queryStr); if (is_bool($resArr) && !$resArr) return false; $this->_accessList[$mode] = array("groups" => array(), "users" => array()); foreach ($resArr as $row) { if ($row["userID"] != -1) array_push($this->_accessList[$mode]["users"], new SeedDMS_Core_UserAccess($this->_dms->getUser($row["userID"]), $row["mode"])); else //if ($row["groupID"] != -1) array_push($this->_accessList[$mode]["groups"], new SeedDMS_Core_GroupAccess($this->_dms->getGroup($row["groupID"]), $row["mode"])); } } return $this->_accessList[$mode]; } /* }}} */ /** * Add access right to folder * This function may change in the future. Instead of passing the a flag * and a user/group id a user or group object will be expected. * * @param integer $mode access mode * @param integer $userOrGroupID id of user or group * @param integer $isUser set to 1 if $userOrGroupID is the id of a * user * @return bool */ function addAccess($mode, $userOrGroupID, $isUser) { /* {{{ */ $db = $this->_dms->getDB(); $userOrGroup = ($isUser) ? "`userID`" : "`groupID`"; $queryStr = "INSERT INTO `tblACLs` (`target`, `targetType`, ".$userOrGroup.", `mode`) VALUES (".$this->_id.", ".T_DOCUMENT.", " . (int) $userOrGroupID . ", " .(int) $mode. ")"; if (!$db->getResult($queryStr)) return false; unset($this->_accessList); // Update the notify list, if necessary. if ($mode == M_NONE) { $this->removeNotify($userOrGroupID, $isUser); } return true; } /* }}} */ /** * Change access right of document * This function may change in the future. Instead of passing the a flag * and a user/group id a user or group object will be expected. * * @param integer $newMode access mode * @param integer $userOrGroupID id of user or group * @param integer $isUser set to 1 if $userOrGroupID is the id of a * user * @return bool */ function changeAccess($newMode, $userOrGroupID, $isUser) { /* {{{ */ $db = $this->_dms->getDB(); $userOrGroup = ($isUser) ? "`userID`" : "`groupID`"; $queryStr = "UPDATE `tblACLs` SET `mode` = " . (int) $newMode . " WHERE `targetType` = ".T_DOCUMENT." AND `target` = " . $this->_id . " AND " . $userOrGroup . " = " . (int) $userOrGroupID; if (!$db->getResult($queryStr)) return false; unset($this->_accessList); // Update the notify list, if necessary. if ($newMode == M_NONE) { $this->removeNotify($userOrGroupID, $isUser); } return true; } /* }}} */ /** * Remove access rights for a user or group * * @param integer $userOrGroupID ID of user or group * @param boolean $isUser true if $userOrGroupID is a user id, false if it * is a group id. * @return boolean true on success, otherwise false */ function removeAccess($userOrGroupID, $isUser) { /* {{{ */ $db = $this->_dms->getDB(); $userOrGroup = ($isUser) ? "`userID`" : "`groupID`"; $queryStr = "DELETE FROM `tblACLs` WHERE `targetType` = ".T_DOCUMENT." AND `target` = ".$this->_id." AND ".$userOrGroup." = " . (int) $userOrGroupID; if (!$db->getResult($queryStr)) return false; unset($this->_accessList); // Update the notify list, if the user looses access rights. $mode = ($isUser ? $this->getAccessMode($this->_dms->getUser($userOrGroupID)) : $this->getGroupAccessMode($this->_dms->getGroup($userOrGroupID))); if ($mode == M_NONE) { $this->removeNotify($userOrGroupID, $isUser); } return true; } /* }}} */ /** * Returns the greatest access privilege for a given user * * This function returns the access mode for a given user. An administrator * and the owner of the folder has unrestricted access. A guest user has * read only access or no access if access rights are further limited * by access control lists. All other users have access rights according * to the access control lists or the default access. This function will * recursive check for access rights of parent folders if access rights * are inherited. * * The function searches the access control list for entries of * user $user. If it finds more than one entry it will return the * one allowing the greatest privileges, but user rights will always * precede group rights. If there is no entry in the * access control list, it will return the default access mode. * The function takes inherited access rights into account. * For a list of possible access rights see @file inc.AccessUtils.php * * Having access on a document does not necessarily mean the document * content is accessible too. Accessing the content is checked by * {@link SeedDMS_Core_DocumentContent::getAccessMode()} which calls * a callback function defined by the application. If the callback * function is not set, access on the content is always granted. * * Before checking the access in the method itself a callback 'onCheckAccessDocument' * is called. If it returns a value > 0, then this will be returned by this * method without any further checks. The optional paramater $context * will be passed as a third parameter to the callback. It contains * the operation for which the access mode is retrieved. It is for example * set to 'removeDocument' if the access mode is used to check for sufficient * permission on deleting a document. * * @param $user object instance of class SeedDMS_Core_User * @param string $context context in which the access mode is requested * @return integer access mode */ function getAccessMode($user, $context='') { /* {{{ */ if(!$user) return M_NONE; /* Check if 'onCheckAccessDocument' callback is set */ if(isset($this->_dms->callbacks['onCheckAccessDocument'])) { foreach($this->_dms->callbacks['onCheckAccessDocument'] as $callback) { if(($ret = call_user_func($callback[0], $callback[1], $this, $user, $context)) > 0) { return $ret; } } } /* Administrators have unrestricted access */ if ($user->isAdmin()) return M_ALL; /* The owner of the document has unrestricted access */ if ($user->getID() == $this->_ownerID) return M_ALL; /* Check ACLs */ $accessList = $this->getAccessList(); if (!$accessList) return false; /** @var SeedDMS_Core_UserAccess $userAccess */ foreach ($accessList["users"] as $userAccess) { if ($userAccess->getUserID() == $user->getID()) { $mode = $userAccess->getMode(); if ($user->isGuest()) { if ($mode >= M_READ) $mode = M_READ; } return $mode; } } /* Get the highest right defined by a group */ if($accessList['groups']) { $mode = 0; /** @var SeedDMS_Core_GroupAccess $groupAccess */ foreach ($accessList["groups"] as $groupAccess) { if ($user->isMemberOfGroup($groupAccess->getGroup())) { if ($groupAccess->getMode() > $mode) $mode = $groupAccess->getMode(); } } if($mode) { if ($user->isGuest()) { if ($mode >= M_READ) $mode = M_READ; } return $mode; } } $mode = $this->getDefaultAccess(); if ($user->isGuest()) { if ($mode >= M_READ) $mode = M_READ; } return $mode; } /* }}} */ /** * Returns the greatest access privilege for a given group * * This function searches the access control list for entries of * group $group. If it finds more than one entry it will return the * one allowing the greatest privileges. If there is no entry in the * access control list, it will return the default access mode. * The function takes inherited access rights into account. * For a list of possible access rights see @file inc.AccessUtils.php * * @param SeedDMS_Core_Group $group object instance of class SeedDMS_Core_Group * @return integer access mode */ function getGroupAccessMode($group) { /* {{{ */ $highestPrivileged = M_NONE; //ACLs durchforsten $foundInACL = false; $accessList = $this->getAccessList(); if (!$accessList) return false; /** @var SeedDMS_Core_GroupAccess $groupAccess */ foreach ($accessList["groups"] as $groupAccess) { if ($groupAccess->getGroupID() == $group->getID()) { $foundInACL = true; if ($groupAccess->getMode() > $highestPrivileged) $highestPrivileged = $groupAccess->getMode(); if ($highestPrivileged == M_ALL) // max access right -> skip the rest return $highestPrivileged; } } if ($foundInACL) return $highestPrivileged; //Standard-Berechtigung verwenden return $this->getDefaultAccess(); } /* }}} */ /** * Returns a list of all notifications * * The returned list has two elements called 'users' and 'groups'. Each one * is an array itself countaining objects of class SeedDMS_Core_User and * SeedDMS_Core_Group. * * @param integer $type type of notification (not yet used) * @param bool $incdisabled set to true if disabled user shall be included * @return array|bool */ function getNotifyList($type=0, $incdisabled=false) { /* {{{ */ if (empty($this->_notifyList)) { $db = $this->_dms->getDB(); $queryStr ="SELECT * FROM `tblNotify` WHERE `targetType` = " . T_DOCUMENT . " AND `target` = " . $this->_id; $resArr = $db->getResultArray($queryStr); if (is_bool($resArr) && $resArr == false) return false; $this->_notifyList = array("groups" => array(), "users" => array()); foreach ($resArr as $row) { if ($row["userID"] != -1) { $u = $this->_dms->getUser($row["userID"]); if($u && (!$u->isDisabled() || $incdisabled)) array_push($this->_notifyList["users"], $u); } else { //if ($row["groupID"] != -1) $g = $this->_dms->getGroup($row["groupID"]); if($g) array_push($this->_notifyList["groups"], $g); } } } return $this->_notifyList; } /* }}} */ /** * Make sure only users/groups with read access are in the notify list * */ function cleanNotifyList() { /* {{{ */ // If any of the notification subscribers no longer have read access, // remove their subscription. if (empty($this->_notifyList)) $this->getNotifyList(); /* Make a copy of both notifier lists because removeNotify will empty * $this->_notifyList and the second foreach will not work anymore. */ /** @var SeedDMS_Core_User[] $nusers */ $nusers = $this->_notifyList["users"]; /** @var SeedDMS_Core_Group[] $ngroups */ $ngroups = $this->_notifyList["groups"]; foreach ($nusers as $u) { if ($this->getAccessMode($u) < M_READ) { $this->removeNotify($u->getID(), true); } } foreach ($ngroups as $g) { if ($this->getGroupAccessMode($g) < M_READ) { $this->removeNotify($g->getID(), false); } } } /* }}} */ /** * Add a user/group to the notification list * This function does not check if the currently logged in user * is allowed to add a notification. This must be checked by the calling * application. * * @param $userOrGroupID integer id of user or group to add * @param $isUser integer 1 if $userOrGroupID is a user, * 0 if $userOrGroupID is a group * @return integer 0: Update successful. * -1: Invalid User/Group ID. * -2: Target User / Group does not have read access. * -3: User is already subscribed. * -4: Database / internal error. */ function addNotify($userOrGroupID, $isUser) { /* {{{ */ $db = $this->_dms->getDB(); $userOrGroup = ($isUser ? "`userID`" : "`groupID`"); /* Verify that user / group exists. */ $obj = ($isUser ? $this->_dms->getUser($userOrGroupID) : $this->_dms->getGroup($userOrGroupID)); if (!is_object($obj)) { return -1; } /* Verify that the requesting user has permission to add the target to * the notification system. */ /* * The calling application should enforce the policy on who is allowed * to add someone to the notification system. If is shall remain here * the currently logged in user should be passed to this function * GLOBAL $user; if ($user->isGuest()) { return -2; } if (!$user->isAdmin()) { if ($isUser) { if ($user->getID() != $obj->getID()) { return -2; } } else { if (!$obj->isMember($user)) { return -2; } } } */ /* Verify that target user / group has read access to the document. */ if ($isUser) { // Users are straightforward to check. if ($this->getAccessMode($obj) < M_READ) { return -2; } } else { // Groups are a little more complex. if ($this->getDefaultAccess() >= M_READ) { // If the default access is at least READ-ONLY, then just make sure // that the current group has not been explicitly excluded. $acl = $this->getAccessList(M_NONE, O_EQ); $found = false; /** @var SeedDMS_Core_GroupAccess $group */ foreach ($acl["groups"] as $group) { if ($group->getGroupID() == $userOrGroupID) { $found = true; break; } } if ($found) { return -2; } } else { // The default access is restricted. Make sure that the group has // been explicitly allocated access to the document. $acl = $this->getAccessList(M_READ, O_GTEQ); if (is_bool($acl)) { return -4; } $found = false; /** @var SeedDMS_Core_GroupAccess $group */ foreach ($acl["groups"] as $group) { if ($group->getGroupID() == $userOrGroupID) { $found = true; break; } } if (!$found) { return -2; } } } /* Check to see if user/group is already on the list. */ $queryStr = "SELECT * FROM `tblNotify` WHERE `tblNotify`.`target` = '".$this->_id."' ". "AND `tblNotify`.`targetType` = '".T_DOCUMENT."' ". "AND `tblNotify`.".$userOrGroup." = '".(int) $userOrGroupID."'"; $resArr = $db->getResultArray($queryStr); if (is_bool($resArr)) { return -4; } if (count($resArr)>0) { return -3; } $queryStr = "INSERT INTO `tblNotify` (`target`, `targetType`, " . $userOrGroup . ") VALUES (" . $this->_id . ", " . T_DOCUMENT . ", " . (int) $userOrGroupID . ")"; if (!$db->getResult($queryStr)) return -4; unset($this->_notifyList); return 0; } /* }}} */ /** * Remove a user or group from the notification list * This function does not check if the currently logged in user * is allowed to remove a notification. This must be checked by the calling * application. * * @param integer $userOrGroupID id of user or group * @param boolean $isUser boolean true if a user is passed in $userOrGroupID, false * if a group is passed in $userOrGroupID * @param integer $type type of notification (0 will delete all) Not used yet! * @return integer 0 if operation was succesful * -1 if the userid/groupid is invalid * -3 if the user/group is already subscribed * -4 in case of an internal database error */ function removeNotify($userOrGroupID, $isUser, $type=0) { /* {{{ */ $db = $this->_dms->getDB(); /* Verify that user / group exists. */ /** @var SeedDMS_Core_Group|SeedDMS_Core_User $obj */ $obj = ($isUser ? $this->_dms->getUser($userOrGroupID) : $this->_dms->getGroup($userOrGroupID)); if (!is_object($obj)) { return -1; } $userOrGroup = ($isUser) ? "`userID`" : "`groupID`"; /* Verify that the requesting user has permission to add the target to * the notification system. */ /* * The calling application should enforce the policy on who is allowed * to add someone to the notification system. If is shall remain here * the currently logged in user should be passed to this function * GLOBAL $user; if ($user->isGuest()) { return -2; } if (!$user->isAdmin()) { if ($isUser) { if ($user->getID() != $obj->getID()) { return -2; } } else { if (!$obj->isMember($user)) { return -2; } } } */ /* Check to see if the target is in the database. */ $queryStr = "SELECT * FROM `tblNotify` WHERE `tblNotify`.`target` = '".$this->_id."' ". "AND `tblNotify`.`targetType` = '".T_DOCUMENT."' ". "AND `tblNotify`.".$userOrGroup." = '".(int) $userOrGroupID."'"; $resArr = $db->getResultArray($queryStr); if (is_bool($resArr)) { return -4; } if (count($resArr)==0) { return -3; } $queryStr = "DELETE FROM `tblNotify` WHERE `target` = " . $this->_id . " AND `targetType` = " . T_DOCUMENT . " AND " . $userOrGroup . " = " . (int) $userOrGroupID; /* If type is given then delete only those notifications */ if($type) $queryStr .= " AND `type` = ".(int) $type; if (!$db->getResult($queryStr)) return -4; unset($this->_notifyList); return 0; } /* }}} */ /** * Add content to a document * * Each document may have any number of content elements attached to it. * Each content element has a version number. Newer versions (greater * version number) replace older versions. * * @param string $comment comment * @param object $user user who shall be the owner of this content * @param string $tmpFile file containing the actuall content * @param string $orgFileName original file name * @param string $fileType * @param string $mimeType MimeType of the content * @param array $reviewers list of reviewers * @param array $approvers list of approvers * @param integer $version version number of content or 0 if next higher version shall be used. * @param array $attributes list of version attributes. The element key * must be the id of the attribute definition. * @param object $workflow * @return bool|SeedDMS_Core_AddContentResultSet */ function addContent($comment, $user, $tmpFile, $orgFileName, $fileType, $mimeType, $reviewers=array(), $approvers=array(), $version=0, $attributes=array(), $workflow=null) { /* {{{ */ $db = $this->_dms->getDB(); // the doc path is id/version.filetype $dir = $this->getDir(); /* The version field in table tblDocumentContent used to be auto * increment but that requires the field to be primary as well if * innodb is used. That's why the version is now determined here. */ if ((int)$version<1) { $queryStr = "SELECT MAX(`version`) as m from `tblDocumentContent` where `document` = ".$this->_id; $resArr = $db->getResultArray($queryStr); if (is_bool($resArr) && !$resArr) return false; $version = $resArr[0]['m']+1; } if($fileType == '.') $fileType = ''; $filesize = SeedDMS_Core_File::fileSize($tmpFile); $checksum = SeedDMS_Core_File::checksum($tmpFile); $db->startTransaction(); $queryStr = "INSERT INTO `tblDocumentContent` (`document`, `version`, `comment`, `date`, `createdBy`, `dir`, `orgFileName`, `fileType`, `mimeType`, `fileSize`, `checksum`) VALUES ". "(".$this->_id.", ".(int)$version.",".$db->qstr($comment).", ".$db->getCurrentTimestamp().", ".$user->getID().", ".$db->qstr($dir).", ".$db->qstr($orgFileName).", ".$db->qstr($fileType).", ".$db->qstr($mimeType).", ".$filesize.", ".$db->qstr($checksum).")"; if (!$db->getResult($queryStr)) { $db->rollbackTransaction(); return false; } $contentID = $db->getInsertID('tblDocumentContent'); // copy file if (!SeedDMS_Core_File::makeDir($this->_dms->contentDir . $dir)) { $db->rollbackTransaction(); return false; } if($this->_dms->forceRename) $err = SeedDMS_Core_File::renameFile($tmpFile, $this->_dms->contentDir . $dir . $version . $fileType); else $err = SeedDMS_Core_File::copyFile($tmpFile, $this->_dms->contentDir . $dir . $version . $fileType); if (!$err) { $db->rollbackTransaction(); return false; } $this->_content = null; $this->_latestContent = null; $content = $this->getLatestContent($contentID); /** @todo: Parameter not defined in Funktion */ $docResultSet = new SeedDMS_Core_AddContentResultSet($content); $docResultSet->setDMS($this->_dms); if($attributes) { foreach($attributes as $attrdefid=>$attribute) { /* $attribute can be a string or an array */ if($attribute) if(!$content->setAttributeValue($this->_dms->getAttributeDefinition($attrdefid), $attribute)) { $this->_removeContent($content); $db->rollbackTransaction(); return false; } } } $queryStr = "INSERT INTO `tblDocumentStatus` (`documentID`, `version`) ". "VALUES (". $this->_id .", ". (int) $version .")"; if (!$db->getResult($queryStr)) { $this->_removeContent($content); $db->rollbackTransaction(); return false; } $statusID = $db->getInsertID('tblDocumentStatus', 'statusID'); if($workflow) $content->setWorkflow($workflow, $user); // Add reviewers into the database. Reviewers must review the document // and submit comments, if appropriate. Reviewers can also recommend that // a document be rejected. $pendingReview=false; /** @noinspection PhpUnusedLocalVariableInspection */ foreach (array("i", "g") as $i){ if (isset($reviewers[$i])) { foreach ($reviewers[$i] as $reviewerID) { $reviewer=($i=="i" ?$this->_dms->getUser($reviewerID) : $this->_dms->getGroup($reviewerID)); $res = ($i=="i" ? $docResultSet->getContent()->addIndReviewer($reviewer, $user, true) : $docResultSet->getContent()->addGrpReviewer($reviewer, $user, true)); $docResultSet->addReviewer($reviewer, $i, $res); // If no error is returned, or if the error is just due to email // failure, mark the state as "pending review". if ($res==0 || $res=-3 || $res=-4) { $pendingReview=true; } } } } // Add approvers to the database. Approvers must also review the document // and make a recommendation on its release as an approved version. $pendingApproval=false; /** @noinspection PhpUnusedLocalVariableInspection */ foreach (array("i", "g") as $i){ if (isset($approvers[$i])) { foreach ($approvers[$i] as $approverID) { $approver=($i=="i" ? $this->_dms->getUser($approverID) : $this->_dms->getGroup($approverID)); $res=($i=="i" ? $docResultSet->getContent()->addIndApprover($approver, $user, true) : $docResultSet->getContent()->addGrpApprover($approver, $user, !$pendingReview)); $docResultSet->addApprover($approver, $i, $res); if ($res==0 || $res=-3 || $res=-4) { $pendingApproval=true; } } } } // If there are no reviewers or approvers, the document is automatically // promoted to the released state. if ($pendingReview) { $status = S_DRAFT_REV; $comment = ""; } elseif ($pendingApproval) { $status = S_DRAFT_APP; $comment = ""; } elseif($workflow) { $status = S_IN_WORKFLOW; $comment = ", workflow: ".$workflow->getName(); } else { $status = S_RELEASED; $comment = ""; } $queryStr = "INSERT INTO `tblDocumentStatusLog` (`statusID`, `status`, `comment`, `date`, `userID`) ". "VALUES ('". $statusID ."', '". $status."', 'New document content submitted". $comment ."', ".$db->getCurrentDatetime().", '". $user->getID() ."')"; if (!$db->getResult($queryStr)) { $db->rollbackTransaction(); return false; } /** @noinspection PhpMethodParametersCountMismatchInspection */ $docResultSet->setStatus($status); $db->commitTransaction(); return $docResultSet; } /* }}} */ /** * Replace a version of a document * * Each document may have any number of content elements attached to it. * This function replaces the file content of a given version. * Using this function is highly discourage, because it undermines the * idea of keeping all versions of a document as originally saved. * Content will only be replaced if the mimetype, filetype, user and * original filename are identical to the version being updated. * * This function was introduced for the webdav server because any saving * of a document created a new version. * * @param object $user user who shall be the owner of this content * @param string $tmpFile file containing the actuall content * @param string $orgFileName original file name * @param string $fileType * @param string $mimeType MimeType of the content * @param integer $version version number of content or 0 if next higher version shall be used. * @return bool/array false in case of an error or a result set */ function replaceContent($version, $user, $tmpFile, $orgFileName, $fileType, $mimeType) { /* {{{ */ $db = $this->_dms->getDB(); // the doc path is id/version.filetype $dir = $this->getDir(); /* If $version < 1 than replace the content of the latest version. */ if ((int) $version<1) { $queryStr = "SELECT MAX(`version`) as m from `tblDocumentContent` where `document` = ".$this->_id; $resArr = $db->getResultArray($queryStr); if (is_bool($resArr) && !$resArr) return false; $version = $resArr[0]['m']; } $content = $this->getContentByVersion($version); if(!$content) return false; if($fileType == '.') $fileType = ''; /* Check if $user, $orgFileName, $fileType and $mimeType are the same */ if($user->getID() != $content->getUser()->getID()) { return false; } if($orgFileName != $content->getOriginalFileName()) { return false; } if($fileType != $content->getFileType()) { return false; } if($mimeType != $content->getMimeType()) { return false; } $filesize = SeedDMS_Core_File::fileSize($tmpFile); $checksum = SeedDMS_Core_File::checksum($tmpFile); $db->startTransaction(); $queryStr = "UPDATE `tblDocumentContent` set `date`=".$db->getCurrentTimestamp().", `fileSize`=".$filesize.", `checksum`=".$db->qstr($checksum)." WHERE `id`=".$content->getID(); if (!$db->getResult($queryStr)) { $db->rollbackTransaction(); return false; } // copy file if (!SeedDMS_Core_File::copyFile($tmpFile, $this->_dms->contentDir . $dir . $version . $fileType)) { $db->rollbackTransaction(); return false; } $this->_content = null; $this->_latestContent = null; $db->commitTransaction(); return true; } /* }}} */ /** * Return all content elements of a document * * This functions returns an array of content elements ordered by version. * Version which are not accessible because of its status, will be filtered * out. Access rights based on the document status are calculated for the * currently logged in user. * * @return bool|SeedDMS_Core_DocumentContent[] */ function getContent() { /* {{{ */ $db = $this->_dms->getDB(); if (!isset($this->_content)) { $queryStr = "SELECT * FROM `tblDocumentContent` WHERE `document` = ".$this->_id." ORDER BY `version`"; $resArr = $db->getResultArray($queryStr); if (is_bool($resArr) && !$resArr) return false; $this->_content = array(); $classname = $this->_dms->getClassname('documentcontent'); $user = $this->_dms->getLoggedInUser(); foreach ($resArr as $row) { /** @var SeedDMS_Core_DocumentContent $content */ $content = new $classname($row["id"], $this, $row["version"], $row["comment"], $row["date"], $row["createdBy"], $row["dir"], $row["orgFileName"], $row["fileType"], $row["mimeType"], $row['fileSize'], $row['checksum']); /* TODO: Better use content id as key in $this->_content. This * would allow to remove a single content object in removeContent(). * Currently removeContent() must clear $this->_content completely */ if($user) { if($content->getAccessMode($user) >= M_READ) array_push($this->_content, $content); } else { array_push($this->_content, $content); } } } return $this->_content; } /* }}} */ /** * Return the content element of a document with a given version number * * This function will check if the version is accessible and return false * if not. Access rights based on the document status are calculated for the * currently logged in user. * * @param integer $version version number of content element * @return SeedDMS_Core_DocumentContent|boolean object of class {@link SeedDMS_Core_DocumentContent} * or false */ function getContentByVersion($version) { /* {{{ */ if (!is_numeric($version)) return false; if (isset($this->_content)) { foreach ($this->_content as $revision) { if ($revision->getVersion() == $version) return $revision; } return false; } $db = $this->_dms->getDB(); $queryStr = "SELECT * FROM `tblDocumentContent` WHERE `document` = ".$this->_id." AND `version` = " . (int) $version; $resArr = $db->getResultArray($queryStr); if (is_bool($resArr) && !$resArr) return false; if (count($resArr) != 1) return false; $resArr = $resArr[0]; $classname = $this->_dms->getClassname('documentcontent'); /** @var SeedDMS_Core_DocumentContent $content */ if($content = new $classname($resArr["id"], $this, $resArr["version"], $resArr["comment"], $resArr["date"], $resArr["createdBy"], $resArr["dir"], $resArr["orgFileName"], $resArr["fileType"], $resArr["mimeType"], $resArr['fileSize'], $resArr['checksum'])) { $user = $this->_dms->getLoggedInUser(); /* A user with write access on the document may always see the version */ if($user && $content->getAccessMode($user) == M_NONE) return false; else return $content; } else { return false; } } /* }}} */ /** * @return bool|null|SeedDMS_Core_DocumentContent */ function __getLatestContent() { /* {{{ */ if (!$this->_latestContent) { $db = $this->_dms->getDB(); $queryStr = "SELECT * FROM `tblDocumentContent` WHERE `document` = ".$this->_id." ORDER BY `version` DESC LIMIT 1"; $resArr = $db->getResultArray($queryStr); if (is_bool($resArr) && !$resArr) return false; if (count($resArr) != 1) return false; $resArr = $resArr[0]; $classname = $this->_dms->getClassname('documentcontent'); $this->_latestContent = new $classname($resArr["id"], $this, $resArr["version"], $resArr["comment"], $resArr["date"], $resArr["createdBy"], $resArr["dir"], $resArr["orgFileName"], $resArr["fileType"], $resArr["mimeType"], $resArr['fileSize'], $resArr['checksum']); } return $this->_latestContent; } /* }}} */ /** * Get the latest version of document * * This function returns the latest accessible version of a document. * If content access has been restricted by setting * {@link SeedDMS_Core_DMS::noReadForStatus} the function will go * backwards in history until an accessible version is found. If none * is found null will be returned. * Access rights based on the document status are calculated for the * currently logged in user. * * @return bool|SeedDMS_Core_DocumentContent object of class {@link SeedDMS_Core_DocumentContent} */ function getLatestContent() { /* {{{ */ if (!$this->_latestContent) { $db = $this->_dms->getDB(); $queryStr = "SELECT * FROM `tblDocumentContent` WHERE `document` = ".$this->_id." ORDER BY `version` DESC"; $resArr = $db->getResultArray($queryStr); if (is_bool($resArr) && !$resArr) return false; $classname = $this->_dms->getClassname('documentcontent'); $user = $this->_dms->getLoggedInUser(); foreach ($resArr as $row) { if (!$this->_latestContent) { /** @var SeedDMS_Core_DocumentContent $content */ $content = new $classname($row["id"], $this, $row["version"], $row["comment"], $row["date"], $row["createdBy"], $row["dir"], $row["orgFileName"], $row["fileType"], $row["mimeType"], $row['fileSize'], $row['checksum']); if($user) { /* If the user may even write the document, then also allow to see all content. * This is needed because the user could upload a new version */ if($content->getAccessMode($user) >= M_READ) { $this->_latestContent = $content; } } else { $this->_latestContent = $content; } } } } return $this->_latestContent; } /* }}} */ /** * Remove version of document * * @param SeedDMS_Core_DocumentContent $version version number of content * @return boolean true if successful, otherwise false */ private function _removeContent($version) { /* {{{ */ $db = $this->_dms->getDB(); if (file_exists( $this->_dms->contentDir.$version->getPath() )) if (!SeedDMS_Core_File::removeFile( $this->_dms->contentDir.$version->getPath() )) return false; $db->startTransaction(); $status = $version->getStatus(); $stID = $status["statusID"]; $queryStr = "DELETE FROM `tblDocumentContent` WHERE `document` = " . $this->getID() . " AND `version` = " . $version->getVersion(); if (!$db->getResult($queryStr)) { $db->rollbackTransaction(); return false; } $queryStr = "DELETE FROM `tblDocumentContentAttributes` WHERE `content` = " . $version->getId(); if (!$db->getResult($queryStr)) { $db->rollbackTransaction(); return false; } $queryStr = "DELETE FROM `tblDocumentStatusLog` WHERE `statusID` = '".$stID."'"; if (!$db->getResult($queryStr)) { $db->rollbackTransaction(); return false; } $queryStr = "DELETE FROM `tblDocumentStatus` WHERE `documentID` = '". $this->getID() ."' AND `version` = '" . $version->getVersion()."'"; if (!$db->getResult($queryStr)) { $db->rollbackTransaction(); return false; } $status = $version->getReviewStatus(); $stList = ""; foreach ($status as $st) { $stList .= (strlen($stList)==0 ? "" : ", "). "'".$st["reviewID"]."'"; $queryStr = "SELECT * FROM `tblDocumentReviewLog` WHERE `reviewID` = " . $st['reviewID']; $resArr = $db->getResultArray($queryStr); if ((is_bool($resArr) && !$resArr)) { $db->rollbackTransaction(); return false; } foreach($resArr as $res) { $file = $this->_dms->contentDir . $this->getDir().'r'.$res['reviewLogID']; if(file_exists($file)) SeedDMS_Core_File::removeFile($file); } } if (strlen($stList)>0) { $queryStr = "DELETE FROM `tblDocumentReviewLog` WHERE `tblDocumentReviewLog`.`reviewID` IN (".$stList.")"; if (!$db->getResult($queryStr)) { $db->rollbackTransaction(); return false; } } $queryStr = "DELETE FROM `tblDocumentReviewers` WHERE `documentID` = '". $this->getID() ."' AND `version` = '" . $version->getVersion()."'"; if (!$db->getResult($queryStr)) { $db->rollbackTransaction(); return false; } $status = $version->getApprovalStatus(); $stList = ""; foreach ($status as $st) { $stList .= (strlen($stList)==0 ? "" : ", "). "'".$st["approveID"]."'"; $queryStr = "SELECT * FROM `tblDocumentApproveLog` WHERE `approveID` = " . $st['approveID']; $resArr = $db->getResultArray($queryStr); if ((is_bool($resArr) && !$resArr)) { $db->rollbackTransaction(); return false; } foreach($resArr as $res) { $file = $this->_dms->contentDir . $this->getDir().'a'.$res['approveLogID']; if(file_exists($file)) SeedDMS_Core_File::removeFile($file); } } if (strlen($stList)>0) { $queryStr = "DELETE FROM `tblDocumentApproveLog` WHERE `tblDocumentApproveLog`.`approveID` IN (".$stList.")"; if (!$db->getResult($queryStr)) { $db->rollbackTransaction(); return false; } } $queryStr = "DELETE FROM `tblDocumentApprovers` WHERE `documentID` = '". $this->getID() ."' AND `version` = '" . $version->getVersion()."'"; if (!$db->getResult($queryStr)) { $db->rollbackTransaction(); return false; } $queryStr = "DELETE FROM `tblWorkflowDocumentContent` WHERE `document` = '". $this->getID() ."' AND `version` = '" . $version->getVersion()."'"; if (!$db->getResult($queryStr)) { $db->rollbackTransaction(); return false; } $queryStr = "DELETE FROM `tblWorkflowLog` WHERE `document` = '". $this->getID() ."' AND `version` = '" . $version->getVersion()."'"; if (!$db->getResult($queryStr)) { $db->rollbackTransaction(); return false; } // remove document files attached to version $res = $this->getDocumentFiles($version->getVersion()); if (is_bool($res) && !$res) { $db->rollbackTransaction(); return false; } foreach ($res as $documentfile) if(!$this->removeDocumentFile($documentfile->getId())) { $db->rollbackTransaction(); return false; } $db->commitTransaction(); return true; } /* }}} */ /** * Call callback onPreRemoveDocument before deleting content * * @param SeedDMS_Core_DocumentContent $version version number of content * @return bool|mixed */ function removeContent($version) { /* {{{ */ $this->_dms->lasterror = ''; /* Check if 'onPreRemoveDocument' callback is set */ if(isset($this->_dms->callbacks['onPreRemoveContent'])) { foreach($this->_dms->callbacks['onPreRemoveContent'] as $callback) { $ret = call_user_func($callback[0], $callback[1], $this, $version); if(is_bool($ret)) return $ret; } } if(false === ($ret = self::_removeContent($version))) { return false; } /* Invalidate the content list and the latest content of this document, * otherwise getContent() and getLatestContent() * will still return the content just deleted. */ $this->_latestContent = null; $this->_content = null; /* Check if 'onPostRemoveDocument' callback is set */ if(isset($this->_dms->callbacks['onPostRemoveContent'])) { foreach($this->_dms->callbacks['onPostRemoveContent'] as $callback) { if(!call_user_func($callback[0], $callback[1], $version)) { } } } return $ret; } /* }}} */ /** * Return a certain document link * * @param integer $linkID id of link * @return SeedDMS_Core_DocumentLink|bool of SeedDMS_Core_DocumentLink or false in case of * an error. */ function getDocumentLink($linkID) { /* {{{ */ $db = $this->_dms->getDB(); if (!is_numeric($linkID)) return false; $queryStr = "SELECT * FROM `tblDocumentLinks` WHERE `document` = " . $this->_id ." AND `id` = " . (int) $linkID; $resArr = $db->getResultArray($queryStr); if ((is_bool($resArr) && !$resArr) || count($resArr)==0) return false; $resArr = $resArr[0]; $document = $this->_dms->getDocument($resArr["document"]); $target = $this->_dms->getDocument($resArr["target"]); $link = new SeedDMS_Core_DocumentLink($resArr["id"], $document, $target, $resArr["userID"], $resArr["public"]); $user = $this->_dms->getLoggedInUser(); if($link->getAccessMode($user, $document, $target) >= M_READ) return $link; return null; } /* }}} */ /** * Return all document links * * The list may contain all links to other documents, even those which * may not be visible by certain users, unless you pass appropriate * parameters to filter out public links and those created by * the given user. The application may call * SeedDMS_Core_DMS::filterDocumentLinks() afterwards. * * @param boolean $publiconly return on publically visible links * @param object $user return also private links of this user * @return array list of objects of class SeedDMS_Core_DocumentLink */ function getDocumentLinks($publiconly=false, $user=null) { /* {{{ */ if (!isset($this->_documentLinks)) { $db = $this->_dms->getDB(); $queryStr = "SELECT * FROM `tblDocumentLinks` WHERE `document` = " . $this->_id; $tmp = array(); if($publiconly) $tmp[] = "`public`=1"; if($user) $tmp[] = "`userID`=".$user->getID(); if($tmp) { $queryStr .= " AND (".implode(" OR ", $tmp).")"; } $resArr = $db->getResultArray($queryStr); if (is_bool($resArr) && !$resArr) return false; $this->_documentLinks = array(); $user = $this->_dms->getLoggedInUser(); foreach ($resArr as $row) { $target = $this->_dms->getDocument($row["target"]); $link = new SeedDMS_Core_DocumentLink($row["id"], $this, $target, $row["userID"], $row["public"]); if($link->getAccessMode($user, $this, $target) >= M_READ) array_push($this->_documentLinks, $link); } } return $this->_documentLinks; } /* }}} */ /** * Return all document having a link on this document * * The list contains all documents which have a link to the current * document. The list contains even those documents which * may not be accessible by the user, unless you pass appropriate * parameters to filter out public links and those created by * the given user. * This functions is basically the reverse of * SeedDMS_Core_Document::getDocumentLinks() * * The application may call * SeedDMS_Core_DMS::filterDocumentLinks() afterwards. * * @param boolean $publiconly return on publically visible links * @param object $user return also private links of this user * @return array list of objects of class SeedDMS_Core_DocumentLink */ function getReverseDocumentLinks($publiconly=false, $user=null) { /* {{{ */ $db = $this->_dms->getDB(); $queryStr = "SELECT * FROM `tblDocumentLinks` WHERE `target` = " . $this->_id; $tmp = array(); if($publiconly) $tmp[] = "`public`=1"; if($user) $tmp[] = "`userID`=".$user->getID(); if($tmp) { $queryStr .= " AND (".implode(" OR ", $tmp).")"; } $resArr = $db->getResultArray($queryStr); if (is_bool($resArr) && !$resArr) return false; $links = array(); foreach ($resArr as $row) { $document = $this->_dms->getDocument($row["document"]); $link = new SeedDMS_Core_DocumentLink($row["id"], $document, $this, $row["userID"], $row["public"]); if($link->getAccessMode($user, $document, $this) >= M_READ) array_push($links, $link); } return $links; } /* }}} */ function addDocumentLink($targetID, $userID, $public) { /* {{{ */ $db = $this->_dms->getDB(); $public = ($public) ? "1" : "0"; $queryStr = "INSERT INTO `tblDocumentLinks` (`document`, `target`, `userID`, `public`) VALUES (".$this->_id.", ".(int)$targetID.", ".(int)$userID.", ".(int)$public.")"; if (!$db->getResult($queryStr)) return false; unset($this->_documentLinks); return true; } /* }}} */ function removeDocumentLink($linkID) { /* {{{ */ $db = $this->_dms->getDB(); if (!is_numeric($linkID)) return false; $queryStr = "DELETE FROM `tblDocumentLinks` WHERE `document` = " . $this->_id ." AND `id` = " . (int) $linkID; if (!$db->getResult($queryStr)) return false; unset ($this->_documentLinks); return true; } /* }}} */ /** * Get attached file by its id * * @return object instance of SeedDMS_Core_DocumentFile, null if file is not * accessible, false in case of an sql error */ function getDocumentFile($ID) { /* {{{ */ $db = $this->_dms->getDB(); if (!is_numeric($ID)) return false; $queryStr = "SELECT * FROM `tblDocumentFiles` WHERE `document` = " . $this->_id ." AND `id` = " . (int) $ID; $resArr = $db->getResultArray($queryStr); if ((is_bool($resArr) && !$resArr) || count($resArr)==0) return false; $resArr = $resArr[0]; $file = new SeedDMS_Core_DocumentFile($resArr["id"], $this, $resArr["userID"], $resArr["comment"], $resArr["date"], $resArr["dir"], $resArr["fileType"], $resArr["mimeType"], $resArr["orgFileName"], $resArr["name"],$resArr["version"],$resArr["public"]); $user = $this->_dms->getLoggedInUser(); if($file->getAccessMode($user) >= M_READ) return $file; return null; } /* }}} */ /** * Get list of files attached to document * * @return array list of files, false in case of an sql error */ function getDocumentFiles($version=0) { /* {{{ */ if (!isset($this->_documentFiles)) { $db = $this->_dms->getDB(); $queryStr = "SELECT * FROM `tblDocumentFiles` WHERE `document` = " . $this->_id; if($version) { $queryStr .= " AND (`version`=0 OR `version`=".(int) $version.")"; } $queryStr .= " ORDER BY "; if($version) { $queryStr .= "`version` DESC,"; } $queryStr .= "`date` DESC"; $resArr = $db->getResultArray($queryStr); if (is_bool($resArr) && !$resArr) return false; $this->_documentFiles = array(); $user = $this->_dms->getLoggedInUser(); foreach ($resArr as $row) { $file = new SeedDMS_Core_DocumentFile($row["id"], $this, $row["userID"], $row["comment"], $row["date"], $row["dir"], $row["fileType"], $row["mimeType"], $row["orgFileName"], $row["name"], $row["version"], $row["public"]); if($file->getAccessMode($user) >= M_READ) array_push($this->_documentFiles, $file); } } return $this->_documentFiles; } /* }}} */ function addDocumentFile($name, $comment, $user, $tmpFile, $orgFileName,$fileType, $mimeType,$version=0,$public=1) { /* {{{ */ $db = $this->_dms->getDB(); $dir = $this->getDir(); $db->startTransaction(); $queryStr = "INSERT INTO `tblDocumentFiles` (`comment`, `date`, `dir`, `document`, `fileType`, `mimeType`, `orgFileName`, `userID`, `name`, `version`, `public`) VALUES ". "(".$db->qstr($comment).", ".$db->getCurrentTimestamp().", ".$db->qstr($dir).", ".$this->_id.", ".$db->qstr($fileType).", ".$db->qstr($mimeType).", ".$db->qstr($orgFileName).",".$user->getID().",".$db->qstr($name).", ".((int) $version).", ".($public ? 1 : 0).")"; if (!$db->getResult($queryStr)) { $db->rollbackTransaction(); return false; } $id = $db->getInsertID('tblDocumentFiles'); $file = $this->getDocumentFile($id); if (is_bool($file) && !$file) { $db->rollbackTransaction(); return false; } // copy file if (!SeedDMS_Core_File::makeDir($this->_dms->contentDir . $dir)) return false; if($this->_dms->forceRename) $err = SeedDMS_Core_File::renameFile($tmpFile, $this->_dms->contentDir . $file->getPath()); else $err = SeedDMS_Core_File::copyFile($tmpFile, $this->_dms->contentDir . $file->getPath()); if (!$err) { $db->rollbackTransaction(); return false; } $db->commitTransaction(); return $file; } /* }}} */ function removeDocumentFile($ID) { /* {{{ */ $db = $this->_dms->getDB(); if (!is_numeric($ID)) return false; $file = $this->getDocumentFile($ID); if (is_bool($file) && !$file) return false; if (file_exists( $this->_dms->contentDir . $file->getPath() )){ if (!SeedDMS_Core_File::removeFile( $this->_dms->contentDir . $file->getPath() )) return false; } $name=$file->getName(); $comment=$file->getcomment(); $queryStr = "DELETE FROM `tblDocumentFiles` WHERE `document` = " . $this->getID() . " AND `id` = " . (int) $ID; if (!$db->getResult($queryStr)) return false; unset ($this->_documentFiles); return true; } /* }}} */ /** * Remove a document completly * * This methods calls the callback 'onPreRemoveDocument' before removing * the document. The current document will be passed as the second * parameter to the callback function. After successful deletion the * 'onPostRemoveDocument' callback will be used. The current document id * will be passed as the second parameter. If onPreRemoveDocument fails * the whole function will fail and the document will not be deleted. * The return value of 'onPostRemoveDocument' will be disregarded. * * @return boolean true on success, otherwise false */ function remove() { /* {{{ */ $db = $this->_dms->getDB(); $this->_dms->lasterror = ''; /* Check if 'onPreRemoveDocument' callback is set */ if(isset($this->_dms->callbacks['onPreRemoveDocument'])) { foreach($this->_dms->callbacks['onPreRemoveDocument'] as $callback) { $ret = call_user_func($callback[0], $callback[1], $this); if(is_bool($ret)) return $ret; } } $res = $this->getContent(); if (is_bool($res) && !$res) return false; $db->startTransaction(); // remove content of document foreach ($this->_content as $version) { if (!$this->_removeContent($version)) { $db->rollbackTransaction(); return false; } } // remove document file $res = $this->getDocumentFiles(); if (is_bool($res) && !$res) { $db->rollbackTransaction(); return false; } foreach ($res as $documentfile) if(!$this->removeDocumentFile($documentfile->getId())) { $db->rollbackTransaction(); return false; } // TODO: versioning file? if (file_exists( $this->_dms->contentDir . $this->getDir() )) if (!SeedDMS_Core_File::removeDir( $this->_dms->contentDir . $this->getDir() )) { $db->rollbackTransaction(); return false; } $queryStr = "DELETE FROM `tblDocuments` WHERE `id` = " . $this->_id; if (!$db->getResult($queryStr)) { $db->rollbackTransaction(); return false; } $queryStr = "DELETE FROM `tblDocumentAttributes` WHERE `document` = " . $this->_id; if (!$db->getResult($queryStr)) { $db->rollbackTransaction(); return false; } $queryStr = "DELETE FROM `tblACLs` WHERE `target` = " . $this->_id . " AND `targetType` = " . T_DOCUMENT; if (!$db->getResult($queryStr)) { $db->rollbackTransaction(); return false; } $queryStr = "DELETE FROM `tblDocumentLinks` WHERE `document` = " . $this->_id . " OR `target` = " . $this->_id; if (!$db->getResult($queryStr)) { $db->rollbackTransaction(); return false; } $queryStr = "DELETE FROM `tblDocumentLocks` WHERE `document` = " . $this->_id; if (!$db->getResult($queryStr)) { $db->rollbackTransaction(); return false; } $queryStr = "DELETE FROM `tblDocumentFiles` WHERE `document` = " . $this->_id; if (!$db->getResult($queryStr)) { $db->rollbackTransaction(); return false; } $queryStr = "DELETE FROM `tblDocumentCategory` WHERE `documentID` = " . $this->_id; if (!$db->getResult($queryStr)) { $db->rollbackTransaction(); return false; } // Delete the notification list. $queryStr = "DELETE FROM `tblNotify` WHERE `target` = " . $this->_id . " AND `targetType` = " . T_DOCUMENT; if (!$db->getResult($queryStr)) { $db->rollbackTransaction(); return false; } $db->commitTransaction(); /* Check if 'onPostRemoveDocument' callback is set */ if(isset($this->_dms->callbacks['onPostRemoveDocument'])) { foreach($this->_dms->callbacks['onPostRemoveDocument'] as $callback) { if(!call_user_func($callback[0], $callback[1], $this->_id)) { } } } return true; } /* }}} */ /** * Get List of users and groups which have read access on the document * The list will not include any guest users, * administrators and the owner of the folder unless $listadmin resp. * $listowner is set to true. * * This function is deprecated. Use * {@see SeedDMS_Core_Document::getReadAccessList()} instead. */ function getApproversList() { /* {{{ */ return $this->getReadAccessList(0, 0, 0); } /* }}} */ /** * Returns a list of groups and users with read access on the document * * @param boolean $listadmin if set to true any admin will be listed too * @param boolean $listowner if set to true the owner will be listed too * @param boolean $listguest if set to true any guest will be listed too * * @return array list of users and groups */ function getReadAccessList($listadmin=0, $listowner=0, $listguest=0) { /* {{{ */ $db = $this->_dms->getDB(); if (!isset($this->_readAccessList)) { $this->_readAccessList = array("groups" => array(), "users" => array()); $userIDs = ""; $groupIDs = ""; $defAccess = $this->getDefaultAccess(); if ($defAccessgetAccessList(M_READ, O_GTEQ); } else { // Get the list of all users and groups that DO NOT have read access // to the document. $tmpList = $this->getAccessList(M_NONE, O_LTEQ); } foreach ($tmpList["groups"] as $groupAccess) { $groupIDs .= (strlen($groupIDs)==0 ? "" : ", ") . $groupAccess->getGroupID(); } foreach ($tmpList["users"] as $userAccess) { $user = $userAccess->getUser(); if (!$listadmin && $user->isAdmin()) continue; if (!$listowner && $user->getID() == $this->_ownerID) continue; if (!$listguest && $user->isGuest()) continue; $userIDs .= (strlen($userIDs)==0 ? "" : ", ") . $userAccess->getUserID(); } // Construct a query against the users table to identify those users // that have read access to this document, either directly through an // ACL entry, by virtue of ownership or by having administrative rights // on the database. $queryStr=""; /* If default access is less then read, $userIDs and $groupIDs contains * a list of user with read access */ if ($defAccess < M_READ) { if (strlen($groupIDs)>0) { $queryStr = "SELECT `tblUsers`.* FROM `tblUsers` ". "LEFT JOIN `tblGroupMembers` ON `tblGroupMembers`.`userID`=`tblUsers`.`id` ". "WHERE `tblGroupMembers`.`groupID` IN (". $groupIDs .") ". "AND `tblUsers`.`role` != ".SeedDMS_Core_User::role_guest." UNION "; } $queryStr .= "SELECT `tblUsers`.* FROM `tblUsers` ". "WHERE (`tblUsers`.`role` != ".SeedDMS_Core_User::role_guest.") ". "AND ((`tblUsers`.`id` = ". $this->_ownerID . ") ". "OR (`tblUsers`.`role` = ".SeedDMS_Core_User::role_admin.")". (strlen($userIDs) == 0 ? "" : " OR (`tblUsers`.`id` IN (". $userIDs ."))"). ") ORDER BY `login`"; } /* If default access is equal or greater then M_READ, $userIDs and * $groupIDs contains a list of user without read access */ else { if (strlen($groupIDs)>0) { $queryStr = "SELECT `tblUsers`.* FROM `tblUsers` ". "LEFT JOIN `tblGroupMembers` ON `tblGroupMembers`.`userID`=`tblUsers`.`id` ". "WHERE `tblGroupMembers`.`groupID` NOT IN (". $groupIDs .")". "AND `tblUsers`.`role` != ".SeedDMS_Core_User::role_guest." ". (strlen($userIDs) == 0 ? "" : " AND (`tblUsers`.`id` NOT IN (". $userIDs ."))")." UNION "; } else { $queryStr .= "SELECT `tblUsers`.* FROM `tblUsers` ". "WHERE `tblUsers`.`role` != ".SeedDMS_Core_User::role_guest." ". (strlen($userIDs) == 0 ? "" : " AND (`tblUsers`.`id` NOT IN (". $userIDs ."))")." UNION "; } $queryStr .= "SELECT `tblUsers`.* FROM `tblUsers` ". "WHERE (`tblUsers`.`id` = ". $this->_ownerID . ") ". "OR (`tblUsers`.`role` = ".SeedDMS_Core_User::role_admin.") ". // "UNION ". // "SELECT `tblUsers`.* FROM `tblUsers` ". // "WHERE `tblUsers`.`role` != ".SeedDMS_Core_User::role_guest." ". // (strlen($userIDs) == 0 ? "" : " AND (`tblUsers`.`id` NOT IN (". $userIDs ."))"). " ORDER BY `login`"; } $resArr = $db->getResultArray($queryStr); if (!is_bool($resArr)) { foreach ($resArr as $row) { $user = $this->_dms->getUser($row['id']); if (!$listadmin && $user->isAdmin()) continue; if (!$listowner && $user->getID() == $this->_ownerID) continue; $this->_readAccessList["users"][] = $user; } } // Assemble the list of groups that have read access to the document. $queryStr=""; if ($defAccess < M_READ) { if (strlen($groupIDs)>0) { $queryStr = "SELECT `tblGroups`.* FROM `tblGroups` ". "WHERE `tblGroups`.`id` IN (". $groupIDs .") ORDER BY `name`"; } } else { if (strlen($groupIDs)>0) { $queryStr = "SELECT `tblGroups`.* FROM `tblGroups` ". "WHERE `tblGroups`.`id` NOT IN (". $groupIDs .") ORDER BY `name`"; } else { $queryStr = "SELECT `tblGroups`.* FROM `tblGroups` ORDER BY `name`"; } } if (strlen($queryStr)>0) { $resArr = $db->getResultArray($queryStr); if (!is_bool($resArr)) { foreach ($resArr as $row) { $group = $this->_dms->getGroup($row["id"]); $this->_readAccessList["groups"][] = $group; } } } } return $this->_readAccessList; } /* }}} */ /** * Get the internally used folderList which stores the ids of folders from * the root folder to the parent folder. * * @return string column separated list of folder ids */ function getFolderList() { /* {{{ */ $db = $this->_dms->getDB(); $queryStr = "SELECT `folderList` FROM `tblDocuments` where id = ".$this->_id; $resArr = $db->getResultArray($queryStr); if (is_bool($resArr) && !$resArr) return false; return $resArr[0]['folderList']; } /* }}} */ /** * Checks the internal data of the document and repairs it. * Currently, this function only repairs an incorrect folderList * * @return boolean true on success, otherwise false */ function repair() { /* {{{ */ $db = $this->_dms->getDB(); $curfolderlist = $this->getFolderList(); // calculate the folderList of the folder $parent = $this->getFolder(); $pathPrefix=""; $path = $parent->getPath(); foreach ($path as $f) { $pathPrefix .= ":".$f->getID(); } if (strlen($pathPrefix)>1) { $pathPrefix .= ":"; } if($curfolderlist != $pathPrefix) { $queryStr = "UPDATE `tblDocuments` SET `folderList`='".$pathPrefix."' WHERE `id` = ". $this->_id; $res = $db->getResult($queryStr); if (!$res) return false; } return true; } /* }}} */ /** * Calculate the disk space including all versions of the document * * This is done by using the internal database field storing the * filesize of a document version. * * @return integer total disk space in Bytes */ function getUsedDiskSpace() { /* {{{ */ $db = $this->_dms->getDB(); $queryStr = "SELECT SUM(`fileSize`) sum FROM `tblDocumentContent` WHERE `document` = " . $this->_id; $resArr = $db->getResultArray($queryStr); if (is_bool($resArr) && $resArr == false) return false; return $resArr[0]['sum']; } /* }}} */ /** * Returns a list of events happend during the life of the document * * This includes the creation of new versions, approval and reviews, etc. * * @return array list of events */ function getTimeline() { /* {{{ */ $db = $this->_dms->getDB(); $timeline = array(); /* No need to add entries for new version because the status log * will generate an entry as well. $queryStr = "SELECT * FROM `tblDocumentContent` WHERE `document` = " . $this->_id; $resArr = $db->getResultArray($queryStr); if (is_bool($resArr) && $resArr == false) return false; foreach ($resArr as $row) { $date = date('Y-m-d H:i:s', $row['date']); $timeline[] = array('date'=>$date, 'msg'=>'Added version '.$row['version'], 'type'=>'add_version', 'version'=>$row['version'], 'document'=>$this, 'params'=>array($row['version'])); } */ $queryStr = "SELECT * FROM `tblDocumentFiles` WHERE `document` = " . $this->_id; $resArr = $db->getResultArray($queryStr); if (is_bool($resArr) && $resArr == false) return false; foreach ($resArr as $row) { $date = date('Y-m-d H:i:s', $row['date']); $timeline[] = array('date'=>$date, 'msg'=>'Added attachment "'.$row['name'].'"', 'document'=>$this, 'type'=>'add_file', 'fileid'=>$row['id']); } $queryStr= "SELECT `tblDocumentStatus`.*, `tblDocumentStatusLog`.`statusLogID`,`tblDocumentStatusLog`.`status`, ". "`tblDocumentStatusLog`.`comment`, `tblDocumentStatusLog`.`date`, ". "`tblDocumentStatusLog`.`userID` ". "FROM `tblDocumentStatus` ". "LEFT JOIN `tblDocumentStatusLog` USING (`statusID`) ". "WHERE `tblDocumentStatus`.`documentID` = '". $this->_id ."' ". "ORDER BY `tblDocumentStatusLog`.`statusLogID` DESC"; $resArr = $db->getResultArray($queryStr); if (is_bool($resArr) && !$resArr) return false; /* The above query will also contain entries where a document status exists * but no status log entry. Those records will have no date and must be * skipped. */ foreach ($resArr as $row) { if($row['date']) { $date = $row['date']; $timeline[] = array('date'=>$date, 'msg'=>'Version '.$row['version'].': Status change to '.$row['status'], 'type'=>'status_change', 'version'=>$row['version'], 'document'=>$this, 'status'=>$row['status'], 'statusid'=>$row['statusID'], 'statuslogid'=>$row['statusLogID']); } } return $timeline; } /* }}} */ /** * Transfers the document to a new user * * This method not just sets a new owner of the document but also * transfers the document links, attachments and locks to the new user. * * @return boolean true if successful, otherwise false */ function transferToUser($newuser) { /* {{{ */ $db = $this->_dms->getDB(); if($newuser->getId() == $this->_ownerID) return true; $db->startTransaction(); $queryStr = "UPDATE `tblDocuments` SET `owner` = ".$newuser->getId()." WHERE `id` = " . $this->_id; if (!$db->getResult($queryStr)) { $db->rollbackTransaction(); return false; } $queryStr = "UPDATE `tblDocumentLocks` SET `userID` = ".$newuser->getId()." WHERE `document` = " . $this->_id . " AND `userID` = ".$this->_ownerID; if (!$db->getResult($queryStr)) { $db->rollbackTransaction(); return false; } $queryStr = "UPDATE `tblDocumentLinks` SET `userID` = ".$newuser->getId()." WHERE `document` = " . $this->_id . " AND `userID` = ".$this->_ownerID; if (!$db->getResult($queryStr)) { $db->rollbackTransaction(); return false; } $queryStr = "UPDATE `tblDocumentFiles` SET `userID` = ".$newuser->getId()." WHERE `document` = " . $this->_id . " AND `userID` = ".$this->_ownerID; if (!$db->getResult($queryStr)) { $db->rollbackTransaction(); return false; } $this->_ownerID = $newuser->getID(); $this->_owner = $newuser; $db->commitTransaction(); return true; } /* }}} */ } /* }}} */ /** * Class to represent content of a document * * Each document has content attached to it, often called a 'version' of the * document. The document content represents a file on the disk with some * meta data stored in the database. A document content has a version number * which is incremented with each replacement of the old content. Old versions * are kept unless they are explicitly deleted by * {@link SeedDMS_Core_Document::removeContent()}. * * @category DMS * @package SeedDMS_Core * @author Markus Westphal, Malcolm Cowe, Matteo Lucarelli, * Uwe Steinmann * @copyright Copyright (C) 2002-2005 Markus Westphal, * 2006-2008 Malcolm Cowe, 2010 Matteo Lucarelli, * 2010 Uwe Steinmann * @version Release: @package_version@ */ class SeedDMS_Core_DocumentContent extends SeedDMS_Core_Object { /* {{{ */ /** * @var object document */ protected $_document; /** * @var integer version */ protected $_version; /** * @var string comment */ protected $_comment; /** * @var string date */ protected $_date; /** * @var integer userID */ protected $_userID; /** * @var string dir on disk (deprecated) */ protected $_dir; /** * @var string original file name */ protected $_orgFileName; /** * @var string file type (actually the extension without the leading dot) */ protected $_fileType; /** * @var string mime type */ protected $_mimeType; /** * @var string checksum of content */ protected $_checksum; /** * @var object workflow */ protected $_workflow; /** * @var object workflow state */ protected $_workflowState; /** * @var object dms */ public $_dms; /** * Recalculate the status of a document * * The methods checks the review and approval status and sets the * status of the document accordingly. * * If status is S_RELEASED and the version has a workflow, then set * the status to S_IN_WORKFLOW * If status is S_RELEASED and there are reviewers => set status S_DRAFT_REV * If status is S_RELEASED or S_DRAFT_REV and there are approvers => set * status S_DRAFT_APP * If status is draft and there are no approver and no reviewers => set * status to S_RELEASED * The status of a document with the current status S_OBSOLETE, S_REJECTED, * or S_EXPIRED will not be changed unless the parameter * $ignorecurrentstatus is set to true. * * This method may not be called after a negative approval or review to * recalculated the status, because * it doesn't take a defeating approval or review into account. It will * just check for a pending workflow, approval or review and set the status * accordingly, e.g. after the list of reviewers or appovers has been * modified. If there is not pending workflow, approval or review the * status will be set to S_RELEASED. * * This method will call {@see SeedDMS_Core_DocumentContent::setStatus()} * which checks if the status has actually changed. This is, why this * function can be called at any time without harm to the status log. * * @param boolean $ignorecurrentstatus ignore the current status and * recalculate a new status in any case * @param object $user the user initiating this method * @param string $msg message stored in status log when status is set */ function verifyStatus($ignorecurrentstatus=false, $user=null, $msg='') { /* {{{ */ unset($this->_status); $st=$this->getStatus(); if (!$ignorecurrentstatus && ($st["status"]==S_OBSOLETE || $st["status"]==S_REJECTED || $st["status"]==S_EXPIRED )) return; unset($this->_workflow); // force to be reloaded from DB $hasworkflow = $this->getWorkflow() ? true : false; /* $pendingReview will be set when there are still open reviews */ $pendingReview=false; unset($this->_reviewStatus); // force to be reloaded from DB $reviewStatus=$this->getReviewStatus(); if (is_array($reviewStatus) && count($reviewStatus)>0) { foreach ($reviewStatus as $r){ if ($r["status"]==0){ $pendingReview=true; break; } } } /* $pendingApproval will be set when there are still open approvals */ $pendingApproval=false; unset($this->_approvalStatus); // force to be reloaded from DB $approvalStatus=$this->getApprovalStatus(); if (is_array($approvalStatus) && count($approvalStatus)>0) { foreach ($approvalStatus as $a){ if ($a["status"]==0){ $pendingApproval=true; break; } } } /* First check for a running workflow or open reviews or approvals. */ if ($hasworkflow) $this->setStatus(S_IN_WORKFLOW,$msg,$user); elseif ($pendingReview) $this->setStatus(S_DRAFT_REV,$msg,$user); elseif ($pendingApproval) $this->setStatus(S_DRAFT_APP,$msg,$user); else $this->setStatus(S_RELEASED,$msg,$user); } /* }}} */ function __construct($id, $document, $version, $comment, $date, $userID, $dir, $orgFileName, $fileType, $mimeType, $fileSize=0, $checksum='') { /* {{{ */ parent::__construct($id); $this->_document = $document; $this->_version = (int) $version; $this->_comment = $comment; $this->_date = $date; $this->_userID = (int) $userID; $this->_dir = $dir; $this->_orgFileName = $orgFileName; $this->_fileType = $fileType; $this->_mimeType = $mimeType; $this->_dms = $document->getDMS(); if(!$fileSize) { $this->_fileSize = SeedDMS_Core_File::fileSize($this->_dms->contentDir . $this->getPath()); } else { $this->_fileSize = $fileSize; } $this->_checksum = $checksum; $this->_workflow = null; $this->_workflowState = null; } /* }}} */ /** * Check if this object is of type 'documentcontent'. * * @param string $type type of object */ public function isType($type) { /* {{{ */ return $type == 'documentcontent'; } /* }}} */ function getVersion() { return $this->_version; } function getComment() { return $this->_comment; } function getDate() { return $this->_date; } function getOriginalFileName() { return $this->_orgFileName; } function getFileType() { return $this->_fileType; } function getFileName(){ return $this->_version . $this->_fileType; } /** * getDir and the corresponding database table field are deprecated */ function __getDir() { return $this->_dir; } function getMimeType() { return $this->_mimeType; } function getDocument() { return $this->_document; } function getUser() { /* {{{ */ if (!isset($this->_user)) $this->_user = $this->_document->getDMS()->getUser($this->_userID); return $this->_user; } /* }}} */ /** * Return path of file on disk relative to the content directory * * Since version 5.1.13 a single '.' in the fileType will be skipped. * On Windows a file named 'name.' will be saved as 'name' but the fileType * will contain the a single '.'. * * @return string path of file on disc */ function getPath() { return $this->_document->getDir() . $this->_version . $this->_fileType; } function setDate($date = false) { /* {{{ */ $db = $this->_document->getDMS()->getDB(); if(!$date) $date = time(); else { if(!is_numeric($date)) return false; } $queryStr = "UPDATE `tblDocumentContent` SET `date` = ".(int) $date." WHERE `document` = " . $this->_document->getID() . " AND `version` = " . $this->_version; if (!$db->getResult($queryStr)) return false; $this->_date = $date; return true; } /* }}} */ function getFileSize() { /* {{{ */ return $this->_fileSize; } /* }}} */ /** * Set file size by reading the file */ function setFileSize() { /* {{{ */ $filesize = SeedDMS_Core_File::fileSize($this->_dms->contentDir . $this->_document->getDir() . $this->getFileName()); if($filesize === false) return false; $db = $this->_document->getDMS()->getDB(); $queryStr = "UPDATE `tblDocumentContent` SET `fileSize` = ".$filesize." where `document` = " . $this->_document->getID() . " AND `version` = " . $this->_version; if (!$db->getResult($queryStr)) return false; $this->_fileSize = $filesize; return true; } /* }}} */ function getChecksum() { /* {{{ */ return $this->_checksum; } /* }}} */ /** * Set checksum by reading the file */ function setChecksum() { /* {{{ */ $checksum = SeedDMS_Core_File::checksum($this->_dms->contentDir . $this->_document->getDir() . $this->getFileName()); if($checksum === false) return false; $db = $this->_document->getDMS()->getDB(); $queryStr = "UPDATE `tblDocumentContent` SET `checksum` = ".$db->qstr($checksum)." where `document` = " . $this->_document->getID() . " AND `version` = " . $this->_version; if (!$db->getResult($queryStr)) return false; $this->_checksum = $checksum; return true; } /* }}} */ function setComment($newComment) { /* {{{ */ $db = $this->_document->getDMS()->getDB(); $queryStr = "UPDATE `tblDocumentContent` SET `comment` = ".$db->qstr($newComment)." WHERE `document` = " . $this->_document->getID() . " AND `version` = " . $this->_version; if (!$db->getResult($queryStr)) return false; $this->_comment = $newComment; return true; } /* }}} */ /** * Get the latest status of the content * * The status of the content reflects its current review, approval or workflow * state. A status can be a negative or positive number or 0. A negative * numbers indicate a missing approval, review or an obsolete content. * Positive numbers indicate some kind of approval or workflow being * active, but not necessarily a release. * S_DRAFT_REV, 0 * S_DRAFT_APP, 1 * S_RELEASED, 2 * S_IN_WORKFLOW, 3 * S_REJECTED, -1 * S_OBSOLETE, -2 * S_EXPIRED, -3 * When a content is inserted and does not need approval nor review, * then its status is set to S_RELEASED immediately. Any change of * the status is monitored in the table tblDocumentStatusLog. This * function will always return the latest entry for the content. * * @return array latest record from tblDocumentStatusLog */ function getStatus($limit=1) { /* {{{ */ $db = $this->_document->getDMS()->getDB(); if (!is_numeric($limit)) return false; // Retrieve the current overall status of the content represented by // this object. if (!isset($this->_status)) { $queryStr= "SELECT `tblDocumentStatus`.*, `tblDocumentStatusLog`.`status`, ". "`tblDocumentStatusLog`.`comment`, `tblDocumentStatusLog`.`date`, ". "`tblDocumentStatusLog`.`userID` ". "FROM `tblDocumentStatus` ". "LEFT JOIN `tblDocumentStatusLog` USING (`statusID`) ". "WHERE `tblDocumentStatus`.`documentID` = '". $this->_document->getID() ."' ". "AND `tblDocumentStatus`.`version` = '". $this->_version ."' ". "ORDER BY `tblDocumentStatusLog`.`statusLogID` DESC LIMIT ".(int) $limit; $res = $db->getResultArray($queryStr); if (is_bool($res) && !$res) return false; if (count($res)!=1) return false; $this->_status = $res[0]; } return $this->_status; } /* }}} */ /** * Get current and former states of the document content * * @param integer $limit if not set all log entries will be returned * @return array list of status changes */ function getStatusLog($limit=0) { /* {{{ */ $db = $this->_document->getDMS()->getDB(); if (!is_numeric($limit)) return false; $queryStr= "SELECT `tblDocumentStatus`.*, `tblDocumentStatusLog`.`status`, ". "`tblDocumentStatusLog`.`comment`, `tblDocumentStatusLog`.`date`, ". "`tblDocumentStatusLog`.`userID` ". "FROM `tblDocumentStatus` ". "LEFT JOIN `tblDocumentStatusLog` USING (`statusID`) ". "WHERE `tblDocumentStatus`.`documentID` = '". $this->_document->getID() ."' ". "AND `tblDocumentStatus`.`version` = '". $this->_version ."' ". "ORDER BY `tblDocumentStatusLog`.`statusLogID` DESC "; if($limit) $queryStr .= "LIMIT ".(int) $limit; $res = $db->getResultArray($queryStr); if (is_bool($res) && !$res) return false; return $res; } /* }}} */ /** * Set the status of the content * Setting the status means to add another entry into the table * tblDocumentStatusLog. The method returns also false if the status * is already set on the value passed to the method. * * @param integer $status new status of content * @param string $comment comment for this status change * @param object $updateUser user initiating the status change * @return boolean true on success, otherwise false */ function setStatus($status, $comment, $updateUser, $date='') { /* {{{ */ $db = $this->_document->getDMS()->getDB(); if (!is_numeric($status)) return false; /* return an error if $updateuser is not set */ if(!$updateUser) return false; // If the supplied value lies outside of the accepted range, return an // error. if ($status < -3 || $status > 3) { return false; } // Retrieve the current overall status of the content represented by // this object, if it hasn't been done already. if (!isset($this->_status)) { $this->getStatus(); } if ($this->_status["status"]==$status) { return true; } if($date) $ddate = $db->qstr($date); else $ddate = $db->getCurrentDatetime(); $db->startTransaction(); $queryStr = "INSERT INTO `tblDocumentStatusLog` (`statusID`, `status`, `comment`, `date`, `userID`) ". "VALUES ('". $this->_status["statusID"] ."', '". (int) $status ."', ".$db->qstr($comment).", ".$ddate.", '". $updateUser->getID() ."')"; $res = $db->getResult($queryStr); if (is_bool($res) && !$res) { $db->rollbackTransaction(); return false; } /* Check if 'onSetStatus' callback is set */ if(isset($this->_dms->callbacks['onSetStatus'])) { foreach($this->_dms->callbacks['onSetStatus'] as $callback) { $ret = call_user_func($callback[0], $callback[1], $this, $this->_status["status"], $status); if(is_bool($ret)) { unset($this->_status); if($ret) $db->commitTransaction(); else $db->rollbackTransaction(); return $ret; } } } $db->commitTransaction(); unset($this->_status); return true; } /* }}} */ /** * Rewrites the complete status log * * Attention: this function is highly dangerous. * It removes an existing status log and rewrites it. * This method was added for importing an xml dump. * * @param array $statuslog new status log with the newest log entry first. * @return boolean true on success, otherwise false */ function rewriteStatusLog($statuslog) { /* {{{ */ $db = $this->_document->getDMS()->getDB(); $queryStr= "SELECT `tblDocumentStatus`.* FROM `tblDocumentStatus` WHERE `tblDocumentStatus`.`documentID` = '". $this->_document->getID() ."' AND `tblDocumentStatus`.`version` = '". $this->_version ."' "; $res = $db->getResultArray($queryStr); if (is_bool($res) && !$res) return false; $statusID = $res[0]['statusID']; $db->startTransaction(); /* First, remove the old entries */ $queryStr = "DELETE from `tblDocumentStatusLog` where `statusID`=".$statusID; if (!$db->getResult($queryStr)) { $db->rollbackTransaction(); return false; } /* Second, insert the new entries */ $statuslog = array_reverse($statuslog); foreach($statuslog as $log) { if(!SeedDMS_Core_DMS::checkDate($log['date'], 'Y-m-d H:i:s')) { $db->rollbackTransaction(); return false; } $queryStr = "INSERT INTO `tblDocumentStatusLog` (`statusID`, `status`, `comment`, `date`, `userID`) ". "VALUES ('".$statusID ."', '".(int) $log['status']."', ".$db->qstr($log['comment']) .", ".$db->qstr($log['date']).", ".$log['user']->getID().")"; if (!$db->getResult($queryStr)) { $db->rollbackTransaction(); return false; } } $db->commitTransaction(); return true; } /* }}} */ /** * Returns the access mode similar to a document * * There is no real access mode for document content, so this is more * like a virtual access mode, derived from the status of the document * content. The function checks if {@link SeedDMS_Core_DMS::noReadForStatus} * contains the status of the version and returns M_NONE if it exists and * the user is not involved in a workflow or review/approval/revision. * This method is called by all functions that returns the content e.g. * {@link SeedDMS_Core_Document::getLatestContent()} * It is also used by {@link SeedDMS_Core_Document::getAccessMode()} to * prevent access on the whole document if there is no accessible version. * * FIXME: This function only works propperly if $u is the currently logged in * user, because noReadForStatus will be set for this user. * FIXED: instead of using $dms->noReadForStatus it is take from the user's role * * @param object $u user * @return integer either M_NONE or M_READ */ function getAccessMode($u) { /* {{{ */ $dms = $this->_document->getDMS(); /* Check if 'onCheckAccessDocumentContent' callback is set */ if(isset($this->_dms->callbacks['onCheckAccessDocumentContent'])) { foreach($this->_dms->callbacks['onCheckAccessDocumentContent'] as $callback) { if(($ret = call_user_func($callback[0], $callback[1], $this, $u)) > 0) { return $ret; } } } return M_READ; if(!$u) return M_NONE; /* If read access isn't further restricted by status, than grant read access */ if(!$dms->noReadForStatus) return M_READ; $noReadForStatus = $dms->noReadForStatus; /* If the current status is not in list of status without read access, then grant read access */ if(!in_array($this->getStatus()['status'], $noReadForStatus)) return M_READ; /* Administrators have unrestricted access */ if ($u->isAdmin()) return M_READ; /* The owner of the document has unrestricted access */ $owner = $this->_document->getOwner(); if ($u->getID() == $owner->getID()) return M_READ; /* Read/Write access on the document will also grant access on the version */ if($this->_document->getAccessMode($u) >= M_READWRITE) return M_READ; /* At this point the current status is in the list of status without read access. * The only way to still gain read access is, if the user is involved in the * process, e.g. is a reviewer, approver or an active person in the workflow. */ $s = $this->getStatus(); switch($s['status']) { case S_DRAFT_REV: $status = $this->getReviewStatus(); foreach ($status as $r) { if($r['status'] != -2) // Check if reviewer was removed switch ($r["type"]) { case 0: // Reviewer is an individual. if($u->getId() == $r["required"]) return M_READ; break; case 1: // Reviewer is a group. $required = $dms->getGroup($r["required"]); if (is_object($required) && $required->isMember($u)) return M_READ; break; } } break; case S_DRAFT_APP: $status = $this->getApprovalStatus(); foreach ($status as $r) { if($r['status'] != -2) // Check if approver was removed switch ($r["type"]) { case 0: // Reviewer is an individual. if($u->getId() == $r["required"]) return M_READ; break; case 1: // Reviewer is a group. $required = $dms->getGroup($r["required"]); if (is_object($required) && $required->isMember($u)) return M_READ; break; } } break; case S_RELEASED: break; case S_IN_WORKFLOW: if(!$this->_workflow) $this->getWorkflow(); if($this->_workflow) { if (!$this->_workflowState) $this->getWorkflowState(); $transitions = $this->_workflow->getNextTransitions($this->_workflowState); foreach($transitions as $transition) { if($this->triggerWorkflowTransitionIsAllowed($u, $transition)) return M_READ; } } break; case S_REJECTED: break; case S_OBSOLETE: break; case S_EXPIRED: break; } return M_NONE; } /* }}} */ /** * Get the current review status of the document content * The review status is a list of reviews and its current status * * @param integer $limit the number of recent status changes per reviewer * @return array list of review status */ function getReviewStatus($limit=1) { /* {{{ */ $db = $this->_document->getDMS()->getDB(); if (!is_numeric($limit)) return false; // Retrieve the current status of each assigned reviewer for the content // represented by this object. // FIXME: caching was turned off to make list of review log in ViewDocument // possible if (1 || !isset($this->_reviewStatus)) { /* First get a list of all reviews for this document content */ $queryStr= "SELECT `reviewID` FROM `tblDocumentReviewers` WHERE `version`='".$this->_version ."' AND `documentID` = '". $this->_document->getID() ."' "; $recs = $db->getResultArray($queryStr); if (is_bool($recs) && !$recs) return false; $this->_reviewStatus = array(); if($recs) { foreach($recs as $rec) { $queryStr= "SELECT `tblDocumentReviewers`.*, `tblDocumentReviewLog`.`reviewLogID`, `tblDocumentReviewLog`.`status`, ". "`tblDocumentReviewLog`.`comment`, `tblDocumentReviewLog`.`date`, ". "`tblDocumentReviewLog`.`userID`, `tblUsers`.`fullName`, `tblGroups`.`name` AS `groupName` ". "FROM `tblDocumentReviewers` ". "LEFT JOIN `tblDocumentReviewLog` USING (`reviewID`) ". "LEFT JOIN `tblUsers` on `tblUsers`.`id` = `tblDocumentReviewers`.`required`". "LEFT JOIN `tblGroups` on `tblGroups`.`id` = `tblDocumentReviewers`.`required`". "WHERE `tblDocumentReviewers`.`reviewID` = '". $rec['reviewID'] ."' ". "ORDER BY `tblDocumentReviewLog`.`reviewLogID` DESC LIMIT ".(int) $limit; $res = $db->getResultArray($queryStr); if (is_bool($res) && !$res) { unset($this->_reviewStatus); return false; } foreach($res as &$t) { $filename = $this->_dms->contentDir . $this->_document->getDir().'r'.$t['reviewLogID']; if(file_exists($filename)) $t['file'] = $filename; else $t['file'] = ''; } $this->_reviewStatus = array_merge($this->_reviewStatus, $res); } } } return $this->_reviewStatus; } /* }}} */ /** * Rewrites the complete review log * * Attention: this function is highly dangerous. * It removes an existing review log and rewrites it. * This method was added for importing an xml dump. * * @param array $reviewlog new status log with the newest log entry first. * @return boolean true on success, otherwise false */ function rewriteReviewLog($reviewers) { /* {{{ */ $db = $this->_document->getDMS()->getDB(); $queryStr= "SELECT `tblDocumentReviewers`.* FROM `tblDocumentReviewers` WHERE `tblDocumentReviewers`.`documentID` = '". $this->_document->getID() ."' AND `tblDocumentReviewers`.`version` = '". $this->_version ."' "; $res = $db->getResultArray($queryStr); if (is_bool($res) && !$res) return false; $db->startTransaction(); if($res) { foreach($res as $review) { $reviewID = $review['reviewID']; /* First, remove the old entries */ $queryStr = "DELETE from `tblDocumentReviewLog` where `reviewID`=".$reviewID; if (!$db->getResult($queryStr)) { $db->rollbackTransaction(); return false; } $queryStr = "DELETE from `tblDocumentReviewers` where `reviewID`=".$reviewID; if (!$db->getResult($queryStr)) { $db->rollbackTransaction(); return false; } } } /* Second, insert the new entries */ foreach($reviewers as $review) { $queryStr = "INSERT INTO `tblDocumentReviewers` (`documentID`, `version`, `type`, `required`) ". "VALUES ('".$this->_document->getID()."', '".$this->_version."', ".$review['type'] .", ".(is_object($review['required']) ? $review['required']->getID() : (int) $review['required']).")"; if (!$db->getResult($queryStr)) { $db->rollbackTransaction(); return false; } $reviewID = $db->getInsertID('tblDocumentReviewers', 'reviewID'); $reviewlog = array_reverse($review['logs']); foreach($reviewlog as $log) { if(!SeedDMS_Core_DMS::checkDate($log['date'], 'Y-m-d H:i:s')) { $db->rollbackTransaction(); return false; } $queryStr = "INSERT INTO `tblDocumentReviewLog` (`reviewID`, `status`, `comment`, `date`, `userID`) ". "VALUES ('".$reviewID ."', '".(int) $log['status']."', ".$db->qstr($log['comment']) .", ".$db->qstr($log['date']).", ".(is_object($log['user']) ? $log['user']->getID() : (int) $log['user']).")"; if (!$db->getResult($queryStr)) { $db->rollbackTransaction(); return false; } $reviewLogID = $db->getInsertID('tblDocumentReviewLog', 'reviewLogID'); if(!empty($log['file'])) { SeedDMS_Core_File::copyFile($log['file'], $this->_dms->contentDir . $this->_document->getDir() . 'r' . $reviewLogID); } } } $db->commitTransaction(); return true; } /* }}} */ /** * Get the current approval status of the document content * The approval status is a list of approvals and its current status * * @param integer $limit the number of recent status changes per approver * @return array list of approval status */ function getApprovalStatus($limit=1) { /* {{{ */ $db = $this->_document->getDMS()->getDB(); if (!is_numeric($limit)) return false; // Retrieve the current status of each assigned approver for the content // represented by this object. // FIXME: caching was turned off to make list of approval log in ViewDocument // possible if (1 || !isset($this->_approvalStatus)) { /* First get a list of all approvals for this document content */ $queryStr= "SELECT `approveID` FROM `tblDocumentApprovers` WHERE `version`='".$this->_version ."' AND `documentID` = '". $this->_document->getID() ."' "; $recs = $db->getResultArray($queryStr); if (is_bool($recs) && !$recs) return false; $this->_approvalStatus = array(); if($recs) { foreach($recs as $rec) { $queryStr= "SELECT `tblDocumentApprovers`.*, `tblDocumentApproveLog`.`approveLogID`, `tblDocumentApproveLog`.`status`, ". "`tblDocumentApproveLog`.`comment`, `tblDocumentApproveLog`.`date`, ". "`tblDocumentApproveLog`.`userID`, `tblUsers`.`fullName`, `tblGroups`.`name` AS `groupName` ". "FROM `tblDocumentApprovers` ". "LEFT JOIN `tblDocumentApproveLog` USING (`approveID`) ". "LEFT JOIN `tblUsers` on `tblUsers`.`id` = `tblDocumentApprovers`.`required` ". "LEFT JOIN `tblGroups` on `tblGroups`.`id` = `tblDocumentApprovers`.`required`". "WHERE `tblDocumentApprovers`.`approveID` = '". $rec['approveID'] ."' ". "ORDER BY `tblDocumentApproveLog`.`approveLogID` DESC LIMIT ".(int) $limit; $res = $db->getResultArray($queryStr); if (is_bool($res) && !$res) { unset($this->_approvalStatus); return false; } foreach($res as &$t) { $filename = $this->_dms->contentDir . $this->_document->getDir().'a'.$t['approveLogID']; if(file_exists($filename)) $t['file'] = $filename; else $t['file'] = ''; } $this->_approvalStatus = array_merge($this->_approvalStatus, $res); } } } return $this->_approvalStatus; } /* }}} */ /** * Rewrites the complete approval log * * Attention: this function is highly dangerous. * It removes an existing review log and rewrites it. * This method was added for importing an xml dump. * * @param array $reviewlog new status log with the newest log entry first. * @return boolean true on success, otherwise false */ function rewriteApprovalLog($reviewers) { /* {{{ */ $db = $this->_document->getDMS()->getDB(); $queryStr= "SELECT `tblDocumentApprovers`.* FROM `tblDocumentApprovers` WHERE `tblDocumentApprovers`.`documentID` = '". $this->_document->getID() ."' AND `tblDocumentApprovers`.`version` = '". $this->_version ."' "; $res = $db->getResultArray($queryStr); if (is_bool($res) && !$res) return false; $db->startTransaction(); if($res) { foreach($res as $review) { $reviewID = $review['reviewID']; /* First, remove the old entries */ $queryStr = "DELETE from `tblDocumentApproveLog` where `approveID`=".$reviewID; if (!$db->getResult($queryStr)) { $db->rollbackTransaction(); return false; } $queryStr = "DELETE from `tblDocumentApprovers` where `approveID`=".$reviewID; if (!$db->getResult($queryStr)) { $db->rollbackTransaction(); return false; } } } /* Second, insert the new entries */ foreach($reviewers as $review) { $queryStr = "INSERT INTO `tblDocumentApprovers` (`documentID`, `version`, `type`, `required`) ". "VALUES ('".$this->_document->getID()."', '".$this->_version."', ".$review['type'] .", ".(is_object($review['required']) ? $review['required']->getID() : (int) $review['required']).")"; if (!$db->getResult($queryStr)) { $db->rollbackTransaction(); return false; } $reviewID = $db->getInsertID('tblDocumentApprovers', 'approveID'); $reviewlog = array_reverse($review['logs']); foreach($reviewlog as $log) { if(!SeedDMS_Core_DMS::checkDate($log['date'], 'Y-m-d H:i:s')) { $db->rollbackTransaction(); return false; } $queryStr = "INSERT INTO `tblDocumentApproveLog` (`approveID`, `status`, `comment`, `date`, `userID`) ". "VALUES ('".$reviewID ."', '".(int) $log['status']."', ".$db->qstr($log['comment']) .", ".$db->qstr($log['date']).", ".(is_object($log['user']) ? $log['user']->getID() : (int) $log['user']).")"; if (!$db->getResult($queryStr)) { $db->rollbackTransaction(); return false; } $approveLogID = $db->getInsertID('tblDocumentApproveLog', 'approveLogID'); if(!empty($log['file'])) { SeedDMS_Core_File::copyFile($log['file'], $this->_dms->contentDir . $this->_document->getDir() . 'a' . $approveLogID); } } } $db->commitTransaction(); return true; } /* }}} */ function addIndReviewer($user, $requestUser) { /* {{{ */ $db = $this->_document->getDMS()->getDB(); $userID = $user->getID(); // Get the list of users and groups with read access to this document. if($this->_document->getAccessMode($user) < M_READ) { return -2; } // Check to see if the user has already been added to the review list. $reviewStatus = $user->getReviewStatus($this->_document->getID(), $this->_version); if (is_bool($reviewStatus) && !$reviewStatus) { return -1; } $indstatus = false; if (count($reviewStatus["indstatus"]) > 0) { $indstatus = array_pop($reviewStatus["indstatus"]); if($indstatus["status"]!=-2) { // User is already on the list of reviewers; return an error. return -3; } } // Add the user into the review database. if (!$indstatus || ($indstatus && $indstatus["status"]!=-2)) { $queryStr = "INSERT INTO `tblDocumentReviewers` (`documentID`, `version`, `type`, `required`) ". "VALUES ('". $this->_document->getID() ."', '". $this->_version ."', '0', '". $userID ."')"; $res = $db->getResult($queryStr); if (is_bool($res) && !$res) { return -1; } $reviewID = $db->getInsertID('tblDocumentReviewers', 'reviewID'); } else { $reviewID = isset($indstatus["reviewID"]) ? $indstatus["reviewID"] : NULL; } $queryStr = "INSERT INTO `tblDocumentReviewLog` (`reviewID`, `status`, `comment`, `date`, `userID`) ". "VALUES ('". $reviewID ."', '0', '', ".$db->getCurrentDatetime().", '". $requestUser->getID() ."')"; $res = $db->getResult($queryStr); if (is_bool($res) && !$res) { return -1; } // Add reviewer to event notification table. //$this->_document->addNotify($userID, true); return 0; } /* }}} */ function addGrpReviewer($group, $requestUser) { /* {{{ */ $db = $this->_document->getDMS()->getDB(); $groupID = $group->getID(); // Get the list of users and groups with read access to this document. if (!isset($this->_readAccessList)) { // TODO: error checking. $this->_readAccessList = $this->_document->getReadAccessList(); } $approved = false; foreach ($this->_readAccessList["groups"] as $appGroup) { if ($groupID == $appGroup->getID()) { $approved = true; break; } } if (!$approved) { return -2; } // Check to see if the group has already been added to the review list. $reviewStatus = $group->getReviewStatus($this->_document->getID(), $this->_version); if (is_bool($reviewStatus) && !$reviewStatus) { return -1; } if (count($reviewStatus) > 0 && $reviewStatus[0]["status"]!=-2) { // Group is already on the list of reviewers; return an error. return -3; } // Add the group into the review database. if (!isset($reviewStatus[0]["status"]) || (isset($reviewStatus[0]["status"]) && $reviewStatus[0]["status"]!=-2)) { $queryStr = "INSERT INTO `tblDocumentReviewers` (`documentID`, `version`, `type`, `required`) ". "VALUES ('". $this->_document->getID() ."', '". $this->_version ."', '1', '". $groupID ."')"; $res = $db->getResult($queryStr); if (is_bool($res) && !$res) { return -1; } $reviewID = $db->getInsertID('tblDocumentReviewers', 'reviewID'); } else { $reviewID = isset($reviewStatus[0]["reviewID"])?$reviewStatus[0]["reviewID"]:NULL; } $queryStr = "INSERT INTO `tblDocumentReviewLog` (`reviewID`, `status`, `comment`, `date`, `userID`) ". "VALUES ('". $reviewID ."', '0', '', ".$db->getCurrentDatetime().", '". $requestUser->getID() ."')"; $res = $db->getResult($queryStr); if (is_bool($res) && !$res) { return -1; } // Add reviewer to event notification table. //$this->_document->addNotify($groupID, false); return 0; } /* }}} */ /** * Add a review to the document content * * This method will add an entry to the table tblDocumentReviewLog. * It will first check if the user is ment to review the document version. * It not the return value is -3. * Next it will check if the users has been removed from the list of * reviewers. In that case -4 will be returned. * If the given review status has been set by the user before, it cannot * be set again and 0 will be returned. Іf the review could be succesfully * added, the review log id will be returned. * * @see SeedDMS_Core_DocumentContent::setApprovalByInd() * @param object $user user doing the review * @param object $requestUser user asking for the review, this is mostly * the user currently logged in. * @param integer $status status of review * @param string $comment comment for review * @return integer new review log id */ function setReviewByInd($user, $requestUser, $status, $comment, $file='') { /* {{{ */ $db = $this->_document->getDMS()->getDB(); // Check to see if the user can be removed from the review list. $reviewStatus = $user->getReviewStatus($this->_document->getID(), $this->_version); if (is_bool($reviewStatus) && !$reviewStatus) { return -1; } if (count($reviewStatus["indstatus"])==0) { // User is not assigned to review this document. No action required. // Return an error. return -3; } $indstatus = array_pop($reviewStatus["indstatus"]); if ($indstatus["status"]==-2) { // User has been deleted from reviewers return -4; } // Check if the status is really different from the current status if ($indstatus["status"] == $status) return 0; $queryStr = "INSERT INTO `tblDocumentReviewLog` (`reviewID`, `status`, `comment`, `date`, `userID`) ". "VALUES ('". $indstatus["reviewID"] ."', '". (int) $status ."', ".$db->qstr($comment).", ".$db->getCurrentDatetime().", '". $requestUser->getID() ."')"; $res=$db->getResult($queryStr); if (is_bool($res) && !$res) return -1; $reviewLogID = $db->getInsertID('tblDocumentReviewLog', 'reviewLogID'); if($file) { SeedDMS_Core_File::copyFile($file, $this->_dms->contentDir . $this->_document->getDir() . 'r' . $reviewLogID); } return $reviewLogID; } /* }}} */ /** * Add a review to the document content * * This method is similar to * {@see SeedDMS_Core_DocumentContent::setReviewByInd()} but adds a review * for a group instead of a user. * * @param object $group group doing the review * @param object $requestUser user asking for the review, this is mostly * the user currently logged in. * @param integer $status status of review * @param string $comment comment for review * @return integer new review log id */ function setReviewByGrp($group, $requestUser, $status, $comment, $file='') { /* {{{ */ $db = $this->_document->getDMS()->getDB(); // Check to see if the user can be removed from the review list. $reviewStatus = $group->getReviewStatus($this->_document->getID(), $this->_version); if (is_bool($reviewStatus) && !$reviewStatus) { return -1; } if (count($reviewStatus)==0) { // User is not assigned to review this document. No action required. // Return an error. return -3; } if ($reviewStatus[0]["status"]==-2) { // Group has been deleted from reviewers return -4; } // Check if the status is really different from the current status if ($reviewStatus[0]["status"] == $status) return 0; $queryStr = "INSERT INTO `tblDocumentReviewLog` (`reviewID`, `status`, `comment`, `date`, `userID`) ". "VALUES ('". $reviewStatus[0]["reviewID"] ."', '". (int) $status ."', ".$db->qstr($comment).", ".$db->getCurrentDatetime().", '". $requestUser->getID() ."')"; $res=$db->getResult($queryStr); if (is_bool($res) && !$res) return -1; else { $reviewLogID = $db->getInsertID('tblDocumentReviewLog', 'reviewLogID'); if($file) { SeedDMS_Core_File::copyFile($file, $this->_dms->contentDir . $this->_document->getDir() . 'r' . $reviewLogID); } return $reviewLogID; } } /* }}} */ function addIndApprover($user, $requestUser) { /* {{{ */ $db = $this->_document->getDMS()->getDB(); $userID = $user->getID(); // Get the list of users and groups with read access to this document. if($this->_document->getAccessMode($user) < M_READ) { return -2; } // Check to see if the user has already been added to the approvers list. $approvalStatus = $user->getApprovalStatus($this->_document->getID(), $this->_version); if (is_bool($approvalStatus) && !$approvalStatus) { return -1; } $indstatus = false; if (count($approvalStatus["indstatus"]) > 0) { $indstatus = array_pop($approvalStatus["indstatus"]); if($indstatus["status"]!=-2) { // User is already on the list of approverss; return an error. return -3; } } if ( !$indstatus || (isset($indstatus["status"]) && $indstatus["status"]!=-2)) { // Add the user into the approvers database. $queryStr = "INSERT INTO `tblDocumentApprovers` (`documentID`, `version`, `type`, `required`) ". "VALUES ('". $this->_document->getID() ."', '". $this->_version ."', '0', '". $userID ."')"; $res = $db->getResult($queryStr); if (is_bool($res) && !$res) { return -1; } $approveID = $db->getInsertID('tblDocumentApprovers', 'approveID'); } else { $approveID = isset($indstatus["approveID"]) ? $indstatus["approveID"] : NULL; } $queryStr = "INSERT INTO `tblDocumentApproveLog` (`approveID`, `status`, `comment`, `date`, `userID`) ". "VALUES ('". $approveID ."', '0', '', ".$db->getCurrentDatetime().", '". $requestUser->getID() ."')"; $res = $db->getResult($queryStr); if (is_bool($res) && !$res) { return -1; } $approveLogID = $db->getInsertID('tblDocumentApproveLog', 'approveLogID'); return $approveLogID; } /* }}} */ function addGrpApprover($group, $requestUser) { /* {{{ */ $db = $this->_document->getDMS()->getDB(); $groupID = $group->getID(); // Get the list of users and groups with read access to this document. if (!isset($this->_readAccessList)) { // TODO: error checking. $this->_readAccessList = $this->_document->getReadAccessList(); } $approved = false; foreach ($this->_readAccessList["groups"] as $appGroup) { if ($groupID == $appGroup->getID()) { $approved = true; break; } } if (!$approved) { return -2; } // Check to see if the group has already been added to the approver list. $approvalStatus = $group->getApprovalStatus($this->_document->getID(), $this->_version); if (is_bool($approvalStatus) && !$approvalStatus) { return -1; } if (count($approvalStatus) > 0 && $approvalStatus[0]["status"]!=-2) { // Group is already on the list of approvers; return an error. return -3; } // Add the group into the approver database. if (!isset($approvalStatus[0]["status"]) || (isset($approvalStatus[0]["status"]) && $approvalStatus[0]["status"]!=-2)) { $queryStr = "INSERT INTO `tblDocumentApprovers` (`documentID`, `version`, `type`, `required`) ". "VALUES ('". $this->_document->getID() ."', '". $this->_version ."', '1', '". $groupID ."')"; $res = $db->getResult($queryStr); if (is_bool($res) && !$res) { return -1; } $approveID = $db->getInsertID('tblDocumentApprovers', 'approveID'); } else { $approveID = isset($approvalStatus[0]["approveID"])?$approvalStatus[0]["approveID"]:NULL; } $queryStr = "INSERT INTO `tblDocumentApproveLog` (`approveID`, `status`, `comment`, `date`, `userID`) ". "VALUES ('". $approveID ."', '0', '', ".$db->getCurrentDatetime().", '". $requestUser->getID() ."')"; $res = $db->getResult($queryStr); if (is_bool($res) && !$res) { return -1; } // Add approver to event notification table. //$this->_document->addNotify($groupID, false); $approveLogID = $db->getInsertID('tblDocumentApproveLog', 'approveLogID'); return $approveLogID; } /* }}} */ /** * Sets approval status of a document content for a user * * This function can be used to approve or reject a document content, or * to reset its approval state. In most cases this function will be * called by an user, but an admin may set the approval for * somebody else. * It is first checked if the user is in the list of approvers at all. * Then it is check if the approval status is already -2. In both cases * the function returns with an error. * * @see SeedDMS_Core_DocumentContent::setReviewByInd() * @param object $user user in charge for doing the approval * @param object $requestUser user actually calling this function * @param integer $status the status of the approval, possible values are * 0=unprocessed (maybe used to reset a status) * 1=approved, * -1=rejected, * -2=user is deleted (use {link * SeedDMS_Core_DocumentContent::delIndApprover} instead) * @param string $comment approval comment * @return integer 0 on success, < 0 in case of an error */ function setApprovalByInd($user, $requestUser, $status, $comment, $file='') { /* {{{ */ $db = $this->_document->getDMS()->getDB(); // Check to see if the user can be removed from the approval list. $approvalStatus = $user->getApprovalStatus($this->_document->getID(), $this->_version); if (is_bool($approvalStatus) && !$approvalStatus) { return -1; } if (count($approvalStatus["indstatus"])==0) { // User is not assigned to approve this document. No action required. // Return an error. return -3; } $indstatus = array_pop($approvalStatus["indstatus"]); if ($indstatus["status"]==-2) { // User has been deleted from approvers return -4; } // Check if the status is really different from the current status if ($indstatus["status"] == $status) return 0; $queryStr = "INSERT INTO `tblDocumentApproveLog` (`approveID`, `status`, `comment`, `date`, `userID`) ". "VALUES ('". $indstatus["approveID"] ."', '". (int) $status ."', ".$db->qstr($comment).", ".$db->getCurrentDatetime().", '". $requestUser->getID() ."')"; $res=$db->getResult($queryStr); if (is_bool($res) && !$res) return -1; $approveLogID = $db->getInsertID('tblDocumentApproveLog', 'approveLogID'); if($file) { SeedDMS_Core_File::copyFile($file, $this->_dms->contentDir . $this->_document->getDir() . 'a' . $approveLogID); } return $approveLogID; } /* }}} */ /** * Sets approval status of a document content for a group * The functions behaves like * {link SeedDMS_Core_DocumentContent::setApprovalByInd} but does it for * group instead of a user */ function setApprovalByGrp($group, $requestUser, $status, $comment, $file='') { /* {{{ */ $db = $this->_document->getDMS()->getDB(); // Check to see if the user can be removed from the approval list. $approvalStatus = $group->getApprovalStatus($this->_document->getID(), $this->_version); if (is_bool($approvalStatus) && !$approvalStatus) { return -1; } if (count($approvalStatus)==0) { // User is not assigned to approve this document. No action required. // Return an error. return -3; } if ($approvalStatus[0]["status"]==-2) { // Group has been deleted from approvers return -4; } // Check if the status is really different from the current status if ($approvalStatus[0]["status"] == $status) return 0; $queryStr = "INSERT INTO `tblDocumentApproveLog` (`approveID`, `status`, `comment`, `date`, `userID`) ". "VALUES ('". $approvalStatus[0]["approveID"] ."', '". (int) $status ."', ".$db->qstr($comment).", ".$db->getCurrentDatetime().", '". $requestUser->getID() ."')"; $res=$db->getResult($queryStr); if (is_bool($res) && !$res) return -1; $approveLogID = $db->getInsertID('tblDocumentApproveLog', 'approveLogID'); if($file) { SeedDMS_Core_File::copyFile($file, $this->_dms->contentDir . $this->_document->getDir() . 'a' . $approveLogID); } return $approveLogID; } /* }}} */ function delIndReviewer($user, $requestUser, $msg='') { /* {{{ */ $db = $this->_document->getDMS()->getDB(); // Check to see if the user can be removed from the review list. $reviewStatus = $user->getReviewStatus($this->_document->getID(), $this->_version); if (is_bool($reviewStatus) && !$reviewStatus) { return -1; } if (count($reviewStatus["indstatus"])==0) { // User is not assigned to review this document. No action required. // Return an error. return -2; } $indstatus = array_pop($reviewStatus["indstatus"]); if ($indstatus["status"]!=0) { // User has already submitted a review or has already been deleted; // return an error. return -3; } $queryStr = "INSERT INTO `tblDocumentReviewLog` (`reviewID`, `status`, `comment`, `date`, `userID`) ". "VALUES ('". $indstatus["reviewID"] ."', '".S_LOG_USER_REMOVED."', ".$db->qstr($msg).", ".$db->getCurrentDatetime().", '". $requestUser->getID() ."')"; $res = $db->getResult($queryStr); if (is_bool($res) && !$res) { return -1; } return 0; } /* }}} */ function delGrpReviewer($group, $requestUser, $msg='') { /* {{{ */ $db = $this->_document->getDMS()->getDB(); $groupID = $group->getID(); // Check to see if the user can be removed from the review list. $reviewStatus = $group->getReviewStatus($this->_document->getID(), $this->_version); if (is_bool($reviewStatus) && !$reviewStatus) { return -1; } if (count($reviewStatus)==0) { // User is not assigned to review this document. No action required. // Return an error. return -2; } if ($reviewStatus[0]["status"]!=0) { // User has already submitted a review or has already been deleted; // return an error. return -3; } $queryStr = "INSERT INTO `tblDocumentReviewLog` (`reviewID`, `status`, `comment`, `date`, `userID`) ". "VALUES ('". $reviewStatus[0]["reviewID"] ."', '".S_LOG_USER_REMOVED."', ".$db->qstr($msg).", ".$db->getCurrentDatetime().", '". $requestUser->getID() ."')"; $res = $db->getResult($queryStr); if (is_bool($res) && !$res) { return -1; } return 0; } /* }}} */ function delIndApprover($user, $requestUser, $msg='') { /* {{{ */ $db = $this->_document->getDMS()->getDB(); $userID = $user->getID(); // Check to see if the user can be removed from the approval list. $approvalStatus = $user->getApprovalStatus($this->_document->getID(), $this->_version); if (is_bool($approvalStatus) && !$approvalStatus) { return -1; } if (count($approvalStatus["indstatus"])==0) { // User is not assigned to approve this document. No action required. // Return an error. return -2; } $indstatus = array_pop($approvalStatus["indstatus"]); if ($indstatus["status"]!=0) { // User has already submitted an approval or has already been deleted; // return an error. return -3; } $queryStr = "INSERT INTO `tblDocumentApproveLog` (`approveID`, `status`, `comment`, `date`, `userID`) ". "VALUES ('". $indstatus["approveID"] ."', '".S_LOG_USER_REMOVED."', ".$db->qstr($msg).", ".$db->getCurrentDatetime().", '". $requestUser->getID() ."')"; $res = $db->getResult($queryStr); if (is_bool($res) && !$res) { return -1; } return 0; } /* }}} */ function delGrpApprover($group, $requestUser, $msg='') { /* {{{ */ $db = $this->_document->getDMS()->getDB(); $groupID = $group->getID(); // Check to see if the user can be removed from the approver list. $approvalStatus = $group->getApprovalStatus($this->_document->getID(), $this->_version); if (is_bool($approvalStatus) && !$approvalStatus) { return -1; } if (count($approvalStatus)==0) { // User is not assigned to approve this document. No action required. // Return an error. return -2; } if ($approvalStatus[0]["status"]!=0) { // User has already submitted an approval or has already been deleted; // return an error. return -3; } $queryStr = "INSERT INTO `tblDocumentApproveLog` (`approveID`, `status`, `comment`, `date`, `userID`) ". "VALUES ('". $approvalStatus[0]["approveID"] ."', '".S_LOG_USER_REMOVED."', ".$db->qstr($msg).", ".$db->getCurrentDatetime().", '". $requestUser->getID() ."')"; $res = $db->getResult($queryStr); if (is_bool($res) && !$res) { return -1; } return 0; } /* }}} */ /** * Set state of workflow assigned to the document content * * @param object $state */ function setWorkflowState($state) { /* {{{ */ $db = $this->_document->getDMS()->getDB(); if($this->_workflow) { $queryStr = "UPDATE `tblWorkflowDocumentContent` set `state`=". $state->getID() ." WHERE `workflow`=". intval($this->_workflow->getID()). " AND `document`=". intval($this->_document->getID()) ." AND version=". intval($this->_version) .""; if (!$db->getResult($queryStr)) { return false; } $this->_workflowState = $state; return true; } return false; } /* }}} */ /** * Get state of workflow assigned to the document content * * @return object/boolean an object of class SeedDMS_Core_Workflow_State * or false in case of error, e.g. the version has not a workflow */ function getWorkflowState() { /* {{{ */ $db = $this->_document->getDMS()->getDB(); if(!$this->_workflow) $this->getWorkflow(); if(!$this->_workflow) return false; if (!$this->_workflowState) { $queryStr= "SELECT b.* FROM `tblWorkflowDocumentContent` a LEFT JOIN `tblWorkflowStates` b ON a.`state` = b.`id` WHERE `workflow`=". intval($this->_workflow->getID()) ." AND a.`version`='".$this->_version ."' AND a.`document` = '". $this->_document->getID() ."' "; $recs = $db->getResultArray($queryStr); if (is_bool($recs) && !$recs) return false; $this->_workflowState = new SeedDMS_Core_Workflow_State($recs[0]['id'], $recs[0]['name'], $recs[0]['maxtime'], $recs[0]['precondfunc'], $recs[0]['documentstatus']); $this->_workflowState->setDMS($this->_document->getDMS()); } return $this->_workflowState; } /* }}} */ /** * Assign a workflow to a document * * @param object $workflow */ function setWorkflow($workflow, $user) { /* {{{ */ $db = $this->_document->getDMS()->getDB(); $this->getWorkflow(); if($workflow && is_object($workflow)) { $db->startTransaction(); $initstate = $workflow->getInitState(); $queryStr = "INSERT INTO `tblWorkflowDocumentContent` (`workflow`, `document`, `version`, `state`, `date`) VALUES (". $workflow->getID(). ", ". $this->_document->getID() .", ". $this->_version .", ".$initstate->getID().", ".$db->getCurrentDatetime().")"; if (!$db->getResult($queryStr)) { $db->rollbackTransaction(); return false; } $this->_workflow = $workflow; if(!$this->setStatus(S_IN_WORKFLOW, "Added workflow '".$workflow->getName()."'", $user)) { $db->rollbackTransaction(); return false; } $db->commitTransaction(); return true; } return true; } /* }}} */ /** * Get workflow assigned to the document content * * The method returns the last workflow if one was assigned. * If a the document version is in a sub workflow, it will have * a never date and therefore will be found first. * * @return object/boolean an object of class SeedDMS_Core_Workflow * or false in case of error, e.g. the version has not a workflow */ function getWorkflow() { /* {{{ */ $db = $this->_document->getDMS()->getDB(); if (!isset($this->_workflow)) { $queryStr= "SELECT b.* FROM `tblWorkflowDocumentContent` a LEFT JOIN `tblWorkflows` b ON a.`workflow` = b.`id` WHERE a.`version`='".$this->_version ."' AND a.`document` = '". $this->_document->getID() ."' " ." ORDER BY `date` DESC LIMIT 1"; $recs = $db->getResultArray($queryStr); if (is_bool($recs) && !$recs) return false; if(!$recs) return false; $this->_workflow = new SeedDMS_Core_Workflow($recs[0]['id'], $recs[0]['name'], $this->_document->getDMS()->getWorkflowState($recs[0]['initstate'])); $this->_workflow->setDMS($this->_document->getDMS()); } return $this->_workflow; } /* }}} */ /** * Rewrites the complete workflow log * * Attention: this function is highly dangerous. * It removes an existing workflow log and rewrites it. * This method was added for importing an xml dump. * * @param array $workflowlog new workflow log with the newest log entry first. * @return boolean true on success, otherwise false */ function rewriteWorkflowLog($workflowlog) { /* {{{ */ $db = $this->_document->getDMS()->getDB(); $db->startTransaction(); /* First, remove the old entries */ $queryStr = "DELETE FROM `tblWorkflowLog` WHERE `tblWorkflowLog`.`document` = '". $this->_document->getID() ."' AND `tblWorkflowLog`.`version` = '". $this->_version ."'"; if (!$db->getResult($queryStr)) { $db->rollbackTransaction(); return false; } /* Second, insert the new entries */ $workflowlog = array_reverse($workflowlog); foreach($workflowlog as $log) { if(!SeedDMS_Core_DMS::checkDate($log['date'], 'Y-m-d H:i:s')) { $db->rollbackTransaction(); return false; } $queryStr = "INSERT INTO `tblWorkflowLog` (`document`, `version`, `workflow`, `transition`, `comment`, `date`, `userid`) ". "VALUES ('".$this->_document->getID() ."', '".(int) $this->_version."', '".(int) $log['workflow']->getID()."', '".(int) $log['transition']->getID()."', ".$db->qstr($log['comment']) .", ".$db->qstr($log['date']).", ".$log['user']->getID().")"; if (!$db->getResult($queryStr)) { $db->rollbackTransaction(); return false; } } $db->commitTransaction(); return true; } /* }}} */ /** * Restart workflow from its initial state * * @return boolean true if workflow could be restarted * or false in case of error */ function rewindWorkflow() { /* {{{ */ $db = $this->_document->getDMS()->getDB(); $this->getWorkflow(); if (!isset($this->_workflow)) { return true; } $db->startTransaction(); $queryStr = "DELETE from `tblWorkflowLog` WHERE `document` = ". $this->_document->getID() ." AND `version` = ".$this->_version." AND `workflow` = ".$this->_workflow->getID(); if (!$db->getResult($queryStr)) { $db->rollbackTransaction(); return false; } $this->setWorkflowState($this->_workflow->getInitState()); $db->commitTransaction(); return true; } /* }}} */ /** * Remove workflow * * Fully removing a workflow including entries in the workflow log is * only allowed if the workflow is still its initial state. * At a later point of time only unlinking the document from the * workflow is allowed. It will keep any log entries. * A workflow is unlinked from a document when enterNextState() * succeeds. * * @param object $user user doing initiating the removal * @param boolean $unlink if true, just unlink the workflow from the * document but do not remove the workflow log. The $unlink * flag has been added to detach the workflow from the document * when it has reached a valid end state (see SeedDMS_Core_DocumentContent::enterNextState()) * @return boolean true if workflow could be removed * or false in case of error */ function removeWorkflow($user, $unlink=false) { /* {{{ */ $db = $this->_document->getDMS()->getDB(); $this->getWorkflow(); if (!isset($this->_workflow)) { return true; } if(SeedDMS_Core_DMS::checkIfEqual($this->_workflow->getInitState(), $this->getWorkflowState()) || $unlink == true) { $db->startTransaction(); $queryStr= "DELETE FROM `tblWorkflowDocumentContent` WHERE " ."`version`='".$this->_version."' " ." AND `document` = '". $this->_document->getID() ."' " ." AND `workflow` = '". $this->_workflow->getID() ."' "; if (!$db->getResult($queryStr)) { $db->rollbackTransaction(); return false; } if(!$unlink) { $queryStr= "DELETE FROM `tblWorkflowLog` WHERE " ."`version`='".$this->_version."' " ." AND `document` = '". $this->_document->getID() ."' " ." AND `workflow` = '". $this->_workflow->getID() ."' "; if (!$db->getResult($queryStr)) { $db->rollbackTransaction(); return false; } } $this->_workflow = null; $this->_workflowState = null; $this->verifyStatus(false, $user, 'Workflow removed'); $db->commitTransaction(); } return true; } /* }}} */ /** * Run a sub workflow * * @param object $subworkflow */ function getParentWorkflow() { /* {{{ */ $db = $this->_document->getDMS()->getDB(); /* document content must be in a workflow */ $this->getWorkflow(); if(!$this->_workflow) return false; $queryStr= "SELECT * FROM `tblWorkflowDocumentContent` WHERE " ."`version`='".$this->_version."' " ." AND `document` = '". $this->_document->getID() ."' " ." AND `workflow` = '". $this->_workflow->getID() ."' "; $recs = $db->getResultArray($queryStr); if (is_bool($recs) && !$recs) return false; if(!$recs) return false; if($recs[0]['parentworkflow']) return $this->_document->getDMS()->getWorkflow($recs[0]['parentworkflow']); return false; } /* }}} */ /** * Run a sub workflow * * @param object $subworkflow */ function runSubWorkflow($subworkflow) { /* {{{ */ $db = $this->_document->getDMS()->getDB(); /* document content must be in a workflow */ $this->getWorkflow(); if(!$this->_workflow) return false; /* The current workflow state must match the sub workflows initial state */ if($subworkflow->getInitState()->getID() != $this->_workflowState->getID()) return false; if($subworkflow) { $initstate = $subworkflow->getInitState(); $queryStr = "INSERT INTO `tblWorkflowDocumentContent` (`parentworkflow`, `workflow`, `document`, `version`, `state`, `date`) VALUES (". $this->_workflow->getID(). ", ". $subworkflow->getID(). ", ". $this->_document->getID() .", ". $this->_version .", ".$initstate->getID().", ".$db->getCurrentDatetime().")"; if (!$db->getResult($queryStr)) { return false; } $this->_workflow = $subworkflow; return true; } return true; } /* }}} */ /** * Return from sub workflow to parent workflow. * The method will trigger the given transition * * FIXME: Needs much better checking if this is allowed * * @param object $user intiating the return * @param object $transtion to trigger * @param string comment for the transition trigger */ function returnFromSubWorkflow($user, $transition=null, $comment='') { /* {{{ */ $db = $this->_document->getDMS()->getDB(); /* document content must be in a workflow */ $this->getWorkflow(); if(!$this->_workflow) return false; if (isset($this->_workflow)) { $db->startTransaction(); $queryStr= "SELECT * FROM `tblWorkflowDocumentContent` WHERE `workflow`=". intval($this->_workflow->getID()) . " AND `version`='".$this->_version ."' AND `document` = '". $this->_document->getID() ."' "; $recs = $db->getResultArray($queryStr); if (is_bool($recs) && !$recs) { $db->rollbackTransaction(); return false; } if(!$recs) { $db->rollbackTransaction(); return false; } $queryStr = "DELETE FROM `tblWorkflowDocumentContent` WHERE `workflow` =". intval($this->_workflow->getID())." AND `document` = '". $this->_document->getID() ."' AND `version` = '" . $this->_version."'"; if (!$db->getResult($queryStr)) { $db->rollbackTransaction(); return false; } $this->_workflow = $this->_document->getDMS()->getWorkflow($recs[0]['parentworkflow']); $this->_workflow->setDMS($this->_document->getDMS()); if($transition) { if(false === $this->triggerWorkflowTransition($user, $transition, $comment)) { $db->rollbackTransaction(); return false; } } $db->commitTransaction(); } return $this->_workflow; } /* }}} */ /** * Check if the user is allowed to trigger the transition * A user is allowed if either the user itself or * a group of which the user is a member of is registered for * triggering a transition. This method does not change the workflow * state of the document content. * * @param object $user * @return boolean true if user may trigger transaction */ function triggerWorkflowTransitionIsAllowed($user, $transition) { /* {{{ */ $db = $this->_document->getDMS()->getDB(); if(!$this->_workflow) $this->getWorkflow(); if(!$this->_workflow) return false; if(!$this->_workflowState) $this->getWorkflowState(); /* Check if the user has already triggered the transition */ $queryStr= "SELECT * FROM `tblWorkflowLog` WHERE `version`='".$this->_version ."' AND `document` = '". $this->_document->getID() ."' AND `workflow` = ". $this->_workflow->getID(). " AND userid = ".$user->getID(); $queryStr .= " AND `transition` = ".$transition->getID(); $resArr = $db->getResultArray($queryStr); if (is_bool($resArr) && !$resArr) return false; if(count($resArr)) return false; /* Get all transition users allowed to trigger the transition */ $transusers = $transition->getUsers(); if($transusers) { foreach($transusers as $transuser) { if($user->getID() == $transuser->getUser()->getID()) return true; } } /* Get all transition groups whose members are allowed to trigger * the transition */ $transgroups = $transition->getGroups(); if($transgroups) { foreach($transgroups as $transgroup) { $group = $transgroup->getGroup(); if($group->isMember($user)) return true; } } return false; } /* }}} */ /** * Check if all conditions are met to change the workflow state * of a document content (run the transition). * The conditions are met if all explicitly set users and a sufficient * number of users of the groups have acknowledged the content. * * @return boolean true if transaction maybe executed */ function executeWorkflowTransitionIsAllowed($transition) { /* {{{ */ if(!$this->_workflow) $this->getWorkflow(); if(!$this->_workflow) return false; if(!$this->_workflowState) $this->getWorkflowState(); /* Get the Log of transition triggers */ $entries = $this->getWorkflowLog($transition); if(!$entries) return false; /* Get all transition users allowed to trigger the transition * $allowedusers is a list of all users allowed to trigger the * transition */ $transusers = $transition->getUsers(); $allowedusers = array(); foreach($transusers as $transuser) { $a = $transuser->getUser(); $allowedusers[$a->getID()] = $a; } /* Get all transition groups whose members are allowed to trigger * the transition */ $transgroups = $transition->getGroups(); foreach($entries as $entry) { $loguser = $entry->getUser(); /* Unset each allowed user if it was found in the log */ if(isset($allowedusers[$loguser->getID()])) unset($allowedusers[$loguser->getID()]); /* Also check groups if required. Count the group membership of * each user in the log in the array $gg */ if($transgroups) { $loggroups = $loguser->getGroups(); foreach($loggroups as $loggroup) { if(!isset($gg[$loggroup->getID()])) $gg[$loggroup->getID()] = 1; else $gg[$loggroup->getID()]++; } } } /* If there are allowed users left, then there some users still * need to trigger the transition. */ if($allowedusers) return false; if($transgroups) { foreach($transgroups as $transgroup) { $group = $transgroup->getGroup(); $minusers = $transgroup->getNumOfUsers(); if(!isset($gg[$group->getID()])) return false; if($gg[$group->getID()] < $minusers) return false; } } return true; } /* }}} */ /** * Trigger transition * * This method will be deprecated * * The method will first check if the user is allowed to trigger the * transition. If the user is allowed, an entry in the workflow log * will be added, which is later used to check if the transition * can actually be processed. The method will finally call * executeWorkflowTransitionIsAllowed() which checks all log entries * and does the transitions post function if all users and groups have * triggered the transition. Finally enterNextState() is called which * will try to enter the next state. * * @param object $user * @param object $transition * @param string $comment user comment * @return boolean/object next state if transition could be triggered and * then next state could be entered, * true if the transition could just be triggered or * false in case of an error */ function triggerWorkflowTransition($user, $transition, $comment='') { /* {{{ */ $db = $this->_document->getDMS()->getDB(); if(!$this->_workflow) $this->getWorkflow(); if(!$this->_workflow) return false; if(!$this->_workflowState) $this->getWorkflowState(); if(!$this->_workflowState) return false; /* Check if the user is allowed to trigger the transition. */ if(!$this->triggerWorkflowTransitionIsAllowed($user, $transition)) return false; $state = $this->_workflowState; $queryStr = "INSERT INTO `tblWorkflowLog` (`document`, `version`, `workflow`, `userid`, `transition`, `date`, `comment`) VALUES (".$this->_document->getID().", ".$this->_version.", " . (int) $this->_workflow->getID() . ", " .(int) $user->getID(). ", ".(int) $transition->getID().", ".$db->getCurrentDatetime().", ".$db->qstr($comment).")"; if (!$db->getResult($queryStr)) return false; /* Check if this transition is processed. Run the post function in * that case. A transition is processed when all users and groups * have triggered it. */ if($this->executeWorkflowTransitionIsAllowed($transition)) { /* run post function of transition */ // echo "run post function of transition ".$transition->getID()."
"; } /* Go into the next state. This will only succeed if the pre condition * function of that states succeeds. */ $nextstate = $transition->getNextState(); if($this->enterNextState($user, $nextstate)) { return $nextstate; } return true; } /* }}} */ /** * Enter next state of workflow if possible * * The method will check if one of the following states in the workflow * can be reached. * It does it by running * the precondition function of that state. The precondition function * gets a list of all transitions leading to the state. It will * determine, whether the transitions has been triggered and if that * is sufficient to enter the next state. If no pre condition function * is set, then 1 of n transtions are enough to enter the next state. * * If moving in the next state is possible and this state has a * corresponding document state, then the document state will be * updated and the workflow will be detached from the document. * * @param object $user * @param object $nextstate * @return boolean true if the state could be reached * false if not */ function enterNextState($user, $nextstate) { /* {{{ */ /* run the pre condition of the next state. If it is not set * the next state will be reached if one of the transitions * leading to the given state can be processed. */ if($nextstate->getPreCondFunc() == '') { $transitions = $this->_workflow->getPreviousTransitions($nextstate); foreach($transitions as $transition) { // echo "transition ".$transition->getID()." led to state ".$nextstate->getName()."
"; if($this->executeWorkflowTransitionIsAllowed($transition)) { // echo "stepping into next state
"; $this->setWorkflowState($nextstate); /* Check if the new workflow state has a mapping into a * document state. If yes, set the document state will * be updated and the workflow will be removed from the * document. */ $docstate = $nextstate->getDocumentStatus(); if($docstate == S_RELEASED || $docstate == S_REJECTED) { $this->setStatus($docstate, "Workflow has ended", $user); /* Detach the workflow from the document, but keep the * workflow log */ $this->removeWorkflow($user, true); return true ; } /* make sure the users and groups allowed to trigger the next * transitions are also allowed to read the document */ $transitions = $this->_workflow->getNextTransitions($nextstate); foreach($transitions as $tran) { // echo "checking access for users/groups allowed to trigger transition ".$tran->getID()."
"; $transusers = $tran->getUsers(); foreach($transusers as $transuser) { $u = $transuser->getUser(); // echo $u->getFullName()."
"; if($this->_document->getAccessMode($u) < M_READ) { $this->_document->addAccess(M_READ, $u->getID(), 1); // echo "granted read access
"; } else { // echo "has already access
"; } } $transgroups = $tran->getGroups(); foreach($transgroups as $transgroup) { $g = $transgroup->getGroup(); // echo $g->getName()."
"; if ($this->_document->getGroupAccessMode($g) < M_READ) { $this->_document->addAccess(M_READ, $g->getID(), 0); // echo "granted read access
"; } else { // echo "has already access
"; } } } return(true); } else { // echo "transition not ready for process now
"; } } return false; } else { } } /* }}} */ /** * Get the so far logged operations on the document content within the * workflow * * @return array list of operations */ function getWorkflowLog($transition = null) { /* {{{ */ $db = $this->_document->getDMS()->getDB(); /* if(!$this->_workflow) $this->getWorkflow(); if(!$this->_workflow) return false; */ $queryStr= "SELECT * FROM `tblWorkflowLog` WHERE `version`='".$this->_version ."' AND `document` = '". $this->_document->getID() ."'"; // AND `workflow` = ". $this->_workflow->getID(); if($transition) $queryStr .= " AND `transition` = ".$transition->getID(); $queryStr .= " ORDER BY `date`"; $resArr = $db->getResultArray($queryStr); if (is_bool($resArr) && !$resArr) return false; $workflowlogs = array(); for ($i = 0; $i < count($resArr); $i++) { $workflow = $this->_document->getDMS()->getWorkflow($resArr[$i]["workflow"]); $workflowlog = new SeedDMS_Core_Workflow_Log($resArr[$i]["id"], $this->_document->getDMS()->getDocument($resArr[$i]["document"]), $resArr[$i]["version"], $workflow, $this->_document->getDMS()->getUser($resArr[$i]["userid"]), $workflow->getTransition($resArr[$i]["transition"]), $resArr[$i]["date"], $resArr[$i]["comment"]); $workflowlog->setDMS($this); $workflowlogs[$i] = $workflowlog; } return $workflowlogs; } /* }}} */ /** * Get the latest logged transition for the document content within the * workflow * * @return array list of operations */ function getLastWorkflowTransition() { /* {{{ */ $db = $this->_document->getDMS()->getDB(); if(!$this->_workflow) $this->getWorkflow(); if(!$this->_workflow) return false; $queryStr= "SELECT * FROM `tblWorkflowLog` WHERE `version`='".$this->_version ."' AND `document` = '". $this->_document->getID() ."' AND `workflow` = ". $this->_workflow->getID(); $queryStr .= " ORDER BY `id` DESC LIMIT 1"; $resArr = $db->getResultArray($queryStr); if (is_bool($resArr) && !$resArr) return false; $workflowlogs = array(); $i = 0; $workflowlog = new SeedDMS_Core_Workflow_Log($resArr[$i]["id"], $this->_document->getDMS()->getDocument($resArr[$i]["document"]), $resArr[$i]["version"], $this->_workflow, $this->_document->getDMS()->getUser($resArr[$i]["userid"]), $this->_workflow->getTransition($resArr[$i]["transition"]), $resArr[$i]["date"], $resArr[$i]["comment"]); $workflowlog->setDMS($this); return $workflowlog; } /* }}} */ /** * Check if the document content needs an action by a user * * This method will return true if document content is in a transition * which can be triggered by the given user. * * @param SeedDMS_Core_User $user * @return boolean true is action is needed */ function needsWorkflowAction($user) { /* {{{ */ $needwkflaction = false; if($this->_workflow) { if (!$this->_workflowState) $this->getWorkflowState(); $workflowstate = $this->_workflowState; if($transitions = $this->_workflow->getNextTransitions($workflowstate)) { foreach($transitions as $transition) { if($this->triggerWorkflowTransitionIsAllowed($user, $transition)) { $needwkflaction = true; } } } } return $needwkflaction; } /* }}} */ /** * Checks the internal data of the document version and repairs it. * Currently, this function only repairs a missing filetype * * @return boolean true on success, otherwise false */ function repair() { /* {{{ */ $dms = $this->_document->getDMS(); $db = $this->_dms->getDB(); if(file_exists($this->_dms->contentDir.$this->_document->getDir() . $this->_version . $this->_fileType)) { if(strlen($this->_fileType) < 2) { switch($this->_mimeType) { case "application/pdf": case "image/png": case "image/gif": case "image/jpg": $expect = substr($this->_mimeType, -3, 3); if($this->_fileType != '.'.$expect) { $db->startTransaction(); $queryStr = "UPDATE `tblDocumentContent` SET `fileType`='.".$expect."' WHERE `id` = ". $this->_id; $res = $db->getResult($queryStr); if ($res) { if(!SeedDMS_Core_File::renameFile($this->_dms->contentDir.$this->_document->getDir() . $this->_version . $this->_fileType, $this->_dms->contentDir.$this->_document->getDir() . $this->_version . '.' . $expect)) { $db->rollbackTransaction(); } else { $db->commitTransaction(); } } else { $db->rollbackTransaction(); } } break; } } } elseif(file_exists($this->_document->getDir() . $this->_version . '.')) { echo "no file"; } else { echo $this->_dms->contentDir.$this->_document->getDir() . $this->_version . $this->_fileType; } return true; } /* }}} */ } /* }}} */ /** * Class to represent a link between two document * * Document links are to establish a reference from one document to * another document. The owner of the document link may not be the same * as the owner of one of the documents. * Use {@link SeedDMS_Core_Document::addDocumentLink()} to add a reference * to another document. * * @category DMS * @package SeedDMS_Core * @author Markus Westphal, Malcolm Cowe, Matteo Lucarelli, * Uwe Steinmann * @copyright Copyright (C) 2002-2005 Markus Westphal, * 2006-2008 Malcolm Cowe, 2010 Matteo Lucarelli, * 2010 Uwe Steinmann * @version Release: @package_version@ */ class SeedDMS_Core_DocumentLink { /* {{{ */ /** * @var integer internal id of document link */ protected $_id; /** * @var SeedDMS_Core_Document reference to document this link belongs to */ protected $_document; /** * @var object reference to target document this link points to */ protected $_target; /** * @var integer id of user who is the owner of this link */ protected $_userID; /** * @var integer 1 if this link is public, or 0 if is only visible to the owner */ protected $_public; /** * SeedDMS_Core_DocumentLink constructor. * @param $id * @param $document * @param $target * @param $userID * @param $public */ function __construct($id, $document, $target, $userID, $public) { $this->_id = $id; $this->_document = $document; $this->_target = $target; $this->_userID = $userID; $this->_public = $public; } /** * @return int */ function getID() { return $this->_id; } /** * @return SeedDMS_Core_Document */ function getDocument() { return $this->_document; } /** * @return object */ function getTarget() { return $this->_target; } /** * @return bool|SeedDMS_Core_User */ function getUser() { if (!isset($this->_user)) { $this->_user = $this->_document->getDMS()->getUser($this->_userID); } return $this->_user; } /** * @return int */ function isPublic() { return $this->_public; } /** * Returns the access mode similar to a document * * There is no real access mode for document links, so this is just * another way to add more access restrictions than the default restrictions. * It is only called for public document links, not accessed by the owner * or the administrator. * * @param SeedDMS_Core_User $u user * @param $source * @param $target * @return int either M_NONE or M_READ */ function getAccessMode($u, $source, $target) { /* {{{ */ $dms = $this->_document->getDMS(); /* Check if 'onCheckAccessDocumentLink' callback is set */ if(isset($dms->callbacks['onCheckAccessDocumentLink'])) { foreach($dms->callbacks['onCheckAccessDocumentLink'] as $callback) { if(($ret = call_user_func($callback[0], $callback[1], $this, $u, $source, $target)) > 0) { return $ret; } } } return M_READ; } /* }}} */ } /* }}} */ /** * Class to represent a file attached to a document * * Beside the regular document content arbitrary files can be attached * to a document. This is a similar concept as attaching files to emails. * The owner of the attached file and the document may not be the same. * Use {@link SeedDMS_Core_Document::addDocumentFile()} to attach a file. * * @category DMS * @package SeedDMS_Core * @author Markus Westphal, Malcolm Cowe, Matteo Lucarelli, * Uwe Steinmann * @copyright Copyright (C) 2002-2005 Markus Westphal, * 2006-2008 Malcolm Cowe, 2010 Matteo Lucarelli, * 2010 Uwe Steinmann * @version Release: @package_version@ */ class SeedDMS_Core_DocumentFile { /* {{{ */ /** * @var integer internal id of document file */ protected $_id; /** * @var SeedDMS_Core_Document reference to document this file belongs to */ protected $_document; /** * @var integer id of user who is the owner of this link */ protected $_userID; /** * @var string comment for the attached file */ protected $_comment; /** * @var string date when the file was attached */ protected $_date; /** * @var integer version of document this file is attached to */ protected $_version; /** * @var integer 1 if this link is public, or 0 if is only visible to the owner */ protected $_public; /** * @var string directory where the file is stored. This is the * document id with a proceding '/'. * FIXME: looks like this isn't used anymore. The file path is * constructed by getPath() */ protected $_dir; /** * @var string extension of the original file name with a leading '.' */ protected $_fileType; /** * @var string mime type of the file */ protected $_mimeType; /** * @var string name of the file that was originally uploaded */ protected $_orgFileName; /** * @var string name of the file as given by the user */ protected $_name; /** * SeedDMS_Core_DocumentFile constructor. * @param $id * @param $document * @param $userID * @param $comment * @param $date * @param $dir * @param $fileType * @param $mimeType * @param $orgFileName * @param $name * @param $version * @param $public */ function __construct($id, $document, $userID, $comment, $date, $dir, $fileType, $mimeType, $orgFileName,$name,$version,$public) { $this->_id = $id; $this->_document = $document; $this->_userID = $userID; $this->_comment = $comment; $this->_date = $date; $this->_dir = $dir; $this->_fileType = $fileType; $this->_mimeType = $mimeType; $this->_orgFileName = $orgFileName; $this->_name = $name; $this->_version = $version; $this->_public = $public; } /** * @return int */ function getID() { return $this->_id; } /** * @return SeedDMS_Core_Document */ function getDocument() { return $this->_document; } /** * @return int */ function getUserID() { return $this->_userID; } /** * @return string */ function getComment() { return $this->_comment; } /* * Set the comment of the document file * * @param string $newComment string new comment of document */ function setComment($newComment) { /* {{{ */ $db = $this->_document->getDMS()->getDB(); $queryStr = "UPDATE `tblDocumentFiles` SET `comment` = ".$db->qstr($newComment)." WHERE `document` = ".$this->_document->getId()." AND `id` = ". $this->_id; if (!$db->getResult($queryStr)) return false; $this->_comment = $newComment; return true; } /* }}} */ /** * @return string */ function getDate() { return $this->_date; } /** * Set creation date of the document file * * @param integer $date timestamp of creation date. If false then set it * to the current timestamp * @return boolean true on success */ function setDate($date) { /* {{{ */ $db = $this->_document->getDMS()->getDB(); if(!$date) $date = time(); else { if(!is_numeric($date)) return false; } $queryStr = "UPDATE `tblDocumentFiles` SET `date` = " . (int) $date . " WHERE `id` = ". $this->_id; if (!$db->getResult($queryStr)) return false; $this->_date = $date; return true; } /* }}} */ /** * @return string */ function getDir() { return $this->_dir; } /** * @return string */ function getFileType() { return $this->_fileType; } /** * @return string */ function getMimeType() { return $this->_mimeType; } /** * @return string */ function getOriginalFileName() { return $this->_orgFileName; } /** * @return string */ function getName() { return $this->_name; } /* * Set the name of the document file * * @param $newComment string new name of document */ function setName($newName) { /* {{{ */ $db = $this->_document->getDMS()->getDB(); $queryStr = "UPDATE `tblDocumentFiles` SET `name` = ".$db->qstr($newName)." WHERE `document` = ".$this->_document->getId()." AND `id` = ". $this->_id; if (!$db->getResult($queryStr)) return false; $this->_name = $newName; return true; } /* }}} */ /** * @return bool|SeedDMS_Core_User */ function getUser() { if (!isset($this->_user)) $this->_user = $this->_document->getDMS()->getUser($this->_userID); return $this->_user; } /** * @return string */ function getPath() { return $this->_document->getDir() . "f" .$this->_id . $this->_fileType; } /** * @return int */ function getVersion() { return $this->_version; } /* * Set the version of the document file * * @param $newComment string new version of document */ function setVersion($newVersion) { /* {{{ */ $db = $this->_document->getDMS()->getDB(); if(!is_numeric($newVersion) && $newVersion != '') return false; $queryStr = "UPDATE `tblDocumentFiles` SET `version` = ".(int) $newVersion." WHERE `document` = ".$this->_document->getId()." AND `id` = ". $this->_id; if (!$db->getResult($queryStr)) return false; $this->_version = (int) $newVersion; return true; } /* }}} */ /** * @return int */ function isPublic() { return $this->_public; } /* * Set the public flag of the document file * * @param $newComment string new comment of document */ function setPublic($newPublic) { /* {{{ */ $db = $this->_document->getDMS()->getDB(); $queryStr = "UPDATE `tblDocumentFiles` SET `public` = ".($newPublic ? 1 : 0)." WHERE `document` = ".$this->_document->getId()." AND `id` = ". $this->_id; if (!$db->getResult($queryStr)) return false; $this->_public = $newPublic ? 1 : 0; return true; } /* }}} */ /** * Returns the access mode similar to a document * * There is no real access mode for document files, so this is just * another way to add more access restrictions than the default restrictions. * It is only called for public document files, not accessed by the owner * or the administrator. * * @param object $u user * @return integer either M_NONE or M_READ */ function getAccessMode($u) { /* {{{ */ $dms = $this->_document->getDMS(); /* Check if 'onCheckAccessDocumentLink' callback is set */ if(isset($this->_dms->callbacks['onCheckAccessDocumentFile'])) { foreach($this->_dms->callbacks['onCheckAccessDocumentFile'] as $callback) { if(($ret = call_user_func($callback[0], $callback[1], $this, $u)) > 0) { return $ret; } } } return M_READ; } /* }}} */ } /* }}} */ // // Perhaps not the cleanest object ever devised, it exists to encapsulate all // of the data generated during the addition of new content to the database. // The object stores a copy of the new DocumentContent object, the newly assigned // reviewers and approvers and the status. // /** * Class to represent a list of document contents * * @category DMS * @package SeedDMS_Core * @author Markus Westphal, Malcolm Cowe, Matteo Lucarelli, * Uwe Steinmann * @copyright Copyright (C) 2002-2005 Markus Westphal, * 2006-2008 Malcolm Cowe, 2010 Matteo Lucarelli, * 2010 Uwe Steinmann * @version Release: @package_version@ */ class SeedDMS_Core_AddContentResultSet { /* {{{ */ /** * @var null */ protected $_indReviewers; /** * @var null */ protected $_grpReviewers; /** * @var null */ protected $_indApprovers; /** * @var null */ protected $_grpApprovers; /** * @var */ protected $_content; /** * @var null */ protected $_status; /** * @var SeedDMS_Core_DMS back reference to document management system */ protected $_dms; /** * SeedDMS_Core_AddContentResultSet constructor. * @param $content */ function __construct($content) { /* {{{ */ $this->_content = $content; $this->_indReviewers = null; $this->_grpReviewers = null; $this->_indApprovers = null; $this->_grpApprovers = null; $this->_status = null; $this->_dms = null; } /* }}} */ /** * Set dms this object belongs to. * * Each object needs a reference to the dms it belongs to. It will be * set when the object is created. * The dms has a references to the currently logged in user * and the database connection. * * @param SeedDMS_Core_DMS $dms reference to dms */ function setDMS($dms) { /* {{{ */ $this->_dms = $dms; } /* }}} */ /** * @param $reviewer * @param $type * @param $status * @return bool */ function addReviewer($reviewer, $type, $status) { /* {{{ */ $dms = $this->_dms; if (!is_object($reviewer) || (strcasecmp($type, "i") && strcasecmp($type, "g")) && !is_integer($status)){ return false; } if (!strcasecmp($type, "i")) { if (strcasecmp(get_class($reviewer), $dms->getClassname("user"))) { return false; } if ($this->_indReviewers == null) { $this->_indReviewers = array(); } $this->_indReviewers[$status][] = $reviewer; } if (!strcasecmp($type, "g")) { if (strcasecmp(get_class($reviewer), $dms->getClassname("group"))) { return false; } if ($this->_grpReviewers == null) { $this->_grpReviewers = array(); } $this->_grpReviewers[$status][] = $reviewer; } return true; } /* }}} */ /** * @param $approver * @param $type * @param $status * @return bool */ function addApprover($approver, $type, $status) { /* {{{ */ $dms = $this->_dms; if (!is_object($approver) || (strcasecmp($type, "i") && strcasecmp($type, "g")) && !is_integer($status)){ return false; } if (!strcasecmp($type, "i")) { if (strcasecmp(get_class($approver), $dms->getClassname("user"))) { return false; } if ($this->_indApprovers == null) { $this->_indApprovers = array(); } $this->_indApprovers[$status][] = $approver; } if (!strcasecmp($type, "g")) { if (strcasecmp(get_class($approver), $dms->getClassname("group"))) { return false; } if ($this->_grpApprovers == null) { $this->_grpApprovers = array(); } $this->_grpApprovers[$status][] = $approver; } return true; } /* }}} */ /** * @param $status * @return bool */ function setStatus($status) { /* {{{ */ if (!is_integer($status)) { return false; } if ($status<-3 || $status>3) { return false; } $this->_status = $status; return true; } /* }}} */ /** * @return null */ function getStatus() { /* {{{ */ return $this->_status; } /* }}} */ /** * @return mixed */ function getContent() { /* {{{ */ return $this->_content; } /* }}} */ /** * @param $type * @return array|bool|null */ function getReviewers($type) { /* {{{ */ if (strcasecmp($type, "i") && strcasecmp($type, "g")) { return false; } if (!strcasecmp($type, "i")) { return ($this->_indReviewers == null ? array() : $this->_indReviewers); } else { return ($this->_grpReviewers == null ? array() : $this->_grpReviewers); } } /* }}} */ /** * @param $type * @return array|bool|null */ function getApprovers($type) { /* {{{ */ if (strcasecmp($type, "i") && strcasecmp($type, "g")) { return false; } if (!strcasecmp($type, "i")) { return ($this->_indApprovers == null ? array() : $this->_indApprovers); } else { return ($this->_grpApprovers == null ? array() : $this->_grpApprovers); } } /* }}} */ } /* }}} */