diff --git a/lib/api/ktcmis/ktNavigationService.inc.php b/lib/api/ktcmis/ktNavigationService.inc.php index 5ddb97c..15e2575 100644 --- a/lib/api/ktcmis/ktNavigationService.inc.php +++ b/lib/api/ktcmis/ktNavigationService.inc.php @@ -137,7 +137,7 @@ class KTNavigationService extends KTCMISBase { { return array( "status_code" => 1, - "message" => "Failed getting descendants for folder" + "message" => "Failed getting children for folder" ); } @@ -154,38 +154,32 @@ class KTNavigationService extends KTCMISBase { * * @param string $repositoryId * @param string $folderId - * @param boolean $includeAllowableActions - * @param boolean $includeRelationships - * @param boolean $returnToRoot * @param string $filter - * @return ancestry[] + * @return parent[] */ - public function getFolderParent($repositoryId, $folderId, $includeAllowableActions, $includeRelationships, $returnToRoot, $filter = '') + public function getFolderParent($repositoryId, $folderId, $filter = '') { try { - $ancestryResult = $this->NavigationService->getFolderParent($repositoryId, $folderId, $includeAllowableActions, - $includeRelationships, $returnToRoot); + $parent = $this->NavigationService->getFolderParent($repositoryId, $folderId, $filter); } catch (Exception $e) { return array( "status_code" => 1, - "message" => "Failed getting ancestry for folder: " . $e->getMessage() + "message" => "Failed getting folder parent: " . $e->getMessage() ); } - if (PEAR::isError($ancestryResult)) + if (PEAR::isError($parent)) { return array( "status_code" => 1, - "message" => "Failed getting ancestry for folder" + "message" => "Failed getting folder parent" ); } - $ancestry = CMISUtil::decodeObjectHierarchy($ancestryResult, 'children'); - return array( "status_code" => 0, - "results" => $ancestry + "results" => CMISUtil::createObjectPropertiesEntry($parent->getProperties()) ); } @@ -225,18 +219,27 @@ class KTNavigationService extends KTCMISBase { * * @param string $repositoryId * @param string $folderId The folder for which checked out docs are requested - * @param string $filter - * @param boolean $includeAllowableActions - * @param boolean $includeRelationships * @param int $maxItems * @param int $skipCount - * @return array $checkedout The collection of checked out documents + * @param string $filter + * @param enum $includeRelationships + * @param boolean $includeAllowableActions + * @param string $renditionFilter + * @return array $checkedout The collection of checked out document objects + * MUST include (unless not requested) for each object: + * array $properties + * array $relationships + * array $renditions + * $allowableActions + * @return boolean $hasMoreItems + * @return int $numItems [optional] */ - function getCheckedOutDocs($repositoryId, $includeAllowableActions, $includeRelationships, $folderId = null, $filter = '', - $maxItems = 0, $skipCount = 0) + function getCheckedOutDocs($repositoryId, $folderId = null, $maxItems = 0, $skipCount = 0, $orderBy = '', + $filter = '', $includeRelationships = null, $includeAllowableActions = false, $renditionFilter = '') { - $checkedout = $this->NavigationService->getCheckedOutDocs($repositoryId, $includeAllowableActions, $includeRelationships, - $folderId, $filter, $maxItems, $skipCount); + $checkedout = $this->NavigationService->getCheckedOutDocs($repositoryId, $folderId = null, $maxItems = 0, $skipCount = 0, + $orderBy, $filter, $includeRelationships, $includeAllowableActions, + $renditionFilter); if (PEAR::isError($checkedout)) { diff --git a/lib/api/ktcmis/ktService.inc.php b/lib/api/ktcmis/ktService.inc.php index fab6e86..f804957 100644 --- a/lib/api/ktcmis/ktService.inc.php +++ b/lib/api/ktcmis/ktService.inc.php @@ -48,7 +48,6 @@ require_once(realpath(dirname(__FILE__) . '/../../../config/dmsDefaults.php')); require_once(KT_DIR . '/ktapi/ktapi.inc.php'); define ('CMIS_DIR', KT_LIB_DIR . '/api/ktcmis'); -require_once(CMIS_DIR . '/exceptions/PermissionDeniedException.inc.php'); require_once(CMIS_DIR . '/util/CMISUtil.inc.php'); /** diff --git a/lib/api/ktcmis/services/CMISNavigationService.inc.php b/lib/api/ktcmis/services/CMISNavigationService.inc.php index aaaf406..394da51 100644 --- a/lib/api/ktcmis/services/CMISNavigationService.inc.php +++ b/lib/api/ktcmis/services/CMISNavigationService.inc.php @@ -87,8 +87,8 @@ class CMISNavigationService { // Otherwise, the latest version of the documents SHALL be returned. // TODO FilterNotValidException: The Repository SHALL throw this exception if this property filter input parameter is not valid function getChildren($repositoryId, $folderId, $includeAllowableActions = null, $includeRelationships = null, - $typeId = 'Any', $filter = '', $maxItems = 0, $skipCount = 0, $orderBy = '', $renditionFilter = null, - $includePathSegment = false) + $typeId = 'Any', $filter = '', $maxItems = 0, $skipCount = 0, $orderBy = '', $renditionFilter = null, + $includePathSegment = false) { // TODO paging // TODO optional parameters @@ -134,13 +134,13 @@ class CMISNavigationService { // NOTE If the Repository supports the optional “VersionSpecificFiling��? capability, // then the repository SHALL return the document versions filed in the specified folder or its descendant folders. // Otherwise, the latest version of the documents SHALL be returned. - // NOTE If the Repository supports the optional capability capabilityMutlifiling and the same document is encountered + // NOTE If the Repository supports the optional capability capabilityMutlifiling and the same document is encountered // multiple times in the hierarchy, then the repository MUST return that document each time is encountered. // NOTE The default value for the $depth parameter is repository specific and SHOULD be at least 2 or -1 // Chosen 2 as the underlying code currently has no concept of digging all the way down // TODO FilterNotValidException: The Repository SHALL throw this exception if this property filter input parameter is not valid - function getDescendants($repositoryId, $folderId, $depth = 2, $filter = '', $includeRelationships = false, $renditionFilter = '', - $includeAllowableActions = false, $includePathSegment = false) + function getDescendants($repositoryId, $folderId, $depth = 2, $filter = '', $includeRelationships = false, $renditionFilter = '', + $includeAllowableActions = false, $includePathSegment = false) { if ($depth == 0) { throw new InvalidArgumentException('Invalid depth argument supplied'); @@ -148,11 +148,11 @@ class CMISNavigationService { // if this is not a folder, cannot get descendants $folderId = CMISUtil::decodeObjectId($folderId, $type); - + if ($type != 'cmis:folder') { throw new InvalidArgumentException('The supplied object is not a folder, unable to return descendants'); } - + // TODO optional parameters $descendants = array(); $repository = new CMISRepository($repositoryId); @@ -171,65 +171,35 @@ class CMISNavigationService { * * @param string $repositoryId * @param string $folderId - * @param boolean $includeAllowableActions - * @param boolean $includeRelationships - * @param boolean $returnToRoot If TRUE, then the repository SHALL return all folder objects - * that are ancestors of the specified folder. - * If FALSE, the repository SHALL return only the parent folder of the specified folder. * @param string $filter - * @return array $ancestry + * @return object $parent The parent folder object */ // TODO FilterNotValidException: The Repository SHALL throw this exception if this property filter input parameter is not valid - // TODO If this service method is invoked on the root folder of the Repository, then the Repository SHALL return an empty result set. - // NOTE SHOULD always include the “ObjectId��? and “ParentId��? properties for all objects returned - function getFolderParent($repositoryId, $folderId, $includeAllowableActions, $includeRelationships, $returnToRoot, $filter = '') + function getFolderParent($repositoryId, $folderId, $filter = '') { // NOTE the root folder obviously has no parent, throw an ObjectNotFoundException here if this is the root folder if (CMISUtil::isRootFolder($repositoryId, $folderId, $this->ktapi)) { - throw new ObjectNotFoundException('Root folder has no parent'); + throw new InvalidArgumentException('Root folder has no parent'); } - - $ancestry = array(); - $repository = new CMISRepository($repositoryId); + + $parent = null; // if this is not a folder, cannot get folder parent :) $folderId = CMISUtil::decodeObjectId($folderId, $type); - // NOTE this will quite possibly break the webservices - if ($type != 'cmis:folder') - { - return $ancestry; + // this exception is not indicated in the CMIS Specification, but it just makes sense and so we include it here + if ($type != 'cmis:folder') { + throw new InvalidArgumentException('The specified object is not a folder'); } $ktapiFolder = $this->ktapi->get_folder_by_id($folderId); - - if ($returnToRoot) - { - $folder = $ktapiFolder->get_folder(); - $parents = $folder->generateFolderIDs($folderId); - // remove the id of the requesting folder and convert to array - $ancestry = explode(',', str_replace(','.$folderId, '', $parents)); - // reverse to get bottom up listing? don't think so with the current implementation - // specifying that objectTypes may have children but do not have parents listed. -// $ancestry = array_reverse($ancestry); - } - else - { - $parent = $ktapiFolder->get_parent_folder_id(); - $ancestry[] = $parent; - } - - // need some info about the parent(s) in order to correctly create the hierarchy - $tmpArray = array(); - foreach ($ancestry as $key => $ancestor) - { - $tmpArray[$key] = $this->ktapi->get_folder_by_id($ancestor); + if (PEAR::isError($ktapiFolder)) { + throw new RuntimeException($ktapiFolder->getMessage()); } - $ancestry = $tmpArray; - unset($tmpArray); - $ancestry = CMISUtil::createParentObjectHierarchy($ancestry, $repository->getRepositoryURI, $this->ktapi); - - return $ancestry; + $parentId = $ktapiFolder->get_parent_folder_id(); + $parent = new CMISFolderObject(CMISUtil::encodeObjectId($parentId, FOLDER), $this->ktapi); + + return $parent; } /** @@ -279,18 +249,25 @@ class CMISNavigationService { * * @param string $repositoryId * @param string $folderId The folder for which checked out docs are requested - * @param string $filter - * @param boolean $includeAllowableActions - * @param boolean $includeRelationships * @param int $maxItems * @param int $skipCount + * @param string $filter + * @param enum $includeRelationships + * @param boolean $includeAllowableActions + * @param string $renditionFilter * @return array $checkedout The collection of checked out document objects + * MUST include (unless not requested) for each object: + * array $properties + * array $relationships + * array $renditions + * $allowableActions + * @return boolean $hasMoreItems + * @return int $numItems [optional] */ // TODO exceptions: • FilterNotValidException: The Repository SHALL throw this exception if this property filter input parameter is not valid. - // TODO filter by folder id // TODO $filter and paging - function getCheckedOutDocs($repositoryId, $folderId = null, $filter = '', $includeAllowableActions = false, $includeRelationships = null, - $maxItems = 0, $skipCount = 0, $orderBy = '') + function getCheckedOutDocs($repositoryId, $folderId = null, $maxItems = 0, $skipCount = 0, $orderBy = '', + $filter = '', $includeRelationships = null, $includeAllowableActions = false, $renditionFilter = '') { $checkedout = array(); diff --git a/lib/api/ktcmis/util/CMISUtil.inc.php b/lib/api/ktcmis/util/CMISUtil.inc.php index e3e977c..f556fd5 100644 --- a/lib/api/ktcmis/util/CMISUtil.inc.php +++ b/lib/api/ktcmis/util/CMISUtil.inc.php @@ -104,7 +104,7 @@ class CMISUtil { $typeId = 'unknown'; return null; } - + $typeId = null; // NOTE Not sure whether this really belongs here, but probably this is the safest and most reliable place @@ -124,6 +124,7 @@ class CMISUtil { // meantime this minor hack will get things working for the existing system structure, as the root // folder should always be id 1. $typeId = 'cmis:folder'; + $className = 'Folder'; return '1'; } @@ -180,9 +181,9 @@ class CMISUtil { $CMISObject = new CMISFolderObject($object['id'], $ktapi, $repositoryURI); break; } - + $CMISArray[$count]['object'] = $CMISObject; - + // if sub-array if (count($object['items']) > 0) { $CMISArray[$count]['children'] = self::createChildObjectHierarchy($object['items'], $repositoryURI, $ktapi); @@ -192,7 +193,7 @@ class CMISUtil { { // NOTE why is this necessary? That's what you get for not commenting it at the time // TODO comment this properly, once we know why it is happening -// $CMISArray[$count] = self::createChildObjectHierarchy($object, $repositoryURI, $ktapi); + // $CMISArray[$count] = self::createChildObjectHierarchy($object, $repositoryURI, $ktapi); } } } @@ -251,14 +252,14 @@ class CMISUtil { static public function decodeObjectHierarchy($input, $linkText = 'children') { $hierarchy = array(); - + // first, run through the base array to get the initial children foreach ($input as $key => $entry) { $object = $entry['object']; $properties = $object->getProperties(); $hierarchy[$key] = self::createObjectPropertiesEntry($properties); - + if (isset($entry[$linkText]) && count($entry[$linkText])) { $hierarchy[$key][$linkText] = self::decodeObjectHierarchy($entry[$linkText], $linkText); } @@ -279,7 +280,7 @@ class CMISUtil { static public function createObjectPropertiesEntry($properties) { $object = array(); - + foreach(CMISPropertyCollection::$propertyTypes as $property => $type) { // hack for Author property @@ -316,9 +317,9 @@ class CMISUtil { // clean up ", )" - NOTE this may not be necessary, but is included for safety $stringdata = preg_replace('/, *\r?\n? *\)/', ')', $stringdata); - // NOTE is this while loop even needed? - while (preg_match('/\b[\w]*::__set_state\(/', $stringdata, $matches)) - { + // NOTE is this while loop even needed, or can we just run a preg_replace without the while? + // TODO find out... + while (preg_match('/\b[\w]*::__set_state\(/', $stringdata, $matches)) { $stringdata = preg_replace('/\b[\w]*::__set_state\(/', $matches[1], $stringdata); } @@ -342,7 +343,7 @@ class CMISUtil { } // TODO more robust base64 encoding detection, if possible - + /** * Checks the contentStream and ensures that it is a correct base64 string; * This is purely for clients such as CMISSpaces breaking the content into @@ -363,23 +364,23 @@ class CMISUtil { { // always trim content, just in case, as the AtomPub specification says content may be padded with whitespace at the start and end. $contentStream = trim($contentStream); - + // check whether the content is encoded first, return as is if not // A–Z, a–z, 0–9, +, / // NOTE this makes the (fairly reasonable) assumption that text content contains at least one space or punctuation character. // of course this may fail should something be sent in plain text such as a passwords file containing sha1 or md5 hashes only. if (preg_match('/[^\w\/\+=\n]+/', $content)) return $contentStream; - + $decoded = ''; - + // split the content stream on ={1,2} $parts = preg_split('/(={1,2})/', $contentStream, null, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE); foreach($parts as $part) - { + { if (preg_match('/={1,2}/', $part)) { continue; } - + // lookahead for delimiter, because we may need it back. // NOTE that decoding appears to work fine without this, so this is just an "in case". // NOTE that even with this it seems the present function works faster than the alternative below. @@ -388,14 +389,14 @@ class CMISUtil { $part .= $parts[$key+1]; } } - + // decode, append to output $decoded .= base64_decode($part); } return $decoded; } - + /** * Checks the contentStream and ensures that it is a correct base64 string; * This is purely for clients such as CMISSpaces breaking the content into @@ -405,7 +406,7 @@ class CMISUtil { * If it is not chunked it is decoded as is and sent back as a single stream. * * NOTE this function and the above need to be checked for efficiency. - * The current one appears to be miles better (1/0/3 vs 14/4/57 on respective test files) + * The other one appears to be miles better (1/0/3 vs 14/4/57 on respective test files) * * @param object $contentStream * @return string decoded @@ -414,13 +415,13 @@ class CMISUtil { { // always trim content, just in case, as the AtomPub specification says content may be padded with whitespace at the start and end. $contentStream = trim($contentStream); - + // check whether the content is encoded first, return as is if not // A–Z, a–z, 0–9, +, / // NOTE this makes the (fairly reasonable) assumption that text content contains at least one space or punctuation character. // of course this may fail should something be sent in plain text such as a passwords file containing sha1 or md5 hashes only. if (preg_match('/[^\w\/\+=\n]+/', $content)) return $contentStream; - + // check the content stream for any lines of unusual length (except the last line, which can be any length) $count = -1; $length = 0; @@ -435,11 +436,11 @@ class CMISUtil { foreach ($splitStream as $line) { $curlen = strlen($line); - + if ($length == 0) { $length = $curlen; } - + // if we find one we know that we must split the line here and end the previous base64 string if ($curlen > $length) { @@ -457,7 +458,7 @@ class CMISUtil { } $decode[++$count] = $b64String . $lastChunk; - + $b64String = $nextChunk . "\n"; $length = strlen($nextChunk); @@ -488,7 +489,7 @@ class CMISUtil { return $decoded; } - + /** * Function to check whether a specified object exists within the KnowledgeTree system * @@ -518,10 +519,10 @@ class CMISUtil { else { $exists = false; } - + return $exists; } - + /** * Creates a temporary file * Cleanup is the responsibility of the calling code @@ -533,22 +534,22 @@ class CMISUtil { { // if contentStream is empty, cannot create file if (empty($contentStream)) return null; - + // TODO consider checking whether content is encoded (currently we expect encoded) // TODO choose between this and the alternative decode function (see CMISUtil class) // this will require some basic benchmarking $contentStream = self::decodeChunkedContentStream($contentStream); - + // NOTE There is a function in CMISUtil to do this, written for the unit tests but since KTUploadManager exists // and has more functionality which could come in useful at some point I decided to go with that instead // (did not know this existed when I wrote the CMISUtil function) $uploadManager = new KTUploadManager(); // assumes already decoded from base64, should use store_base64_file if not $tempfilename = $uploadManager->store_file($contentStream, 'cmis_'); - + return $tempfilename; } - + /** * attempts to fetch the folder id from a name * @@ -562,10 +563,10 @@ class CMISUtil { static public function getIdFromName($name, &$ktapi) { $folder = $ktapi->get_folder_by_name($name); - + return self::encodeObjectId(FOLDER, $folder->get_folderid()); } - + /** * Checks for the root folder * @@ -578,12 +579,12 @@ class CMISUtil { { $repository = new CMISRepository($repositoryId); $repositoryInfo = $repository->getRepositoryInfo(); - + // NOTE this call is required to accomodate the definition of the root folder id in the config as required by the drupal module // we should try to update the drupal module to not require this, but this way is just easier at the moment, and most of // the code accomodates it without any serious hacks $rootFolder = self::getIdFromName($repositoryInfo->getRootFolderId(), $ktapi); - + return $folderId == $rootFolder; }