diff --git a/ktapi/ktapi.inc.php b/ktapi/ktapi.inc.php index c911c0c..4d5c8a1 100644 --- a/ktapi/ktapi.inc.php +++ b/ktapi/ktapi.inc.php @@ -2928,7 +2928,7 @@ class KTAPI * @param string $tempfilename * @return kt_document_detail. status_code can be KTWS_ERR_INVALID_SESSION, KTWS_ERR_INVALID_FOLDER, KTWS_ERR_INVALID_DOCUMENT or KTWS_SUCCESS */ - public function checkin_document($document_id, $filename, $reason, $tempfilename, $major_update, + public function checkin_document($document_id, $filename, $reason, $tempfilename, $major_update, $sig_username = '', $sig_password = '') { $response = $this->_check_electronic_signature($document_id, $sig_username, $sig_password, $reason, $reason, diff --git a/lib/api/ktcmis/classes/CMISDocumentPropertyCollection.inc.php b/lib/api/ktcmis/classes/CMISDocumentPropertyCollection.inc.php index 0a19724..87c9498 100644 --- a/lib/api/ktcmis/classes/CMISDocumentPropertyCollection.inc.php +++ b/lib/api/ktcmis/classes/CMISDocumentPropertyCollection.inc.php @@ -71,6 +71,7 @@ class CMISDocumentPropertyCollection extends CMISPropertyCollection { 'ContentStreamMimeType' => 'propertyString', 'ContentStreamFilename' => 'propertyString', 'ContentStreamUri' => 'propertyUri', + 'IsLatestVersion' => 'propertyBoolean', 'IsVersionSeriesCheckedOut' => 'propertyBoolean', 'VersionSeriesCheckedOutBy' => 'propertyString', 'VersionSeriesCheckedOutId' => 'propertyId', diff --git a/lib/api/ktcmis/ktcmis.inc.php b/lib/api/ktcmis/ktcmis.inc.php index d59fb5b..34f6278 100644 --- a/lib/api/ktcmis/ktcmis.inc.php +++ b/lib/api/ktcmis/ktcmis.inc.php @@ -874,10 +874,10 @@ class KTVersioningService extends KTCMISBase { * @param string $checkinComment [optional] * @return string $documentId */ - public function checkIn($repositoryId, $documentId, $major, $changeToken = '', $properties = array(), $contentStream = null, $checkinComment = '') + public function checkIn($repositoryId, $documentId, $major, $contentStream = null, $changeToken = '', $properties = array(), $checkinComment = '') { try { - $result = $this->VersioningService->checkIn($repositoryId, $documentId, $major, $changeToken, $properties, $contentStream, $checkinComment); + $result = $this->VersioningService->checkIn($repositoryId, $documentId, $major, $contentStream, $changeToken, $properties, $checkinComment); } catch (Exception $e) { diff --git a/lib/api/ktcmis/services/CMISObjectService.inc.php b/lib/api/ktcmis/services/CMISObjectService.inc.php index 39d8d90..bbee385 100644 --- a/lib/api/ktcmis/services/CMISObjectService.inc.php +++ b/lib/api/ktcmis/services/CMISObjectService.inc.php @@ -61,7 +61,7 @@ class CMISObjectService { // 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()); + throw new ConstraintViolationException('Object base type could not be determined. ' . $e->getMessage()); } if ($typeDefinition['attributes']['baseType'] != 'document') @@ -103,49 +103,41 @@ class CMISObjectService { } } - if (!$typeAllowed) - { + if (!$typeAllowed) { throw new ConstraintViolationException('Parent folder may not hold objects of this type (' . $typeId . ')'); } // if content stream is required and no content stream is supplied, throw a ConstraintViolationException - if (($typeDefinition['attributes']['contentStreamAllowed'] == 'required') && is_null($contentStream)) - { + if (($typeDefinition['attributes']['contentStreamAllowed'] == 'required') && is_null($contentStream)) { 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)) - { + else if (($typeDefinition['attributes']['contentStreamAllowed'] == 'notAllowed') && !empty($contentStream)) { 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)) - { + if (!$typeDefinition['attributes']['versionable'] && !empty($versioningState)) { throw new ConstraintViolationException('This repository does not support versioning'); } // TODO deal with $versioningState when supplied // set title and name identical if only one submitted - if ($properties['title'] == '') - { + if ($properties['title'] == '') { $properties['title'] = $properties['name']; } - else if ($properties['name'] == '') - { + else if ($properties['name'] == '') { $properties['name'] = $properties['title']; } // if name is blank throw exception (check type) - using invalidArgument Exception for now - if (trim($properties['name']) == '') - { + if (trim($properties['name']) == '') { throw new InvalidArgumentException('Refusing to create an un-named document'); } // TODO also set to Default if a non-supported type is submitted - if ($properties['type'] == '') - { + if ($properties['type'] == '') { $properties['type'] = 'Default'; } @@ -154,17 +146,7 @@ class CMISObjectService { // this check isn't strictly necessary; however it is needed for a repository which does not support content streams if (!is_null($contentStream)) { - // 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 = CMISUtil::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_'); + $tempfilename = CMISUtil::createTemporaryFile($contentStream); // metadata $metadata = array(); @@ -192,12 +174,10 @@ class CMISObjectService { ); } - if (!empty($properties['category'])) - { + if (!empty($properties['category'])) { $category = $properties['category']; } - else - { + else { $category = 'Miscellaneous'; } @@ -231,12 +211,10 @@ class CMISObjectService { $KTMime = new KTMime(); $mimetype = $KTMime->getMimeTypeFromFile($tempfilename); preg_match('/^([^\/]*)\/([^\/]*)/', $mimetype, $matches); - if (($matches[1] == 'text') || ($matches[1] == 'image') || ($matches[1] == 'audio')) - { + if (($matches[1] == 'text') || ($matches[1] == 'image') || ($matches[1] == 'audio')) { $mediatype = ucwords($matches[1]); } - else if (($matches[2] == 'pdf') || ($matches[2] == 'msword')) - { + else if (($matches[2] == 'pdf') || ($matches[2] == 'msword')) { $mediatype = 'Text'; } @@ -262,12 +240,10 @@ class CMISObjectService { $response = $this->ktapi->add_document_with_metadata((int)$folderId, $properties['title'], $properties['name'], $properties['type'], $tempfilename, $metadata, $sysdata); - if ($response['status_code'] != 0) - { + if ($response['status_code'] != 0) { throw new StorageException('The repository was unable to create the document. ' . $response['message']); } - else - { + else { $objectId = CMISUtil::encodeObjectId('Document', $response['results']['document_id']); } @@ -314,13 +290,11 @@ class CMISObjectService { // 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) - { + catch (Exception $e) { throw new ConstraintViolationException('Object is not of base type folder. ' . $e->getMessage()); } - if ($typeDefinition['attributes']['baseType'] != 'folder') - { + if ($typeDefinition['attributes']['baseType'] != 'folder') { throw new ConstraintViolationException('Object is not of base type folder'); } @@ -333,20 +307,17 @@ class CMISObjectService { // if parent folder is not allowed to hold this type, throw exception $CMISFolder = new CMISFolderObject($folderId, $this->ktapi); $allowed = $CMISFolder->getProperty('AllowedChildObjectTypeIds'); - if (!is_array($allowed) || !in_array($typeId, $allowed)) - { + if (!is_array($allowed) || !in_array($typeId, $allowed)) { throw new ConstraintViolationException('Parent folder may not hold objects of this type (' . $typeId . ')'); } // TODO if name is blank! throw another exception (check type) - using invalidArgument Exception for now - if (trim($properties['name']) == '') - { + if (trim($properties['name']) == '') { throw new InvalidArgumentException('Refusing to create an un-named folder'); } $response = $this->ktapi->create_folder((int)$folderId, $properties['name'], $sig_username = '', $sig_password = '', $reason = ''); - if ($response['status_code'] != 0) - { + if ($response['status_code'] != 0) { throw new StorageException('The repository was unable to create the folder: ' . $response['message']); } else @@ -380,8 +351,7 @@ class CMISObjectService { $objectId = CMISUtil::decodeObjectId($objectId, $typeId); - if ($typeId == 'Unknown') - { + if ($typeId == 'Unknown') { throw new ObjectNotFoundException('The type of the requested object could not be determined'); } @@ -492,8 +462,7 @@ class CMISObjectService { // check type id of object against allowed child types for destination folder $CMISFolder = new CMISFolderObject($targetFolderId, $this->ktapi); $allowed = $CMISFolder->getProperty('AllowedChildObjectTypeIds'); - if (!is_array($allowed) || !in_array($typeId, $allowed)) - { + if (!is_array($allowed) || !in_array($typeId, $allowed)) { throw new ConstraintViolationException('Parent folder may not hold objects of this type (' . $typeId . ')'); } @@ -517,8 +486,7 @@ class CMISObjectService { } // if failed, throw StorageException - if ($response['status_code'] != 0) - { + if ($response['status_code'] != 0) { throw new StorageException('The repository was unable to move the object: ' . $response['message']); } } @@ -541,13 +509,15 @@ class CMISObjectService { // TODO this should probably be a function, it is now used in two places... // throw updateConflictException if the operation is attempting to update an object that is no longer current (as determined by the repository). $exists = true; - if ($typeId == 'Folder') { + if ($typeId == 'Folder') + { $object = $this->ktapi->get_folder_by_id($objectId); if (PEAR::isError($object)) { $exists = false; } } - else if ($typeId == 'Document') { + else if ($typeId == 'Document') + { $object = $this->ktapi->get_document_by_id($objectId); if (PEAR::isError($object)) { $exists = false; @@ -743,11 +713,7 @@ class CMISObjectService { throw new ContentAlreadyExistsException('Unable to overwrite existing content stream'); } - // 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(); - $tempfilename = $uploadManager->store_base64_file($contentStream, 'cmis_'); + $tempfilename = CMISUtil::createTemporaryFile($contentStream); // 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', $tempfilename, false); diff --git a/lib/api/ktcmis/services/CMISVersioningService.inc.php b/lib/api/ktcmis/services/CMISVersioningService.inc.php index 345086e..5ac4f80 100644 --- a/lib/api/ktcmis/services/CMISVersioningService.inc.php +++ b/lib/api/ktcmis/services/CMISVersioningService.inc.php @@ -3,11 +3,12 @@ require_once(KT_DIR . '/ktapi/ktapi.inc.php'); require_once(CMIS_DIR . '/exceptions/ConstraintViolationException.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/CMISObjectService.inc.php'); require_once(CMIS_DIR . '/objecttypes/CMISDocumentObject.inc.php'); -//require_once(CMIS_DIR . '/util/CMISUtil.inc.php'); +require_once(CMIS_DIR . '/util/CMISUtil.inc.php'); class CMISVersioningService { @@ -182,14 +183,8 @@ class CMISVersioningService { * @return string $documentId */ // TODO Exceptions: - // • ConstraintViolationException - SHALL throw if o The Document’s Object-Type definition’s versionable attribute is FALSE. - // • storageException - MAY throw - // • streamNotSupportedException - The Repository SHALL throw this exception if the Object-Type definition specified by the typeId - // parameter’s “contentStreamAllowed” attribute is set to “not allowed” and a contentStream input - // parameter is provided. - // • updateConflictException - MAY throw // • versioningException - The repository MAY throw this exception if the object is a non-current Document Version - public function checkIn($repositoryId, $documentId, $major, $changeToken = '', $properties = array(), $contentStream = null, $checkinComment = '') + public function checkIn($repositoryId, $documentId, $major, $contentStream = null, $changeToken = '', $properties = array(), $checkinComment = '') { $documentId = CMISUtil::decodeObjectId($documentId, $typeId); @@ -206,7 +201,35 @@ class CMISVersioningService { throw new ConstraintViolationException('This document is not versionable and may not be checked in'); } - return $documentId; + $RepositoryService = new CMISRepositoryService(); + try { + $typeDefinition = $RepositoryService->getTypeDefinition($repositoryId, $typeId); + } + catch (exception $e) { + // if we can't get the type definition, then we can't store the content + throw new StorageException($e->getMessage()); + } + + if (($typeDefinition['attributes']['contentStreamAllowed'] == 'notAllowed') && !empty($contentStream)) { + throw new StreamNotSupportedException('Content Streams are not supported'); + } + + // check that this is the latest version + if ($pwc->getProperty('IsLatestVersion') != true) { + throw new VersioningException('The document is not the latest version and cannot be checked in'); + } + + // now do the checkin + $tempfilename = CMISUtil::createTemporaryFile($contentStream); + $response = $this->ktapi->checkin_document($documentId, $pwc->getProperty('ContentStreamFilename'), $reason, $tempfilename, $major, + $sig_username, $sig_password); + + // if there was any error in cancelling the checkout + if ($response['status_code'] == 1) { + throw new RuntimeException('There was an error checking in the document: ' . $response['message']); + } + + return CMISUtil::encodeObjectId(DOCUMENT, $documentId); } } diff --git a/lib/api/ktcmis/util/CMISUtil.inc.php b/lib/api/ktcmis/util/CMISUtil.inc.php index e9d0efd..c116ded 100644 --- a/lib/api/ktcmis/util/CMISUtil.inc.php +++ b/lib/api/ktcmis/util/CMISUtil.inc.php @@ -406,30 +406,6 @@ class CMISUtil { 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; - } - // TODO more robust base64 encoding detection, if possible /** @@ -610,6 +586,33 @@ class CMISUtil { return $exists; } + + /** + * Creates a temporary file + * Cleanup is the responsibility of the calling code + * + * @param string $contentStream The content to be stored (assumed to be base64) + * @return string The path to the created file (for reference and cleanup.) + */ + static public function createTemporaryFile($contentStream) + { + // 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 = CMISUtil::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; + } } 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 ef12863..d194cfc 100644 --- a/webservice/atompub/cmis/KT_cmis_atom_server.services.inc.php +++ b/webservice/atompub/cmis/KT_cmis_atom_server.services.inc.php @@ -101,7 +101,7 @@ class KT_cmis_atom_service_folder extends KT_cmis_atom_service { $feed = KT_cmis_atom_service_helper::getObjectFeed($this, $ObjectService, $repositoryId, $folderId); } - //Expose the responseFeed + // Expose the responseFeed $this->responseFeed = $feed; } @@ -140,7 +140,7 @@ class KT_cmis_atom_service_folder extends KT_cmis_atom_service { $objectId = $this->params[2]; } - $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'])) @@ -159,7 +159,7 @@ class KT_cmis_atom_service_folder extends KT_cmis_atom_service { CMISUtil::decodeObjectId($objectId, $typeId); } - // now check for content stream + // check for content stream $content = KT_cmis_atom_service_helper::getAtomValues($this->parsedXMLContent['@children'], 'content'); // TODO this will possibly need to change somewhat once Relationship Objects come into play. @@ -207,7 +207,7 @@ class KT_cmis_atom_service_folder extends KT_cmis_atom_service { $feed = KT_cmis_atom_service_helper::getErrorFeed($this, self::STATUS_SERVER_ERROR, $error); } - //Expose the responseFeed + // Expose the responseFeed $this->responseFeed = $feed; } @@ -236,7 +236,7 @@ class KT_cmis_atom_service_folder extends KT_cmis_atom_service { if (PEAR::isError($response)) { $feed = KT_cmis_atom_service_helper::getErrorFeed($this, self::STATUS_SERVER_ERROR, $response->getMessage()); - //Expose the responseFeed + // Expose the responseFeed $this->responseFeed = $feed; return null; } @@ -349,7 +349,7 @@ class KT_cmis_atom_service_types extends KT_cmis_atom_service { $type = ((empty($this->params[0])) ? 'all' : $this->params[0]); $feed = KT_cmis_atom_service_helper::getTypeFeed($type, $types); - //Expose the responseFeed + // Expose the responseFeed $this->responseFeed = $feed; } @@ -385,7 +385,7 @@ class KT_cmis_atom_service_type extends KT_cmis_atom_service { $feed = $this->getTypeChildrenFeed($this->params[1]); } - //Expose the responseFeed + // Expose the responseFeed $this->responseFeed=$feed; } @@ -494,7 +494,7 @@ class KT_cmis_atom_service_checkedout extends KT_cmis_atom_service { // $entry = null; // $feed->newField('cmis:hasMoreItems', 'false', $entry, true); - //Expose the responseFeed + // Expose the responseFeed $this->responseFeed = $feed; } @@ -513,7 +513,7 @@ class KT_cmis_atom_service_checkedout extends KT_cmis_atom_service { if (empty($cmisObjectProperties['ObjectId'])) { $feed = KT_cmis_atom_service_helper::getErrorFeed($this, self::STATUS_SERVER_ERROR, 'No object was specified for checkout'); - //Expose the responseFeed + // Expose the responseFeed $this->responseFeed = $feed; return null; } @@ -523,7 +523,7 @@ class KT_cmis_atom_service_checkedout extends KT_cmis_atom_service { if (PEAR::isError($response)) { $feed = KT_cmis_atom_service_helper::getErrorFeed($this, self::STATUS_SERVER_ERROR, 'No object was specified for checkout'); - //Expose the responseFeed + // Expose the responseFeed $this->responseFeed = $feed; return null; } @@ -531,7 +531,7 @@ 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'); - //Expose the responseFeed + // Expose the responseFeed $this->responseFeed = $feed; } @@ -559,13 +559,13 @@ class KT_cmis_atom_service_document extends KT_cmis_atom_service { // this depends on $this->params[1] if (!empty($this->params[1])) { - $this->getContentStream($ObjectService, $repositoryId); + KT_cmis_atom_service_helper::downloadContentStream($this, $ObjectService, $repositoryId); return null; } $feed = KT_cmis_atom_service_helper::getObjectFeed($this, $ObjectService, $repositoryId, $this->params[0]); - //Expose the responseFeed + // Expose the responseFeed $this->responseFeed = $feed; } @@ -594,7 +594,7 @@ class KT_cmis_atom_service_document extends KT_cmis_atom_service { if (PEAR::isError($response)) { $feed = KT_cmis_atom_service_helper::getErrorFeed($this, self::STATUS_SERVER_ERROR, $response->getMessage()); - //Expose the responseFeed + // Expose the responseFeed $this->responseFeed = $feed; return null; } @@ -603,46 +603,6 @@ class KT_cmis_atom_service_document extends KT_cmis_atom_service { $this->setStatus(self::STATUS_NO_CONTENT); } - private function getContentStream(&$ObjectService, $repositoryId) - { - $response = $ObjectService->getProperties($repositoryId, $this->params[0], false, false); - if (PEAR::isError($response)) { - $feed = KT_cmis_atom_service_helper::getErrorFeed($this, KT_cmis_atom_service::STATUS_SERVER_ERROR, $response->getMessage()); - $this->responseFeed = $feed; - return null; - } - - // TODO also check If-Modified-Since? -// $this->headers['If-Modified-Since'] => 2009-07-24 17:16:54 - - $this->contentDownload = true; - $eTag = md5($response['properties']['LastModificationDate']['value'] . $response['properties']['ContentStreamLength']['value']); - - if ($this->headers['If-None-Match'] == $eTag) - { - $this->setStatus(self::STATUS_NOT_MODIFIED); - $this->contentDownload = false; - return null; - } - - $contentStream = $ObjectService->getContentStream($repositoryId, $this->params[0]); - - // headers specific to output - $this->setEtag($eTag); - $this->setHeader('Last-Modified', $response['properties']['LastModificationDate']['value']); - - if (!empty($response['properties']['ContentStreamMimeType']['value'])) { - $this->setHeader('Content-type', $response['properties']['ContentStreamMimeType']['value'] . ';charset=utf-8'); - } - else { - $this->setHeader('Content-type', 'text/plain;charset=utf-8'); - } - - $this->setHeader('Content-Disposition', 'attachment;filename="' . $response['properties']['ContentStreamFilename']['value'] . '"'); - $this->setHeader('Content-Length', $response['properties']['ContentStreamLength']['value']); - $this->output = $contentStream; - } - } class KT_cmis_atom_service_pwc extends KT_cmis_atom_service { @@ -665,13 +625,13 @@ class KT_cmis_atom_service_pwc extends KT_cmis_atom_service { // this depends on $this->params[1] if (!empty($this->params[1])) { - $this->getContentStream($ObjectService, $repositoryId); + KT_cmis_atom_service_helper::downloadContentStream($this, $ObjectService, $repositoryId); return null; } $feed = KT_cmis_atom_service_helper::getObjectFeed($this, $ObjectService, $repositoryId, $this->params[0]); - //Expose the responseFeed + // Expose the responseFeed $this->responseFeed = $feed; } @@ -696,7 +656,7 @@ class KT_cmis_atom_service_pwc extends KT_cmis_atom_service { if (PEAR::isError($response)) { $feed = KT_cmis_atom_service_helper::getErrorFeed($this, self::STATUS_SERVER_ERROR, $response->getMessage()); - //Expose the responseFeed + // Expose the responseFeed $this->responseFeed = $feed; return null; } @@ -714,18 +674,52 @@ class KT_cmis_atom_service_pwc extends KT_cmis_atom_service { $repositories = $RepositoryService->getRepositories(); $repositoryId = $repositories[0]['repositoryId']; - $response = $VersioningService->checkIn($repositoryId, $this->params[0]); + // check for content stream + // NOTE this is a hack! will not work with CMISSpaces at least, probably not with any client except RestTest and similar + // where we can manually modify the input + // first we try for an atom content tag + $content = KT_cmis_atom_service_helper::getAtomValues($this->parsedXMLContent['@children'], 'content'); + if (!empty($content)) { + $contentStream = $content; + } + // not found? try for a regular content tag + else { + $content = KT_cmis_atom_service_helper::findTag('content', $this->parsedXMLContent['@children'], null, false); + $contentStream = $content['@value']; + } + + // if we haven't found it now, the real hack begins - retrieve the EXISTING content and submit this as the contentStream + // this is needed because KnowledgeTree will not accept a checkin without a content stream but CMISSpaces (and possibly + // other CMIS clients are the same, does not send a content stream on checkin nor does it offer the user a method to choose one) + // NOTE that if the content is INTENDED to be empty this and all the above checks will FAIL! + // FIXME this is horrible, terrible, ugly and bad! + if (empty($contentStream)) + { + $ObjectService = new ObjectService(KT_cmis_atom_service_helper::getKt()); + $contentStream = base64_encode(KT_cmis_atom_service_helper::getContentStream($this, $ObjectService, $repositoryId)); + } + + // and if we don't have it by now, we give up...but leave the error to be generated by the underlying KnowledgeTree code + + // checkin function call + // TODO dynamically detect version change type - leaving this for now as the CMIS clients tested do not appear to + // offer the choice to the user - perhaps it will turn out that this will come from somewhere else but for now + // we assume minor version updates only + $major = false; + $response = $VersioningService->checkIn($repositoryId, $this->params[0], $major, $contentStream); if (PEAR::isError($response)) { $feed = KT_cmis_atom_service_helper::getErrorFeed($this, self::STATUS_SERVER_ERROR, $response->getMessage()); - //Expose the responseFeed + // Expose the responseFeed $this->responseFeed = $feed; return null; } - $this->setStatus(self::STATUS_NO_CONTENT); - $this->responseFeed = null; + $feed = KT_cmis_atom_service_helper::getObjectFeed($this, $ObjectService, $repositoryId, $this->params[0]); + + // Expose the responseFeed + $this->responseFeed = $feed; } } 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 f3007a9..078e5fc 100644 --- a/webservice/atompub/cmis/KT_cmis_atom_service_helper.inc.php +++ b/webservice/atompub/cmis/KT_cmis_atom_service_helper.inc.php @@ -418,7 +418,7 @@ class KT_cmis_atom_service_helper { while($start < $numFolders) { $name = $path[$numQ-$numFolders+$start]; - // hack to fix drupal url encoding issue + // fix for possible url encoding issue $name = str_replace('%2520', '%20', $name); $folderName = urldecode($name); @@ -494,6 +494,71 @@ class KT_cmis_atom_service_helper { return date('Y-m-d H:i:s', $time); } + /** + * Fetches the document content stream for internal use + * + * @param object $ObjectService + * @param string $repositoryId + * @return null | string $contentStream + */ + static public function getContentStream(&$service, &$ObjectService, $repositoryId) + { + $response = $ObjectService->getProperties($repositoryId, $service->params[0], false, false); + if (PEAR::isError($response)) { + return null; + } + + $contentStream = $ObjectService->getContentStream($repositoryId, $service->params[0]); + + return $contentStream; + } + /** + * Fetches and prepares the document content stream for download/viewing + * + * @param object $ObjectService + * @param string $repositoryId + * @return null | nothing + */ + static public function downloadContentStream(&$service, &$ObjectService, $repositoryId) + { + $response = $ObjectService->getProperties($repositoryId, $service->params[0], false, false); + if (PEAR::isError($response)) { + $feed = KT_cmis_atom_service_helper::getErrorFeed($service, KT_cmis_atom_service::STATUS_SERVER_ERROR, $response->getMessage()); + $service->responseFeed = $feed; + return null; + } + + // TODO also check If-Modified-Since? +// $service->headers['If-Modified-Since'] => 2009-07-24 17:16:54 + + $service->setContentDownload(true); + $eTag = md5($response['properties']['LastModificationDate']['value'] . $response['properties']['ContentStreamLength']['value']); + + if ($service->headers['If-None-Match'] == $eTag) + { + $service->setStatus(KT_cmis_atom_service::STATUS_NOT_MODIFIED); + $service->setContentDownload(false); + return null; + } + + $contentStream = $ObjectService->getContentStream($repositoryId, $service->params[0]); + + // headers specific to output + $service->setEtag($eTag); + $service->setHeader('Last-Modified', $response['properties']['LastModificationDate']['value']); + + if (!empty($response['properties']['ContentStreamMimeType']['value'])) { + $service->setHeader('Content-type', $response['properties']['ContentStreamMimeType']['value'] . ';charset=utf-8'); + } + else { + $service->setHeader('Content-type', 'text/plain;charset=utf-8'); + } + + $service->setHeader('Content-Disposition', 'attachment;filename="' . $response['properties']['ContentStreamFilename']['value'] . '"'); + $service->setHeader('Content-Length', $response['properties']['ContentStreamLength']['value']); + $service->setOutput($contentStream); + } + //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(); diff --git a/webservice/classes/atompub/KT_atom_service.inc.php b/webservice/classes/atompub/KT_atom_service.inc.php index 832a1b2..c20f178 100644 --- a/webservice/classes/atompub/KT_atom_service.inc.php +++ b/webservice/classes/atompub/KT_atom_service.inc.php @@ -147,7 +147,7 @@ class KT_atom_service{ header("HTTP/1.1 ".$status); } - protected function setEtag($etagValue=NULL){ + public function setEtag($etagValue=NULL){ if($etagValue)header('ETag: '.$etagValue); } diff --git a/webservice/classes/atompub/cmis/KT_cmis_atom_service.inc.php b/webservice/classes/atompub/cmis/KT_cmis_atom_service.inc.php index 0b6a44d..acdf7d3 100644 --- a/webservice/classes/atompub/cmis/KT_cmis_atom_service.inc.php +++ b/webservice/classes/atompub/cmis/KT_cmis_atom_service.inc.php @@ -8,7 +8,12 @@ class KT_cmis_atom_service extends KT_atom_service { protected $serviceType = null; protected $contentDownload = false; - + + public function setContentDownload($contentDownload) + { + $this->contentDownload = $contentDownload; + } + public public function isContentDownload() { return $this->contentDownload; @@ -19,6 +24,11 @@ class KT_cmis_atom_service extends KT_atom_service { return $this->status == self::STATUS_NOT_MODIFIED; } + public function setoutput($output) + { + $this->output = $output; + } + public function getOutput() { return $this->output; @@ -29,7 +39,7 @@ class KT_cmis_atom_service extends KT_atom_service { return $this->serviceType; } - protected function setHeader($header = null, $value = null) + public function setHeader($header = null, $value = null) { if ($header) header($header . ': ' . $value); } diff --git a/webservice/classes/atompub/cmis/VersioningService.inc.php b/webservice/classes/atompub/cmis/VersioningService.inc.php index 82cbf4d..ed109a8 100644 --- a/webservice/classes/atompub/cmis/VersioningService.inc.php +++ b/webservice/classes/atompub/cmis/VersioningService.inc.php @@ -85,9 +85,9 @@ class VersioningService extends KTVersioningService { * @param string $checkinComment [optional] * @return string $documentId */ - public function checkIn($repositoryId, $documentId, $major, $changeToken = '', $properties = array(), $contentStream = null, $checkinComment = '') + public function checkIn($repositoryId, $documentId, $major, $contentStream = null, $changeToken = '', $properties = array(), $checkinComment = '') { - $result = parent::checkIn($repositoryId, $documentId, $major, $changeToken, $properties, $contentStream, $checkinComment); + $result = parent::checkIn($repositoryId, $documentId, $major, $contentStream, $changeToken, $properties, $checkinComment); if ($result['status_code'] == 0) { return $result['results'];