diff --git a/ktapi/ktapi.inc.php b/ktapi/ktapi.inc.php index 13487c4..c911c0c 100644 --- a/ktapi/ktapi.inc.php +++ b/ktapi/ktapi.inc.php @@ -3254,6 +3254,8 @@ class KTAPI * @param boolean $userSpecific limit to current user * @return $checkedout An array of checked out documents */ + // TODO determine whether the listing is showing docs the user should not be able to see + // (when not restricting to docs checked out by that user) public function get_checkedout_docs($userSpecific = true) { $checkedout = array(); diff --git a/lib/api/ktcmis/classes/CMISDocumentPropertyCollection.inc.php b/lib/api/ktcmis/classes/CMISDocumentPropertyCollection.inc.php index 8d30e13..a35bda3 100644 --- a/lib/api/ktcmis/classes/CMISDocumentPropertyCollection.inc.php +++ b/lib/api/ktcmis/classes/CMISDocumentPropertyCollection.inc.php @@ -66,6 +66,14 @@ class CMISDocumentPropertyCollection extends CMISPropertyCollection { function __construct() { parent::__construct(); + self::$propertyTypes = array_merge(self::$propertyTypes, array('ContentStreamAllowed' => 'propertyString', + 'ContentStreamLength' => 'propertyInteger', + 'ContentStreamMimeType' => 'propertyString', + 'ContentStreamFilename' => 'propertyString', + 'ContentStreamUri' => 'propertyUri', + 'IsVersionSeriesCheckedOut' => 'propertyBoolean', + 'VersionSeriesCheckedOutBy' => 'propertyString', + 'VersionSeriesCheckedOutId' => 'propertyId')); } } diff --git a/lib/api/ktcmis/classes/CMISPropertyCollection.inc.php b/lib/api/ktcmis/classes/CMISPropertyCollection.inc.php index 95c31b0..6536b6b 100644 --- a/lib/api/ktcmis/classes/CMISPropertyCollection.inc.php +++ b/lib/api/ktcmis/classes/CMISPropertyCollection.inc.php @@ -55,29 +55,25 @@ abstract class CMISPropertyCollection { static $LastModificationDate; static $ChangeToken; // TODO these definitions belong in their own classe definition (see property type definions,) but here will do for now - static $propertyTypes; + static public $propertyTypes; function __construct() { - $this->propertyTypes = array('ObjectId' => 'propertyId', - 'BaseType' => 'propertyString', - 'ObjectTypeId' => 'propertyString', - 'CreatedBy' => 'propertyString', - 'CreationDate' => 'propertyDateTime', - 'LastModifiedBy' => 'propertyString', - 'LastModificationDate' => 'propertyDateTime', - 'Name' => 'propertyString', - 'ContentStreamAllowed' => 'propertyString', - 'ContentStreamLength' => 'propertyInteger', - 'ContentStreamMimeType' => 'propertyString', - 'ContentStreamFilename' => 'propertyString', - 'ContentStreamUri' => 'propertyUri', - 'Uri' => 'propertyUri', - 'AllowedChildObjectTypeIds' => 'propertyId', - 'CreatedBy' => 'propertyString', - 'CreationDate' => 'propertyDateTime', - 'ChangeToken' => 'propertyString', - 'ParentId' => 'propertyId'); + self::$propertyTypes = array('ObjectId' => 'propertyId', + 'Author' => 'propertyString', + 'BaseType' => 'propertyString', + 'ObjectTypeId' => 'propertyString', + 'CreatedBy' => 'propertyString', + 'CreationDate' => 'propertyDateTime', + 'LastModifiedBy' => 'propertyString', + 'LastModificationDate' => 'propertyDateTime', + 'Name' => 'propertyString', + 'Uri' => 'propertyUri', + 'AllowedChildObjectTypeIds' => 'propertyId', + 'CreatedBy' => 'propertyString', + 'CreationDate' => 'propertyDateTime', + 'ChangeToken' => 'propertyString', + 'ParentId' => 'propertyId'); } /** diff --git a/lib/api/ktcmis/ktcmis.inc.php b/lib/api/ktcmis/ktcmis.inc.php index a06c00a..8b55f8d 100644 --- a/lib/api/ktcmis/ktcmis.inc.php +++ b/lib/api/ktcmis/ktcmis.inc.php @@ -458,12 +458,12 @@ class KTNavigationService extends KTCMISBase { "message" => "Failed getting list of checked out documents" ); } - + // convert to array format for external code $co = array(); - foreach ($checkedout as $document) + foreach ($checkedout as $documentProperties) { - $co[] = $document->getProperty('ObjectId'); + $co[] = CMISUtil::createObjectPropertiesEntry($documentProperties);; } return array( diff --git a/lib/api/ktcmis/services/CMISNavigationService.inc.php b/lib/api/ktcmis/services/CMISNavigationService.inc.php index 8822eb6..bed27ff 100644 --- a/lib/api/ktcmis/services/CMISNavigationService.inc.php +++ b/lib/api/ktcmis/services/CMISNavigationService.inc.php @@ -268,7 +268,7 @@ class CMISNavigationService { foreach($results as $document) { $CMISDocument = new CMISDocumentObject($document->getId(), $this->ktapi); - $checkedout[] = $CMISDocument; + $checkedout[] = $CMISDocument->getProperties(); } return $checkedout; diff --git a/lib/api/ktcmis/util/CMISUtil.inc.php b/lib/api/ktcmis/util/CMISUtil.inc.php index ef0080c..e9d0efd 100644 --- a/lib/api/ktcmis/util/CMISUtil.inc.php +++ b/lib/api/ktcmis/util/CMISUtil.inc.php @@ -270,7 +270,20 @@ class CMISUtil { static public function createObjectPropertiesEntry($properties) { $object = array(); + + foreach(CMISPropertyCollection::$propertyTypes as $property => $type) + { + // hack for Author property + if ($property == 'Author') { + $object[$property] = array('value' => $properties->getValue($property)); + } + else { + $object['properties'][$property] = array('type' => $type, 'value' => $properties->getValue($property)); + } + } + /* old static method */ + /* $object['Author'] = array('value' => $properties->getValue('Author')); $object['properties']['BaseType'] = array('type' => $properties->getFieldType('BaseType'), @@ -331,7 +344,10 @@ class CMISUtil { 'value' => $properties->getValue('ContentStreamUri')); } } + */ + /* what on earth was this for? */ + /* // if we have found a child/parent with one or more children/parents, recurse into the child/parent object if (count($entry['items']) > 0) { $object[$linkText] = CMISUtil::decodeObjectHierarchy($entry['items'], $linkText); @@ -341,6 +357,7 @@ class CMISUtil { else { $object[$linkText] = null; } + */ return $object; } diff --git a/webservice/atompub/cmis/KT_cmis_atom_server.services.inc.php b/webservice/atompub/cmis/KT_cmis_atom_server.services.inc.php index 28b2e14..54cfcbe 100644 --- a/webservice/atompub/cmis/KT_cmis_atom_server.services.inc.php +++ b/webservice/atompub/cmis/KT_cmis_atom_server.services.inc.php @@ -37,7 +37,11 @@ include_once CMIS_ATOM_LIB_FOLDER . 'VersioningService.inc.php'; include_once 'KT_cmis_atom_service_helper.inc.php'; // TODO consider changing all responses from the webservice layer to return PEAR errors or success results instead of the half/half we have at the moment. -// the half/half occurred because on initial services PEAR Error seemed unnecessary, but it has proven useful for some of the newer functions :) +// the half/half occurred because on initial services PEAR Error seemed unnecessary, but it has proven useful for some of the newer functions + +// TODO proper first/last links +// FIXME any incorrect or missing links +// FIXME ContentStreamAllowed tag is empty (at least sometimes) /** * AtomPub Service: folder @@ -236,6 +240,7 @@ class KT_cmis_atom_service_folder extends KT_cmis_atom_service { $this->responseFeed = $feed; return null; } + // list of failed objects? if (is_array($response)) { @@ -277,16 +282,13 @@ class KT_cmis_atom_service_folder extends KT_cmis_atom_service { */ private function getFolderChildrenFeed($NavigationService, $repositoryId, $folderId, $folderName, $feedType = 'children') { - if ($feedType == 'children') - { + if ($feedType == 'children') { $entries = $NavigationService->getChildren($repositoryId, $folderId, false, false); } - else if ($feedType == 'descendants') - { + else if ($feedType == 'descendants') { $entries = $NavigationService->getDescendants($repositoryId, $folderId, false, false); } - else - { + else { // error, we shouldn't be here, if we are then the wrong service/function was called } @@ -430,7 +432,6 @@ class KT_cmis_atom_service_type extends KT_cmis_atom_service { /** * AtomPub Service: checkedout */ -// NOTE this is always an empty document, underlying API code still to be implemented class KT_cmis_atom_service_checkedout extends KT_cmis_atom_service { /** @@ -444,10 +445,11 @@ class KT_cmis_atom_service_checkedout extends KT_cmis_atom_service { $repositories = $RepositoryService->getRepositories(); $repositoryId = $repositories[0]['repositoryId']; - $checkedout = $NavigationService->getCheckedoutDocs($repositoryId); + $checkedout = $NavigationService->getCheckedOutDocs($repositoryId); //Create a new response feed $feed = new KT_cmis_atom_responseFeed_GET(CMIS_APP_BASE_URI); + $workspace = $feed->getWorkspace(); $feed->newField('title', 'Checked out Documents', $feed); @@ -460,27 +462,37 @@ class KT_cmis_atom_service_checkedout extends KT_cmis_atom_service { // TODO get actual most recent update time, only use current if no other available $feed->appendChild($feed->newElement('updated', KT_cmis_atom_service_helper::formatDatestamp())); - - foreach($checkedout as $document) - { - $entry = $feed->newEntry(); - $objectElement = $feed->newElement('cmis:object'); - $propertiesElement = $feed->newElement('cmis:properties'); - - foreach($cmisEntry['properties'] as $propertyName => $property) - { - $propElement = $feed->newElement('cmis:' . $property['type']); - $propElement->appendChild($feed->newAttr('cmis:name', $propertyName)); - $feed->newField('cmis:value', CMISUtil::boolToString($property['value']), $propElement); - $propertiesElement->appendChild($propElement); - } + + $link = $feed->newElement('link'); + $link->appendChild($feed->newAttr('rel', 'self')); + $link->appendChild($feed->newAttr('href', CMIS_APP_BASE_URI . $workspace . '/checkedout')); + $feed->appendChild($link); + + $link = $feed->newElement('link'); + $link->appendChild($feed->newAttr('rel','first')); + $link->appendChild($feed->newAttr('href', CMIS_APP_BASE_URI . $workspace . '/checkedout/pageNo=1&pageSize=0')); + $link->appendChild($feed->newAttr('type', 'application/atom+xml;type=feed')); + $feed->appendChild($link); + + $link = $feed->newElement('link'); + $link->appendChild($feed->newAttr('rel','last')); + // TODO set page number correctly - to be done when we support paging the the API + $link->appendChild($feed->newAttr('href', CMIS_APP_BASE_URI . $workspace . '/checkedout/pageNo=1&pageSize=0')); + $link->appendChild($feed->newAttr('type', 'application/atom+xml;type=feed')); + $feed->appendChild($link); - $objectElement->appendChild($propertiesElement); - $entry->appendChild($objectElement); + foreach($checkedout as $cmisEntry) + { + KT_cmis_atom_service_helper::createObjectEntry($feed, $cmisEntry, $folderName); + +// // after each entry, add app:edited tag +// $feed->newField('app:edited', KT_cmis_atom_service_helper::formatDatestamp(), $feed); } - $entry = null; - $feed->newField('cmis:hasMoreItems', 'false', $entry, true); + $feed->newField('cmis:hasMoreItems', 'false', $feed); + +// $entry = null; +// $feed->newField('cmis:hasMoreItems', 'false', $entry, true); //Expose the responseFeed $this->responseFeed = $feed; @@ -495,8 +507,8 @@ class KT_cmis_atom_service_checkedout extends KT_cmis_atom_service { $repositories = $RepositoryService->getRepositories(); $repositoryId = $repositories[0]['repositoryId']; - $cmisObjectProperties = KT_cmis_atom_service_helper::getCmisProperties($this->parsedXMLContent['@children']['cmis:object']); - + $cmisObjectProperties = KT_cmis_atom_service_helper::getCmisProperties($this->parsedXMLContent['@children']); + // check for existing object id as property of submitted object data if (empty($cmisObjectProperties['ObjectId'])) { @@ -518,51 +530,9 @@ class KT_cmis_atom_service_checkedout extends KT_cmis_atom_service { $this->setStatus(self::STATUS_CREATED); $feed = KT_cmis_atom_service_helper::getObjectFeed($this, $ObjectService, $repositoryId, $cmisObjectProperties['ObjectId'], 'POST'); -// $feed = KT_cmis_atom_service_helper::getObjectFeed($this, $ObjectService, $repositoryId, $newObjectId, 'POST'); //Expose the responseFeed $this->responseFeed = $feed; - -// $checkedout = $NavigationService->getCheckedoutDocs($repositoryId); -// -// //Create a new response feed -// $feed = new KT_cmis_atom_responseFeed_GET(CMIS_APP_BASE_URI); -// -// $feed->newField('title', 'Checked out Documents', $feed); -// -// // TODO dynamic? -// $feedElement = $feed->newField('author'); -// $element = $feed->newField('name', 'admin', $feedElement); -// $feed->appendChild($feedElement); -// -// $feed->appendChild($feed->newElement('id', 'urn:uuid:checkedout')); -// -// // TODO get actual most recent update time, only use current if no other available -// $feed->appendChild($feed->newElement('updated', KT_cmis_atom_service_helper::formatDatestamp())); -// -// foreach($checkedout as $document) -// { -// $entry = $feed->newEntry(); -// $objectElement = $feed->newElement('cmis:object'); -// $propertiesElement = $feed->newElement('cmis:properties'); -// -// foreach($cmisEntry['properties'] as $propertyName => $property) -// { -// $propElement = $feed->newElement('cmis:' . $property['type']); -// $propElement->appendChild($feed->newAttr('cmis:name', $propertyName)); -// $feed->newField('cmis:value', CMISUtil::boolToString($property['value']), $propElement); -// $propertiesElement->appendChild($propElement); -// } -// -// $objectElement->appendChild($propertiesElement); -// $entry->appendChild($objectElement); -// } -// -// $entry = null; -// $feed->newField('cmis:hasMoreItems', 'false', $entry, true); -// -// //Expose the responseFeed -// $this->responseFeed = $feed; } } @@ -675,4 +645,64 @@ class KT_cmis_atom_service_document extends KT_cmis_atom_service { } +class KT_cmis_atom_service_pwc extends KT_cmis_atom_service { + + /** + * Deals with GET actions for Private Working Copies. + * This includes individual Private Working Copy retrieval + */ + public function GET_action() + { + $RepositoryService = new RepositoryService(); + $ObjectService = new ObjectService(KT_cmis_atom_service_helper::getKt()); + + $repositories = $RepositoryService->getRepositories(); + $repositoryId = $repositories[0]['repositoryId']; + + // determine whether we want the Private Working Copy entry feed or the actual physical Private Working Copy content. + // this depends on $this->params[1] + if (!empty($this->params[1])) + { + $this->getContentStream($ObjectService, $repositoryId); + return null; + } + + $feed = KT_cmis_atom_service_helper::getObjectFeed($this, $ObjectService, $repositoryId, $this->params[0]); + + //Expose the responseFeed + $this->responseFeed = $feed; + } + + /** + * Deals with DELETE actions for Private Working Copies. + * This includes deletion of a specific version of a document (latest version) via deleteObject + * as well as deleteAllVersions + * + * @return 204 on success, 500 on error + */ + public function DELETE_action() + { + // call the cancel checkout function + $RepositoryService = new RepositoryService(); + $VersioningService = new VersioningService(KT_cmis_atom_service_helper::getKt()); + + $repositories = $RepositoryService->getRepositories(); + $repositoryId = $repositories[0]['repositoryId']; + + $response = $VersioningService->cancelCheckout($repositoryId, $this->params[0]); + + if (PEAR::isError($response)) + { + $feed = KT_cmis_atom_service_helper::getErrorFeed($this, self::STATUS_SERVER_ERROR, $response->getMessage()); + //Expose the responseFeed + $this->responseFeed = $feed; + return null; + } + + $this->setStatus(self::STATUS_NO_CONTENT); + $this->responseFeed = null; + } + +} + ?> \ No newline at end of file diff --git a/webservice/atompub/cmis/KT_cmis_atom_service_helper.inc.php b/webservice/atompub/cmis/KT_cmis_atom_service_helper.inc.php index 9d1b88a..d57198f 100644 --- a/webservice/atompub/cmis/KT_cmis_atom_service_helper.inc.php +++ b/webservice/atompub/cmis/KT_cmis_atom_service_helper.inc.php @@ -75,7 +75,7 @@ class KT_cmis_atom_service_helper { { $field = $response->newElement('content'); $field->appendChild($response->newAttr('type', $cmisEntry['properties']['ContentStreamMimeType']['value'])); - $field->appendChild($response->newAttr('href', CMIS_APP_BASE_URI . $workspace . '/' . $type + $field->appendChild($response->newAttr('src', CMIS_APP_BASE_URI . $workspace . '/' . $type . '/' . $cmisEntry['properties']['ObjectId']['value'] . '/' . $cmisEntry['properties']['ContentStreamFilename']['value'])); $entry->appendChild($field); @@ -190,14 +190,24 @@ class KT_cmis_atom_service_helper { } // if the document is checked out and this is NOT the PWC, this link MUST be present -// if (!empty($cmisEntry['properties']['ContentStreamLength']['value'])) -// { -// $link = $response->newElement('link'); -// $link->appendChild($response->newAttr('rel', 'stream')); -// $link->appendChild($response->newAttr('href', CMIS_APP_BASE_URI . $workspace . '/' . $type . '/' . $cmisEntry['properties']['ParentId']['value'])); -// $entry->appendChild($link); -// } -// + // NOTE at the moment the document and the PWC are the same object, so we always show it for a checked out document + // TODO separated code for PWC and actual document object + if (!empty($cmisEntry['properties']['VersionSeriesCheckedOutId']['value'])) + { + $link = $response->newElement('link'); + $link->appendChild($response->newAttr('rel', 'pwc')); + $link->appendChild($response->newAttr('href', CMIS_APP_BASE_URI . $workspace . '/' . $type + . '/' . $cmisEntry['properties']['ObjectId']['value'] + . '/' . $cmisEntry['properties']['ContentStreamFilename']['value'])); + $entry->appendChild($link); + $link = $response->newElement('link'); + $link->appendChild($response->newAttr('rel', 'source')); + $link->appendChild($response->newAttr('href', CMIS_APP_BASE_URI . $workspace . '/' . $type + . '/' . $cmisEntry['properties']['ObjectId']['value'] + . '/' . $cmisEntry['properties']['ContentStreamFilename']['value'])); + $entry->appendChild($link); + } + // $link = $response->newElement('link'); // $link->appendChild($response->newAttr('rel', 'stream')); // $link->appendChild($response->newAttr('href', CMIS_APP_BASE_URI . $workspace . '/' . $type @@ -253,9 +263,10 @@ class KT_cmis_atom_service_helper { // after every entry, append a cmis:terminator tag $entry->appendChild($response->newElement('cmis:terminator')); - if ($method == 'POST') { + // TODO check determination of when to add app:edited tag +// if ($method == 'POST') { $entry->appendChild($response->newElement('app:edited', self::formatDatestamp())); - } +// } } /** @@ -415,9 +426,17 @@ class KT_cmis_atom_service_helper { { $properties = array(); - foreach($xmlArray as $xmlElement) + // find cmis:object tag + $baseCmisObject = KT_cmis_atom_service_helper::findTag('cmis:object', $xmlArray, null, false); + if(count($baseCmisObject) <= 0) { - foreach($xmlElement['@children'] as $key => $childElement) + $entryObject = KT_cmis_atom_service_helper::findTag('entry', $xmlArray, null, false); + $baseCmisObject = KT_cmis_atom_service_helper::findTag('cmis:object', $entryObject['@children'], null, true); + } + + if(count($baseCmisObject)>0) + { + foreach($baseCmisObject['@children'] as $key => $childElement) { if ($key == 'cmis:properties') { @@ -466,6 +485,34 @@ class KT_cmis_atom_service_helper { if (is_null($time)) $time = time(); return date('Y-m-d H:i:s', $time); } + + //TODO: Add key information to be able to find the same tag in the original struct (MarkH) + static public function findTag($tagName=NULL,$xml=array(),$tagArray=NULL,$deep=false){ + $tagArray=is_array($tagArray)?$tagArray:array(); + foreach($xml as $xmlTag=>$content){ + if($xmlTag===$tagName){ + $tagArray[]=$content; + } + if($deep){ + foreach($content as $contentTags){ + if(is_array($contentTags['@children'])) { + if(count($contentTags['@children'])>0) $tagArray=self::findTag($tagName,$contentTags['@children'],$tagArray); + } + } + } + } + //TODO: this is very ugly. Change it. (MarkH) + return self::rebaseArray($tagArray); + } + + static public function rebaseArray($arr=array()){ + //Force Array + $arr=is_array($arr)?$arr:array(); + + //Rebase recursively + if(count($arr)===1)$arr=self::rebaseArray($arr[0]); + return $arr; + } } diff --git a/webservice/atompub/cmis/index.php b/webservice/atompub/cmis/index.php index 79add03..8f7d7a6 100644 --- a/webservice/atompub/cmis/index.php +++ b/webservice/atompub/cmis/index.php @@ -113,6 +113,7 @@ if ($workspace != 'servicedocument') { $APP->registerService('dms', 'type', 'KT_cmis_atom_service_type', 'Object Type Entry', null, 'type'); $APP->registerService('dms', 'document', 'KT_cmis_atom_service_document', 'Document Entry', null, 'document'); + $APP->registerService('dms', 'pwc', 'KT_cmis_atom_service_pwc', 'Private Working Copy', null, 'pwc'); } //Execute the current url/header request diff --git a/webservice/classes/atompub/cmis/NavigationService.inc.php b/webservice/classes/atompub/cmis/NavigationService.inc.php index fe47e18..8c69504 100644 --- a/webservice/classes/atompub/cmis/NavigationService.inc.php +++ b/webservice/classes/atompub/cmis/NavigationService.inc.php @@ -109,9 +109,9 @@ class NavigationService extends KTNavigationService { * @param int $skipCount * @return array $checkedout The collection of checked out documents */ - function getCheckedoutDocs($repositoryId, $folderId = null, $filter = '', $maxItems = 0, $skipCount = 0) + function getCheckedOutDocs($repositoryId, $folderId = null, $filter = '', $maxItems = 0, $skipCount = 0) { - $checkedout = parent::getObjectParents($repositoryId, $folderId, $filter, $maxItems, $skipCount); + $result = parent::getCheckedOutDocs($repositoryId, $folderId, $filter, $maxItems, $skipCount); if ($result['status_code'] == 0) { diff --git a/webservice/classes/atompub/cmis/VersioningService.inc.php b/webservice/classes/atompub/cmis/VersioningService.inc.php index 04951c7..f7fb909 100644 --- a/webservice/classes/atompub/cmis/VersioningService.inc.php +++ b/webservice/classes/atompub/cmis/VersioningService.inc.php @@ -48,6 +48,30 @@ class VersioningService extends KTVersioningService { return new PEAR_Error($result['message']); } } + + /** + * Reverses the effect of a checkout: I.E. deletes the PWC (Private Working Copy) and re-sets the status of the document to "not checked out" + * + * @param string $repositoryId + * @param string $documentId + * @param string $changeToken [optional] + */ + // TODO exceptions: + // • ConstraintViolationException: The Repository SHALL throw this exception if ANY of the following conditions are met: + // o The Document’s Object-Type definition’s versionable attribute is FALSE. + // • updateConflictException + // • versioningException + public function cancelCheckOut($repositoryId, $documentId, $changeToken = '') + { + $result = parent::cancelCheckOut($repositoryId, $documentId, $changeToken); + + if ($result['status_code'] == 0) { + return $result['results']; + } + else { + return new PEAR_Error($result['message']); + } + } }