diff --git a/lib/api/ktcmis/exceptions/ContentAlreadyExistsException.inc.php b/lib/api/ktcmis/exceptions/ContentAlreadyExistsException.inc.php new file mode 100644 index 0000000..c97c4bc --- /dev/null +++ b/lib/api/ktcmis/exceptions/ContentAlreadyExistsException.inc.php @@ -0,0 +1,7 @@ + diff --git a/lib/api/ktcmis/exceptions/RuntimeException.inc.php b/lib/api/ktcmis/exceptions/RuntimeException.inc.php new file mode 100644 index 0000000..cb1a5b2 --- /dev/null +++ b/lib/api/ktcmis/exceptions/RuntimeException.inc.php @@ -0,0 +1,7 @@ + diff --git a/lib/api/ktcmis/exceptions/StreamNotSupportedException.inc.php b/lib/api/ktcmis/exceptions/StreamNotSupportedException.inc.php new file mode 100644 index 0000000..4e29acb --- /dev/null +++ b/lib/api/ktcmis/exceptions/StreamNotSupportedException.inc.php @@ -0,0 +1,7 @@ + diff --git a/lib/api/ktcmis/exceptions/UpdateConflictException.inc.php b/lib/api/ktcmis/exceptions/UpdateConflictException.inc.php new file mode 100644 index 0000000..f3e8847 --- /dev/null +++ b/lib/api/ktcmis/exceptions/UpdateConflictException.inc.php @@ -0,0 +1,7 @@ + diff --git a/lib/api/ktcmis/exceptions/VersioningException.inc.php b/lib/api/ktcmis/exceptions/VersioningException.inc.php new file mode 100644 index 0000000..e84ff24 --- /dev/null +++ b/lib/api/ktcmis/exceptions/VersioningException.inc.php @@ -0,0 +1,7 @@ + diff --git a/lib/api/ktcmis/ktcmis.inc.php b/lib/api/ktcmis/ktcmis.inc.php index 8b38935..08776e8 100644 --- a/lib/api/ktcmis/ktcmis.inc.php +++ b/lib/api/ktcmis/ktcmis.inc.php @@ -468,7 +468,42 @@ class KTObjectService extends KTCMISBase { } /** - * Function to create a folder + * Creates a new document within the repository + * + * @param string $repositoryId The repository to which the document must be added + * @param string $typeId Object Type id for the document object being created + * @param array $properties Array of properties which must be applied to the created document object + * @param string $folderId The id of the folder which will be the parent of the created document object + * This parameter is optional IF unfilingCapability is supported + * @param contentStream $contentStream optional content stream data + * @param string $versioningState optional version state value: checkedout/major/minor + * @return string $objectId The id of the created folder object + */ + function createDocument($repositoryId, $typeId, $properties, $folderId = null, + $contentStream = null, $versioningState = 'Major') + { + $objectId = null; + + try { + $objectId = $this->ObjectService->createDocument($repositoryId, $typeId, $properties, $folderId, + $contentStream, $versioningState); + } + catch (Exception $e) + { + return array( + "status_code" => 1, + "message" => $e->getMessage() + ); + } + + return array( + 'status_code' => 0, + 'results' => $objectId + ); + } + + /** + * Creates a new folder within the repository * * @param string $repositoryId The repository to which the folder must be added * @param string $typeId Object Type id for the folder object being created @@ -497,6 +532,41 @@ class KTObjectService extends KTCMISBase { ); } + /** + * Sets the content stream data for an existing document + * + * if $overwriteFlag = TRUE, the new content stream is applied whether or not the document has an existing content stream + * if $overwriteFlag = FALSE, the new content stream is applied only if the document does not have an existing content stream + * + * NOTE A Repository MAY automatically create new Document versions as part of this service method. + * Therefore, the documentId output NEED NOT be identical to the documentId input. + * + * @param string $repositoryId + * @param string $documentId + * @param boolean $overwriteFlag + * @param string $contentStream + * @param string $changeToken + * @return string $documentId + */ + function setContentStream($repositoryId, $documentId, $overwriteFlag, $contentStream, $changeToken = null) + { + try { + $objectId = $this->ObjectService->setContentStream($repositoryId, $documentId, $overwriteFlag, $contentStream, $changeToken); + } + catch (Exception $e) + { + return array( + "status_code" => 1, + "message" => $e->getMessage() + ); + } + + return array( + 'status_code' => 0, + 'results' => $documentId + ); + } + } ?> diff --git a/lib/api/ktcmis/objecttypes/CMISDocumentObject.inc.php b/lib/api/ktcmis/objecttypes/CMISDocumentObject.inc.php index 7f45780..bfb6822 100644 --- a/lib/api/ktcmis/objecttypes/CMISDocumentObject.inc.php +++ b/lib/api/ktcmis/objecttypes/CMISDocumentObject.inc.php @@ -81,10 +81,9 @@ class CMISDocumentObject extends CMISBaseObject { $this->includedInSupertypeQuery = true; // // TODO determine what these next 3 should be $this->controllable = false; // - $this->versionable = false; // - $this->contentStreamAllowed = false; // - $this->contentStreamLength = 0; - $this->contentStreamMimeType = ''; + $this->versionable = true; // + $this->contentStreamAllowed = 'required'; // notAllowed/allowed/required + // properties $this->properties = new CMISDocumentPropertyCollection(); diff --git a/lib/api/ktcmis/services/CMISObjectService.inc.php b/lib/api/ktcmis/services/CMISObjectService.inc.php index 157d9ab..b40c7f0 100644 --- a/lib/api/ktcmis/services/CMISObjectService.inc.php +++ b/lib/api/ktcmis/services/CMISObjectService.inc.php @@ -2,8 +2,12 @@ require_once(KT_DIR . '/ktapi/ktapi.inc.php'); require_once(CMIS_DIR . '/exceptions/ConstraintViolationException.inc.php'); +require_once(CMIS_DIR . '/exceptions/ContentAlreadyExistsException.inc.php'); require_once(CMIS_DIR . '/exceptions/ObjectNotFoundException.inc.php'); require_once(CMIS_DIR . '/exceptions/StorageException.inc.php'); +require_once(CMIS_DIR . '/exceptions/StreamNotSupportedException.inc.php'); +require_once(CMIS_DIR . '/exceptions/UpdateConflictException.inc.php'); +require_once(CMIS_DIR . '/exceptions/VersioningException.inc.php'); require_once(CMIS_DIR . '/services/CMISRepositoryService.inc.php'); require_once(CMIS_DIR . '/objecttypes/CMISDocumentObject.inc.php'); require_once(CMIS_DIR . '/objecttypes/CMISFolderObject.inc.php'); @@ -68,7 +72,128 @@ class CMISObjectService { } /** - * Function to create a folder + * Creates a new document within the repository + * + * @param string $repositoryId The repository to which the document must be added + * @param string $typeId Object Type id for the document object being created + * @param array $properties Array of properties which must be applied to the created document object + * @param string $folderId The id of the folder which will be the parent of the created document object + * This parameter is optional IF unfilingCapability is supported + * @param contentStream $contentStream optional content stream data + * @param string $versioningState optional version state value: checkedout/major/minor + * @return string $objectId The id of the created folder object + */ + // TODO throw ConstraintViolationException if: + // value of any of the properties violates the min/max/required/length constraints + // specified in the property definition in the Object-Type. + // TODO throw ConstraintViolationException if: + // The Object-Type definition specified by the typeId parameter's "contentStreamAllowed" attribute + // is set to "required" and no contentStream input parameter is provided + // TODO throw ConstraintViolationException if: + // The Object-Type definition specified by the typeId parameter's "versionable" attribute + // is set to "false" and a value for the versioningState input parameter is provided + function createDocument($repositoryId, $typeId, $properties, $folderId = null, + $contentStream = null, $versioningState = 'Major') + { + $objectId = null; + + // fetch type definition of supplied type and check for base type "document", if not true throw exception + $RepositoryService = new CMISRepositoryService(); + try { + $typeDefinition = $RepositoryService->getTypeDefinition($repositoryId, $typeId); + } + // NOTE Not sure that we should throw this specific exception, maybe just let the underlying + // exception propogate upward... + // Alternatively: throw new exception with original exception message appended + // NOTE The latter method has been adopted for the moment + catch (Exception $e) + { + throw new ConstraintViolationException('Object is not of base type document. ' . $e->getMessage()); + } + + if ($typeDefinition['attributes']['baseType'] != 'document') + { + throw new ConstraintViolationException('Object is not of base type document'); + } + + // if no $folderId submitted and repository does not support "unfiling" throw exception + if (empty($folderId)) + { + $repositoryInfo = $RepositoryService->getRepositoryInfo($repositoryId); + $capabilities = $repositoryInfo->getCapabilities(); + if (!$capabilities->hasCapabilityUnfiling()) + { + throw new ConstraintViolationException('Repository does not support the Unfiling capability and no folder id was supplied'); + } + } + + // Attempt to decode $folderId, use as is if not detected as encoded + $tmpObjectId = $folderId; + $tmpTypeId = CMISUtil::decodeObjectId($tmpObjectId); + 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'); + 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. ' + . 'Refusing to create an empty document'); + } + else if (($typeDefinition['attributes']['contentStreamAllowed'] == 'notAllowed') && !empty($contentStream)) + { + throw new StreamNotSupportedException('Content Streams are not supported'); + } + + // 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) + { + throw new StorageException('The repository was unable to create the document - ' . $response['message']); + } + else + { + $objectId = CMISUtil::encodeObjectId('Document', $response['results']['id']); + } + + // now that the document object exists, create the content stream from the supplied data + if (!empty($contentStream)) + { + // TODO changeToken support + $changeToken = null; + $this->setContentStream($repositoryId, $objectId, false, $contentStream, $changeToken); + } + + return $objectId; + } + + /** + * Creates a new folder within the repository * * @param string $repositoryId The repository to which the folder must be added * @param string $typeId Object Type id for the folder object being created @@ -81,14 +206,20 @@ class CMISObjectService { // specified in the property definition in the Object-Type. function createFolder($repositoryId, $typeId, $properties, $folderId) { + $objectId = null; + // fetch type definition of supplied type and check for base type "folder", if not true throw exception $RepositoryService = new CMISRepositoryService(); try { $typeDefinition = $RepositoryService->getTypeDefinition($repositoryId, $typeId); } + // NOTE Not sure that we should throw this specific exception, maybe just let the underlying + // exception propogate upward... + // Alternatively: throw new exception with original exception message appended + // NOTE The latter method has been adopted for the moment catch (Exception $e) { - throw new ConstraintViolationException('Object is not of base type folder.'); + throw new ConstraintViolationException('Object is not of base type folder. ' . $e->getMessage()); } if ($typeDefinition['attributes']['baseType'] != 'folder') @@ -96,12 +227,11 @@ class CMISObjectService { throw new ConstraintViolationException('Object is not of base type folder'); } - // TODO determine whether this is in fact necessary or if we should require decoding in the calling code // Attempt to decode $folderId, use as is if not detected as encoded - $objectId = $folderId; - $tmpTypeId = CMISUtil::decodeObjectId($objectId); + $tmpObjectId = $folderId; + $tmpTypeId = CMISUtil::decodeObjectId($tmpObjectId); if ($tmpTypeId != 'Unknown') - $folderId = $objectId; + $folderId = $tmpObjectId; // if parent folder is not allowed to hold this type, throw exception $CMISFolder = new CMISFolderObject($folderId, $this->ktapi); @@ -115,7 +245,6 @@ class CMISObjectService { $response = $this->ktapi->create_folder($folderId, $properties['name'], $sig_username = '', $sig_password = '', $reason = ''); if ($response['status_code'] != 0) { - // throw storageException throw new StorageException('The repository was unable to create the folder - ' . $response['message']); } else @@ -126,6 +255,62 @@ class CMISObjectService { return $objectId; } + /** + * Sets the content stream data for an existing document + * + * if $overwriteFlag = TRUE, the new content stream is applied whether or not the document has an existing content stream + * if $overwriteFlag = FALSE, the new content stream is applied only if the document does not have an existing content stream + * + * NOTE A Repository MAY automatically create new Document versions as part of this service method. + * Therefore, the documentId output NEED NOT be identical to the documentId input. + * + * @param string $repositoryId + * @param string $documentId + * @param boolean $overwriteFlag + * @param string $contentStream + * @param string $changeToken + * @return string $documentId + */ + // TODO exceptions: + // updateConflictException: The operation is attempting to update an object that is no longer current + // (as determined by the repository). + // versioningException: The repository MAY throw this exception if the object is a non-current Document Version. + function setContentStream($repositoryId, $documentId, $overwriteFlag, $contentStream, $changeToken = null) + { + // fetch type definition of supplied document + $CMISDocument = new CMISDocumentObject($documentId, $this->ktapi); + + // if content stream is not allowed for this object type definition, throw a ConstraintViolationException + if (($CMISDocument->getAttribute('contentStreamAllowed') == 'notAllowed')) + { + // NOTE spec version 0.61c specifies both a ConstraintViolationException and a StreamNotSupportedException + // for this case. Choosing to throw StreamNotSupportedException until the specification is clarified + // as it is a more specific exception + throw new StreamNotSupportedException('Content Streams are not allowed for this object type'); + } + + $properties = $CMISDocument->getProperties(); + if (!empty($properties->getValue('ContentStreamFilename')) && (!$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) + { + throw new StorageException('Unable to update the content stream'); + } + + return $documentId; + } + } ?> diff --git a/tests/ktcmis/testCmisApi.php b/tests/ktcmis/testCmisApi.php index 94fd66d..96b2fb0 100644 --- a/tests/ktcmis/testCmisApi.php +++ b/tests/ktcmis/testCmisApi.php @@ -592,14 +592,14 @@ class CMISTestCase extends KTUnitTestCase { foreach($results as $key => $value) { ?>
$propval) { - if ($propval == '') continue; + if ($propval['value'] == '') continue; - $this->printRow($propkey, $propval); + $this->printRow($propkey, $propval['value']); } // now any children