diff --git a/ktwebservice/KTUploadManager.inc.php b/ktwebservice/KTUploadManager.inc.php index 57dc38f..f88fcb0 100644 --- a/ktwebservice/KTUploadManager.inc.php +++ b/ktwebservice/KTUploadManager.inc.php @@ -110,6 +110,7 @@ class KTUploadManager return ($tempdir == $this->temp_dir); */ } + function store_base64_file($base64, $prefix= 'sa_') { $tempfilename = $this->get_temp_filename($prefix); diff --git a/lib/api/ktcmis/classes/CMISBaseObject.inc.php b/lib/api/ktcmis/classes/CMISBaseObject.inc.php index c43f247..31964ca 100644 --- a/lib/api/ktcmis/classes/CMISBaseObject.inc.php +++ b/lib/api/ktcmis/classes/CMISBaseObject.inc.php @@ -62,7 +62,7 @@ abstract class CMISBaseObject { // TODO all we have here so far is getAttributes & getProperties // add all the other methods as we go along - function __construct() + public function __construct() { // $propertyDef = new PropertyDefinition(); // $this->properties[] = $propertyDef; @@ -73,7 +73,7 @@ abstract class CMISBaseObject { * * @return array $attributes */ - function getAttributes() + public function getAttributes() { $attributes = array(); @@ -95,7 +95,7 @@ abstract class CMISBaseObject { return $attributes; } - function getAttribute($field) + public function getAttribute($field) { return $this->{$field}; } @@ -104,7 +104,7 @@ abstract class CMISBaseObject { * Sets properties for this type * Obeys the rules as specified in the property definitions (once implemented) */ - function setProperty($field, $value) + public function setProperty($field, $value) { $this->properties->setValue($field, $value); } @@ -123,11 +123,26 @@ abstract class CMISBaseObject { /** * Fetches properties for this object type */ - function getProperties() + public function getProperties() { return $this->properties; } + public function getProperty($property) + { + return $this->properties->getValue($property); + } + + public function reload($documentId) + { + $this->_get($documentId); + } + + private function _get($documentId) + { + // override in child classes + } + } ?> diff --git a/lib/api/ktcmis/exceptions/InvalidArgumentException.inc.php b/lib/api/ktcmis/exceptions/InvalidArgumentException.inc.php deleted file mode 100644 index 53f7390..0000000 --- a/lib/api/ktcmis/exceptions/InvalidArgumentException.inc.php +++ /dev/null @@ -1,7 +0,0 @@ - diff --git a/lib/api/ktcmis/exceptions/RuntimeException.inc.php b/lib/api/ktcmis/exceptions/RuntimeException.inc.php deleted file mode 100644 index cb1a5b2..0000000 --- a/lib/api/ktcmis/exceptions/RuntimeException.inc.php +++ /dev/null @@ -1,7 +0,0 @@ - diff --git a/lib/api/ktcmis/ktcmis.inc.php b/lib/api/ktcmis/ktcmis.inc.php index 08776e8..1c83cba 100644 --- a/lib/api/ktcmis/ktcmis.inc.php +++ b/lib/api/ktcmis/ktcmis.inc.php @@ -480,7 +480,7 @@ class KTObjectService extends KTCMISBase { * @return string $objectId The id of the created folder object */ function createDocument($repositoryId, $typeId, $properties, $folderId = null, - $contentStream = null, $versioningState = 'Major') + $contentStream = null, $versioningState = null) { $objectId = null; diff --git a/lib/api/ktcmis/objecttypes/CMISDocumentObject.inc.php b/lib/api/ktcmis/objecttypes/CMISDocumentObject.inc.php index bfb6822..cd9024e 100644 --- a/lib/api/ktcmis/objecttypes/CMISDocumentObject.inc.php +++ b/lib/api/ktcmis/objecttypes/CMISDocumentObject.inc.php @@ -48,8 +48,8 @@ require_once(CMIS_DIR . '/util/CMISUtil.inc.php'); class CMISDocumentObject extends CMISBaseObject { - private $versionable; - private $contentStreamAllowed; + protected $versionable; + protected $contentStreamAllowed; private $ktapi; private $uri; @@ -100,7 +100,7 @@ class CMISDocumentObject extends CMISBaseObject { private function _get($documentId) { - $object = $this->ktapi->get_document_by_id($documentId); + $object = $this->ktapi->get_document_by_id((int)$documentId); // error? if (PEAR::isError($object)) diff --git a/lib/api/ktcmis/objecttypes/CMISFolderObject.inc.php b/lib/api/ktcmis/objecttypes/CMISFolderObject.inc.php index 4c3eb78..0526893 100644 --- a/lib/api/ktcmis/objecttypes/CMISFolderObject.inc.php +++ b/lib/api/ktcmis/objecttypes/CMISFolderObject.inc.php @@ -46,10 +46,10 @@ require_once(CMIS_DIR . '/util/CMISUtil.inc.php'); class CMISFolderObject extends CMISBaseObject { - var $ktapi; - var $uri; + private $ktapi; + private $uri; - function __construct($folderId = null, &$ktapi = null, $uri = null) + public function __construct($folderId = null, &$ktapi = null, $uri = null) { $this->ktapi = $ktapi; $this->uri = $uri; @@ -78,7 +78,7 @@ class CMISFolderObject extends CMISBaseObject { private function _get($folderId) { - $object = $this->ktapi->get_folder_by_id($folderId); + $object = $this->ktapi->get_folder_by_id((int)$folderId); // error? if (PEAR::isError($object)) diff --git a/lib/api/ktcmis/services/CMISNavigationService.inc.php b/lib/api/ktcmis/services/CMISNavigationService.inc.php index b35d512..eb4393a 100644 --- a/lib/api/ktcmis/services/CMISNavigationService.inc.php +++ b/lib/api/ktcmis/services/CMISNavigationService.inc.php @@ -83,7 +83,7 @@ class CMISNavigationService { $repository = new CMISRepository($repositoryId); // if this is not a folder, cannot get descendants - $type = CMISUtil::decodeObjectId($folderId); + $folderId = CMISUtil::decodeObjectId($folderId, $type); if ($type != 'Folder') { @@ -125,7 +125,7 @@ class CMISNavigationService { $repository = new CMISRepository($repositoryId); // if this is not a folder, cannot get children - $type = CMISUtil::decodeObjectId($folderId); + $folderId = CMISUtil::decodeObjectId($folderId, $type); // NOTE this will quite possibly break the webservices if ($type != 'Folder') { @@ -162,7 +162,7 @@ class CMISNavigationService { $repository = new CMISRepository($repositoryId); // if this is not a folder, cannot get folder parent :) - $type = CMISUtil::decodeObjectId($folderId); + $folderId = CMISUtil::decodeObjectId($folderId, $type); // NOTE this will quite possibly break the webservices if ($type != 'Folder') { @@ -221,7 +221,7 @@ class CMISNavigationService { { $ancestry = array(); - $typeId = CMISUtil::decodeObjectId($objectId); + $objectId = CMISUtil::decodeObjectId($objectId, $typeId); // TODO - what about other types? only implementing folders and documents at the moment so ignore for now switch($typeId) diff --git a/lib/api/ktcmis/services/CMISObjectService.inc.php b/lib/api/ktcmis/services/CMISObjectService.inc.php index b40c7f0..0a7f0e3 100644 --- a/lib/api/ktcmis/services/CMISObjectService.inc.php +++ b/lib/api/ktcmis/services/CMISObjectService.inc.php @@ -1,6 +1,7 @@ ktapi); - $folderProperties = $CMISFolder->getProperties(); - $allowed = $folderProperties->getValue('AllowedChildObjectTypeIds'); + $allowed = $CMISFolder->getProperty('AllowedChildObjectTypeIds'); if (!is_array($allowed) || !in_array($typeId, $allowed)) { throw new ConstraintViolationException('Parent folder may not hold objects of this type (' . $typeId . ')'); } - // set title and name identical if only one submitted - if ($properties['title'] == '') - { - $properties['title'] = $properties['name']; - } - else if ($properties['name'] == '') - { - $properties['name'] = $properties['title']; - } - - if ($properties['type'] == '') - { - $properties['type'] = $properties['Default']; - } - // if content stream is required and no content stream is supplied, throw a ConstraintViolationException if (($typeDefinition['attributes']['contentStreamAllowed'] == 'required') && empty($contentStream)) { - throw new ConstraintViolationException('The Knowledgetree Repository requires a content stream for document creation. ' + throw new ConstraintViolationException('This repository requires a content stream for document creation. ' . 'Refusing to create an empty document'); } else if (($typeDefinition['attributes']['contentStreamAllowed'] == 'notAllowed') && !empty($contentStream)) @@ -168,25 +148,66 @@ class CMISObjectService { throw new StreamNotSupportedException('Content Streams are not supported'); } + // if versionable attribute is set to false and versioningState is supplied, throw a ConstraintViolationException + if (!$typeDefinition['attributes']['versionable'] && !empty($versioningState)) + { + throw new ConstraintViolationException('This repository does not support versioning'); + } + // TODO deal with $versioningState when supplied - // TODO Use add_document_with_metadata instead if metadata content submitted - $response = $this->ktapi->add_document($folderId, $properties['title'], $properties['name'], $properties['type'], $uploadedFile); - if ($response['status_code'] != 0) + // set title and name identical if only one submitted + if ($properties['title'] == '') { - throw new StorageException('The repository was unable to create the document - ' . $response['message']); + $properties['title'] = $properties['name']; } - else + else if ($properties['name'] == '') { - $objectId = CMISUtil::encodeObjectId('Document', $response['results']['id']); + $properties['name'] = $properties['title']; } - // now that the document object exists, create the content stream from the supplied data + // TODO also set to Default if a non-supported type is submitted + if ($properties['type'] == '') + { + $properties['type'] = 'Default'; + } + + // create the content stream from the supplied data + // NOTE since the repository is set to require a content stream and we don't currently have another way to get the document data + // this check isn't strictly necessary; however it is needed for a repository which does not support content streams if (!empty($contentStream)) { - // TODO changeToken support - $changeToken = null; - $this->setContentStream($repositoryId, $objectId, false, $contentStream, $changeToken); + // NOTE There is a function in CMISUtil to do this 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 it existed when + // I wrote the CMISUtil function) + $uploadManager = new KTUploadManager(); + $file = $uploadManager->store_base64_file($contentStream, 'cmis_'); + // create the document from this temporary file as per usual + // TODO Use add_document_with_metadata instead if metadata content submitted || update metadata separately? + $response = $this->ktapi->add_document((int)$folderId, $properties['title'], $properties['name'], + $properties['type'], $file); + if ($response['status_code'] != 0) + { + throw new StorageException('The repository was unable to create the document. ' . $response['message']); + } + else + { + $objectId = CMISUtil::encodeObjectId('Document', $response['results']['content_id']); + } + + // remove temporary file + @unlink($file); + } + // else create the document object in the database but don't actually create any content since none was supplied + // NOTE perhaps this content could be supplied in the $properties array? + else + { + // TODO creation of document without content. leaving this for now as we require content streams and any code + // here will therefore never be executed; if we implement some form of template based document creation + // then we may have something else to do here; + // for now we just throw a general RuntimeException, since we should not + // actually reach this code unless something is wrong; this may be removed or replaced later + throw new RuntimeException('Cannot create empty document'); } return $objectId; @@ -229,20 +250,19 @@ class CMISObjectService { // Attempt to decode $folderId, use as is if not detected as encoded $tmpObjectId = $folderId; - $tmpTypeId = CMISUtil::decodeObjectId($tmpObjectId); + $tmpObjectId = CMISUtil::decodeObjectId($tmpObjectId, $tmpTypeId); if ($tmpTypeId != 'Unknown') $folderId = $tmpObjectId; // if parent folder is not allowed to hold this type, throw exception $CMISFolder = new CMISFolderObject($folderId, $this->ktapi); - $folderProperties = $CMISFolder->getProperties(); - $allowed = $folderProperties->getValue('AllowedChildObjectTypeIds'); + $allowed = $CMISFolder->getProperty('AllowedChildObjectTypeIds'); if (!is_array($allowed) || !in_array($typeId, $allowed)) { throw new ConstraintViolationException('Parent folder may not hold objects of this type (' . $typeId . ')'); } - $response = $this->ktapi->create_folder($folderId, $properties['name'], $sig_username = '', $sig_password = '', $reason = ''); + $response = $this->ktapi->create_folder((int)$folderId, $properties['name'], $sig_username = '', $sig_password = '', $reason = ''); if ($response['status_code'] != 0) { throw new StorageException('The repository was unable to create the folder - ' . $response['message']); @@ -255,6 +275,14 @@ class CMISObjectService { return $objectId; } + // NOTE this function is presently incomplete and untested. Completion deferred to implementation of Checkout/Checkin + // functionality + // NOTE I am not sure yet when this function would ever be called - checkin would be able to update the content stream + // already and the only easy method we have (via KTAPI as it stands) to update the content is on checkin anyway. + // Additionally this function doesn't take a value for the versioning status (major/minor) and so cannot pass it + // on to the ktapi checkin function. + // I imagine this function may be called if we ever allow updating document content independent of checkin, + // or if we change some of the underlying code and call direct to the document functions and not via KTAPI. /** * Sets the content stream data for an existing document * @@ -277,6 +305,17 @@ class CMISObjectService { // versioningException: The repository MAY throw this exception if the object is a non-current Document Version. function setContentStream($repositoryId, $documentId, $overwriteFlag, $contentStream, $changeToken = null) { + // if no document id was supplied, we are going to create the underlying physical document + // NOTE while it might have been nice to keep this out of here, KTAPI has no method for creating a document without + // a physical upload, so we cannot create the document first and then add the upload as a content stream, the + // entire creation step needs to happen here. + + // Attempt to decode $documentId, use as is if not detected as encoded + $tmpObjectId = $documentId; + $tmpObjectId = CMISUtil::decodeObjectId($tmpObjectId, $tmpTypeId); + if ($tmpTypeId != 'Unknown') + $documentId = $tmpObjectId; + // fetch type definition of supplied document $CMISDocument = new CMISDocumentObject($documentId, $this->ktapi); @@ -289,26 +328,34 @@ class CMISObjectService { throw new StreamNotSupportedException('Content Streams are not allowed for this object type'); } - $properties = $CMISDocument->getProperties(); - if (!empty($properties->getValue('ContentStreamFilename')) && (!$overwriteFlag)) + $csFileName = $CMISDocument->getProperty('ContentStreamFilename'); + if (!empty($csFileName) && (!$overwriteFlag)) { throw new ContentAlreadyExistsException('Unable to overwrite existing content stream'); } - // in order to set the stream we need to do the following: - // 1. decode the stream from the supplied base64 encoding - // 2. create a temporary file as if it were uploaded via a file upload dialog - // 3. create the document content as per usual - // 4. link to the created document object? this perhaps only happens on read anyway - - // if there is any problem updating the content stream, throw StorageException - // TODO real test parameter instead of hard-coded FALSE - if (false) + // NOTE There is a function in CMISUtil to do this 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 it existed when + // I wrote the CMISUtil function) + $uploadManager = new KTUploadManager(); + $file = $uploadManager->store_base64_file($contentStream, 'cmis_'); + // update the document content from this temporary file as per usual + // TODO Use checkin_document_with_metadata instead if metadata content submitted || update metadata separately? + $response = $this->ktapi->checkin_document($documentId, $csFileName, 'CMIS setContentStream action', $file, false); + if ($response['status_code'] != 0) { - throw new StorageException('Unable to update the content stream'); + throw new StorageException('Unable to update the content stream. ' . $response['message']); } +// else +// { +// $objectId = CMISUtil::encodeObjectId('Document', $response['results']['id']); +// } + + @unlink($csFile); + // update the CMIS document object with the content stream information +// $CMISDocument->reload($document['result']['document_id']); - return $documentId; + return $CMISDocument->getProperty('ObjectId'); } } diff --git a/lib/api/ktcmis/services/CMISRepositoryService.inc.php b/lib/api/ktcmis/services/CMISRepositoryService.inc.php index ea1ffcd..fdc38ed 100644 --- a/lib/api/ktcmis/services/CMISRepositoryService.inc.php +++ b/lib/api/ktcmis/services/CMISRepositoryService.inc.php @@ -43,7 +43,6 @@ require_once(CMIS_DIR . '/classes/CMISRepository.inc.php'); require_once(CMIS_DIR . '/classes/CMISObjectTypes.inc.php'); -require_once(CMIS_DIR . '/exceptions/InvalidArgumentException.inc.php'); /** * CMIS Repository Service. diff --git a/lib/api/ktcmis/util/CMISUtil.inc.php b/lib/api/ktcmis/util/CMISUtil.inc.php index fc9fe02..8740f84 100644 --- a/lib/api/ktcmis/util/CMISUtil.inc.php +++ b/lib/api/ktcmis/util/CMISUtil.inc.php @@ -53,7 +53,7 @@ class CMISUtil { * @param string $objectId * @return string $encoded */ - static function encodeObjectId($typeId, $objectId) + static public function encodeObjectId($typeId, $objectId) { $encoded = null; @@ -84,7 +84,7 @@ class CMISUtil { * @param string $objectId * @return string $typeId */ - static function decodeObjectId(&$objectId) + static public function decodeObjectId($objectId, &$typeId = null) { $typeId = null; @@ -105,7 +105,7 @@ class CMISUtil { break; } - return $typeId; + return $objectId; } /** @@ -118,7 +118,7 @@ class CMISUtil { * @param object $ktapi // reference to ktapi instance * @return array $CMISArray */ - static function createChildObjectHierarchy($input, $repositoryURI, &$ktapi) + static public function createChildObjectHierarchy($input, $repositoryURI, &$ktapi) { $CMISArray = array(); @@ -172,7 +172,7 @@ class CMISUtil { * @return array $CMISArray */ // NOTE this will have to change if we implement multi-filing - static function createParentObjectHierarchy($input, $repositoryURI, &$ktapi) + static public function createParentObjectHierarchy($input, $repositoryURI, &$ktapi) { $CMISArray = array(); @@ -209,7 +209,7 @@ class CMISUtil { * @param string $linkText // 'child' or 'parent' - indicates direction of hierarchy => descending or ascending * @return array $hierarchy */ - static function decodeObjectHierarchy($input, $linkText) + static public function decodeObjectHierarchy($input, $linkText) { $hierarchy = array(); @@ -225,7 +225,7 @@ class CMISUtil { return $hierarchy; } - static function createObjectPropertiesEntry($properties) + static public function createObjectPropertiesEntry($properties) { $object = array(); @@ -281,7 +281,7 @@ class CMISUtil { * @param object $data * @return array $array */ - static function objectToArray($data) + static public function objectToArray($data) { $array = array(); @@ -309,11 +309,35 @@ class CMISUtil { * @param boolean/other $input * @return string */ - function boolToString($input) + static public function boolToString($input) { return (($input === true) ? 'true' : (($input === false) ? 'false' : $input)); } + /** + * Creates a temporary file + * Cleanup is the responsibility of the calling code + * + * @param string|binary $content The content to be written to the file. + * @param string $uploadDir Optional upload directory. Will use the KnowledgeTree system tmp directory if not supplied. + * @return string The path to the created file (for reference and cleanup.) + */ + static public function createTemporaryFile($content, $encoding = null, $uploadDir = null) + { + if(is_null($uploadDir)) + { + $oKTConfig =& KTConfig::getSingleton(); + $uploadDir = $oKTConfig->get('webservice/uploadDirectory'); + } + + $temp = tempnam($uploadDir, 'myfile'); + $fp = fopen($temp, 'wb'); + fwrite($fp, ($encoding == 'base64' ? base64_decode($content) : $content)); + fclose($fp); + + return $temp; + } + } ?> diff --git a/tests/ktcmis/testCmisApi.php b/tests/ktcmis/testCmisApi.php index 96b2fb0..79b5166 100644 --- a/tests/ktcmis/testCmisApi.php +++ b/tests/ktcmis/testCmisApi.php @@ -355,10 +355,53 @@ class CMISTestCase extends KTUnitTestCase { $this->printTable($properties['results'][0], 'Properties for CMIS Created Folder Object ' . $folderId . ' (getProperties())'); // delete - CMISUtil::decodeObjectId($folderId); - $this->ktapi->delete_folder($folderId, 'Testing API', KT_TEST_USER, KT_TEST_PASS); + $this->ktapi->delete_folder(CMISUtil::decodeObjectId($folderId), 'Testing API', KT_TEST_USER, KT_TEST_PASS); } + // TEST 3 + // test creation of document + $folderId = 'F'.$this->folders[0]; + $folderId = 'F1'; + $properties = array('name' => 'Test CMIS Document 1', 'title' => 'test_cmis_doc_' . mt_rand() . '.txt'); + $contentStream = base64_encode('Some arbitrary text content'); + $created = $ObjectService->createDocument($repositoryId, 'Document', $properties, $folderId, $contentStream); + + $this->assertNotNull($created['results']); + +// echo '
'.print_r($created, true).''; + + if (!is_null($created['results'])) + { + $documentId = $created['results']; + + // check that document object actually exists + $properties = $ObjectService->getProperties($repositoryId, $documentId, false, false); + $this->assertNotNull($properties['results']); + + // test printout + $this->printTable($properties['results'][0], 'Properties for CMIS Created Document Object ' . $documentId . ' (getProperties())'); + } + +// // TEST 5 +// // test updating content stream for existing document +// $contentStream = base64_encode('Some updated text content for the content stream'); +// $updated = $ObjectService->setContentStream($repositoryId, $documentId, true, $contentStream); +// +// $this->assertNotNull($updated['results']); +// +//// echo '
'.print_r($created, true).''; +// +// if (!is_null($updated['results'])) +// { +//// $documentId = $updated['results']; +// +// // TODO test getContentStream here when we have it +// +// } + + // delete created document +// $this->ktapi->delete_document(CMISUtil::decodeObjectId($documentId), 'Testing API', false); + // tear down the folder/doc tree structure with which we were testing $this->cleanupFolderDocStructure();