Commit d6f4d51f6b123dcc539e8286445fa89a27a45885

Authored by Paul Barrett
1 parent 53ab10ed

Added API and AtomPub support for document checkin via CMIS (note: document cont…

…ent update not included as the tested clients do not allow it.)

Story ID:1093056

Committed by: Paul Barrett

Reviewed by: Jarrett Jordaan
ktapi/ktapi.inc.php
... ... @@ -2928,7 +2928,7 @@ class KTAPI
2928 2928 * @param string $tempfilename
2929 2929 * @return kt_document_detail. status_code can be KTWS_ERR_INVALID_SESSION, KTWS_ERR_INVALID_FOLDER, KTWS_ERR_INVALID_DOCUMENT or KTWS_SUCCESS
2930 2930 */
2931   - public function checkin_document($document_id, $filename, $reason, $tempfilename, $major_update,
  2931 + public function checkin_document($document_id, $filename, $reason, $tempfilename, $major_update,
2932 2932 $sig_username = '', $sig_password = '')
2933 2933 {
2934 2934 $response = $this->_check_electronic_signature($document_id, $sig_username, $sig_password, $reason, $reason,
... ...
lib/api/ktcmis/classes/CMISDocumentPropertyCollection.inc.php
... ... @@ -71,6 +71,7 @@ class CMISDocumentPropertyCollection extends CMISPropertyCollection {
71 71 'ContentStreamMimeType' => 'propertyString',
72 72 'ContentStreamFilename' => 'propertyString',
73 73 'ContentStreamUri' => 'propertyUri',
  74 + 'IsLatestVersion' => 'propertyBoolean',
74 75 'IsVersionSeriesCheckedOut' => 'propertyBoolean',
75 76 'VersionSeriesCheckedOutBy' => 'propertyString',
76 77 'VersionSeriesCheckedOutId' => 'propertyId',
... ...
lib/api/ktcmis/ktcmis.inc.php
... ... @@ -874,10 +874,10 @@ class KTVersioningService extends KTCMISBase {
874 874 * @param string $checkinComment [optional]
875 875 * @return string $documentId
876 876 */
877   - public function checkIn($repositoryId, $documentId, $major, $changeToken = '', $properties = array(), $contentStream = null, $checkinComment = '')
  877 + public function checkIn($repositoryId, $documentId, $major, $contentStream = null, $changeToken = '', $properties = array(), $checkinComment = '')
878 878 {
879 879 try {
880   - $result = $this->VersioningService->checkIn($repositoryId, $documentId, $major, $changeToken, $properties, $contentStream, $checkinComment);
  880 + $result = $this->VersioningService->checkIn($repositoryId, $documentId, $major, $contentStream, $changeToken, $properties, $checkinComment);
881 881 }
882 882 catch (Exception $e)
883 883 {
... ...
lib/api/ktcmis/services/CMISObjectService.inc.php
... ... @@ -61,7 +61,7 @@ class CMISObjectService {
61 61 // NOTE The latter method has been adopted for the moment
62 62 catch (Exception $e)
63 63 {
64   - throw new ConstraintViolationException('Object is not of base type document. ' . $e->getMessage());
  64 + throw new ConstraintViolationException('Object base type could not be determined. ' . $e->getMessage());
65 65 }
66 66  
67 67 if ($typeDefinition['attributes']['baseType'] != 'document')
... ... @@ -103,49 +103,41 @@ class CMISObjectService {
103 103 }
104 104 }
105 105  
106   - if (!$typeAllowed)
107   - {
  106 + if (!$typeAllowed) {
108 107 throw new ConstraintViolationException('Parent folder may not hold objects of this type (' . $typeId . ')');
109 108 }
110 109  
111 110 // if content stream is required and no content stream is supplied, throw a ConstraintViolationException
112   - if (($typeDefinition['attributes']['contentStreamAllowed'] == 'required') && is_null($contentStream))
113   - {
  111 + if (($typeDefinition['attributes']['contentStreamAllowed'] == 'required') && is_null($contentStream)) {
114 112 throw new ConstraintViolationException('This repository requires a content stream for document creation. '
115 113 . 'Refusing to create an empty document');
116 114 }
117   - else if (($typeDefinition['attributes']['contentStreamAllowed'] == 'notAllowed') && !empty($contentStream))
118   - {
  115 + else if (($typeDefinition['attributes']['contentStreamAllowed'] == 'notAllowed') && !empty($contentStream)) {
119 116 throw new StreamNotSupportedException('Content Streams are not supported');
120 117 }
121 118  
122 119 // if versionable attribute is set to false and versioningState is supplied, throw a ConstraintViolationException
123   - if (!$typeDefinition['attributes']['versionable'] && !empty($versioningState))
124   - {
  120 + if (!$typeDefinition['attributes']['versionable'] && !empty($versioningState)) {
125 121 throw new ConstraintViolationException('This repository does not support versioning');
126 122 }
127 123  
128 124 // TODO deal with $versioningState when supplied
129 125  
130 126 // set title and name identical if only one submitted
131   - if ($properties['title'] == '')
132   - {
  127 + if ($properties['title'] == '') {
133 128 $properties['title'] = $properties['name'];
134 129 }
135   - else if ($properties['name'] == '')
136   - {
  130 + else if ($properties['name'] == '') {
137 131 $properties['name'] = $properties['title'];
138 132 }
139 133  
140 134 // if name is blank throw exception (check type) - using invalidArgument Exception for now
141   - if (trim($properties['name']) == '')
142   - {
  135 + if (trim($properties['name']) == '') {
143 136 throw new InvalidArgumentException('Refusing to create an un-named document');
144 137 }
145 138  
146 139 // TODO also set to Default if a non-supported type is submitted
147   - if ($properties['type'] == '')
148   - {
  140 + if ($properties['type'] == '') {
149 141 $properties['type'] = 'Default';
150 142 }
151 143  
... ... @@ -154,17 +146,7 @@ class CMISObjectService {
154 146 // this check isn't strictly necessary; however it is needed for a repository which does not support content streams
155 147 if (!is_null($contentStream))
156 148 {
157   - // TODO consider checking whether content is encoded (currently we expect encoded)
158   - // TODO choose between this and the alternative decode function (see CMISUtil class)
159   - // this will require some basic benchmarking
160   - $contentStream = CMISUtil::decodeChunkedContentStream($contentStream);
161   -
162   - // NOTE There is a function in CMISUtil to do this, written for the unit tests but since KTUploadManager exists
163   - // and has more functionality which could come in useful at some point I decided to go with that instead
164   - // (did not know this existed when I wrote the CMISUtil function)
165   - $uploadManager = new KTUploadManager();
166   - // assumes already decoded from base64, should use store_base64_file if not
167   - $tempfilename = $uploadManager->store_file($contentStream, 'cmis_');
  149 + $tempfilename = CMISUtil::createTemporaryFile($contentStream);
168 150  
169 151 // metadata
170 152 $metadata = array();
... ... @@ -192,12 +174,10 @@ class CMISObjectService {
192 174 );
193 175 }
194 176  
195   - if (!empty($properties['category']))
196   - {
  177 + if (!empty($properties['category'])) {
197 178 $category = $properties['category'];
198 179 }
199   - else
200   - {
  180 + else {
201 181 $category = 'Miscellaneous';
202 182 }
203 183  
... ... @@ -231,12 +211,10 @@ class CMISObjectService {
231 211 $KTMime = new KTMime();
232 212 $mimetype = $KTMime->getMimeTypeFromFile($tempfilename);
233 213 preg_match('/^([^\/]*)\/([^\/]*)/', $mimetype, $matches);
234   - if (($matches[1] == 'text') || ($matches[1] == 'image') || ($matches[1] == 'audio'))
235   - {
  214 + if (($matches[1] == 'text') || ($matches[1] == 'image') || ($matches[1] == 'audio')) {
236 215 $mediatype = ucwords($matches[1]);
237 216 }
238   - else if (($matches[2] == 'pdf') || ($matches[2] == 'msword'))
239   - {
  217 + else if (($matches[2] == 'pdf') || ($matches[2] == 'msword')) {
240 218 $mediatype = 'Text';
241 219 }
242 220  
... ... @@ -262,12 +240,10 @@ class CMISObjectService {
262 240 $response = $this->ktapi->add_document_with_metadata((int)$folderId, $properties['title'], $properties['name'],
263 241 $properties['type'], $tempfilename, $metadata, $sysdata);
264 242  
265   - if ($response['status_code'] != 0)
266   - {
  243 + if ($response['status_code'] != 0) {
267 244 throw new StorageException('The repository was unable to create the document. ' . $response['message']);
268 245 }
269   - else
270   - {
  246 + else {
271 247 $objectId = CMISUtil::encodeObjectId('Document', $response['results']['document_id']);
272 248 }
273 249  
... ... @@ -314,13 +290,11 @@ class CMISObjectService {
314 290 // exception propogate upward...
315 291 // Alternatively: throw new exception with original exception message appended
316 292 // NOTE The latter method has been adopted for the moment
317   - catch (Exception $e)
318   - {
  293 + catch (Exception $e) {
319 294 throw new ConstraintViolationException('Object is not of base type folder. ' . $e->getMessage());
320 295 }
321 296  
322   - if ($typeDefinition['attributes']['baseType'] != 'folder')
323   - {
  297 + if ($typeDefinition['attributes']['baseType'] != 'folder') {
324 298 throw new ConstraintViolationException('Object is not of base type folder');
325 299 }
326 300  
... ... @@ -333,20 +307,17 @@ class CMISObjectService {
333 307 // if parent folder is not allowed to hold this type, throw exception
334 308 $CMISFolder = new CMISFolderObject($folderId, $this->ktapi);
335 309 $allowed = $CMISFolder->getProperty('AllowedChildObjectTypeIds');
336   - if (!is_array($allowed) || !in_array($typeId, $allowed))
337   - {
  310 + if (!is_array($allowed) || !in_array($typeId, $allowed)) {
338 311 throw new ConstraintViolationException('Parent folder may not hold objects of this type (' . $typeId . ')');
339 312 }
340 313  
341 314 // TODO if name is blank! throw another exception (check type) - using invalidArgument Exception for now
342   - if (trim($properties['name']) == '')
343   - {
  315 + if (trim($properties['name']) == '') {
344 316 throw new InvalidArgumentException('Refusing to create an un-named folder');
345 317 }
346 318  
347 319 $response = $this->ktapi->create_folder((int)$folderId, $properties['name'], $sig_username = '', $sig_password = '', $reason = '');
348   - if ($response['status_code'] != 0)
349   - {
  320 + if ($response['status_code'] != 0) {
350 321 throw new StorageException('The repository was unable to create the folder: ' . $response['message']);
351 322 }
352 323 else
... ... @@ -380,8 +351,7 @@ class CMISObjectService {
380 351  
381 352 $objectId = CMISUtil::decodeObjectId($objectId, $typeId);
382 353  
383   - if ($typeId == 'Unknown')
384   - {
  354 + if ($typeId == 'Unknown') {
385 355 throw new ObjectNotFoundException('The type of the requested object could not be determined');
386 356 }
387 357  
... ... @@ -492,8 +462,7 @@ class CMISObjectService {
492 462 // check type id of object against allowed child types for destination folder
493 463 $CMISFolder = new CMISFolderObject($targetFolderId, $this->ktapi);
494 464 $allowed = $CMISFolder->getProperty('AllowedChildObjectTypeIds');
495   - if (!is_array($allowed) || !in_array($typeId, $allowed))
496   - {
  465 + if (!is_array($allowed) || !in_array($typeId, $allowed)) {
497 466 throw new ConstraintViolationException('Parent folder may not hold objects of this type (' . $typeId . ')');
498 467 }
499 468  
... ... @@ -517,8 +486,7 @@ class CMISObjectService {
517 486 }
518 487  
519 488 // if failed, throw StorageException
520   - if ($response['status_code'] != 0)
521   - {
  489 + if ($response['status_code'] != 0) {
522 490 throw new StorageException('The repository was unable to move the object: ' . $response['message']);
523 491 }
524 492 }
... ... @@ -541,13 +509,15 @@ class CMISObjectService {
541 509 // TODO this should probably be a function, it is now used in two places...
542 510 // throw updateConflictException if the operation is attempting to update an object that is no longer current (as determined by the repository).
543 511 $exists = true;
544   - if ($typeId == 'Folder') {
  512 + if ($typeId == 'Folder')
  513 + {
545 514 $object = $this->ktapi->get_folder_by_id($objectId);
546 515 if (PEAR::isError($object)) {
547 516 $exists = false;
548 517 }
549 518 }
550   - else if ($typeId == 'Document') {
  519 + else if ($typeId == 'Document')
  520 + {
551 521 $object = $this->ktapi->get_document_by_id($objectId);
552 522 if (PEAR::isError($object)) {
553 523 $exists = false;
... ... @@ -743,11 +713,7 @@ class CMISObjectService {
743 713 throw new ContentAlreadyExistsException('Unable to overwrite existing content stream');
744 714 }
745 715  
746   - // NOTE There is a function in CMISUtil to do this but since KTUploadManager exists and has more functionality
747   - // which could come in useful at some point I decided to go with that instead (did not know it existed when
748   - // I wrote the CMISUtil function)
749   - $uploadManager = new KTUploadManager();
750   - $tempfilename = $uploadManager->store_base64_file($contentStream, 'cmis_');
  716 + $tempfilename = CMISUtil::createTemporaryFile($contentStream);
751 717 // update the document content from this temporary file as per usual
752 718 // TODO Use checkin_document_with_metadata instead if metadata content submitted || update metadata separately?
753 719 $response = $this->ktapi->checkin_document($documentId, $csFileName, 'CMIS setContentStream action', $tempfilename, false);
... ...
lib/api/ktcmis/services/CMISVersioningService.inc.php
... ... @@ -3,11 +3,12 @@
3 3 require_once(KT_DIR . '/ktapi/ktapi.inc.php');
4 4 require_once(CMIS_DIR . '/exceptions/ConstraintViolationException.inc.php');
5 5 require_once(CMIS_DIR . '/exceptions/StorageException.inc.php');
  6 +require_once(CMIS_DIR . '/exceptions/StreamNotSupportedException.inc.php');
6 7 require_once(CMIS_DIR . '/exceptions/UpdateConflictException.inc.php');
7 8 require_once(CMIS_DIR . '/exceptions/VersioningException.inc.php');
8 9 require_once(CMIS_DIR . '/services/CMISObjectService.inc.php');
9 10 require_once(CMIS_DIR . '/objecttypes/CMISDocumentObject.inc.php');
10   -//require_once(CMIS_DIR . '/util/CMISUtil.inc.php');
  11 +require_once(CMIS_DIR . '/util/CMISUtil.inc.php');
11 12  
12 13 class CMISVersioningService {
13 14  
... ... @@ -182,14 +183,8 @@ class CMISVersioningService {
182 183 * @return string $documentId
183 184 */
184 185 // TODO Exceptions:
185   - // • ConstraintViolationException - SHALL throw if o The Document’s Object-Type definition’s versionable attribute is FALSE.
186   - // • storageException - MAY throw
187   - // • streamNotSupportedException - The Repository SHALL throw this exception if the Object-Type definition specified by the typeId
188   - // parameter’s “contentStreamAllowed” attribute is set to “not allowed” and a contentStream input
189   - // parameter is provided.
190   - // • updateConflictException - MAY throw
191 186 // • versioningException - The repository MAY throw this exception if the object is a non-current Document Version
192   - public function checkIn($repositoryId, $documentId, $major, $changeToken = '', $properties = array(), $contentStream = null, $checkinComment = '')
  187 + public function checkIn($repositoryId, $documentId, $major, $contentStream = null, $changeToken = '', $properties = array(), $checkinComment = '')
193 188 {
194 189 $documentId = CMISUtil::decodeObjectId($documentId, $typeId);
195 190  
... ... @@ -206,7 +201,35 @@ class CMISVersioningService {
206 201 throw new ConstraintViolationException('This document is not versionable and may not be checked in');
207 202 }
208 203  
209   - return $documentId;
  204 + $RepositoryService = new CMISRepositoryService();
  205 + try {
  206 + $typeDefinition = $RepositoryService->getTypeDefinition($repositoryId, $typeId);
  207 + }
  208 + catch (exception $e) {
  209 + // if we can't get the type definition, then we can't store the content
  210 + throw new StorageException($e->getMessage());
  211 + }
  212 +
  213 + if (($typeDefinition['attributes']['contentStreamAllowed'] == 'notAllowed') && !empty($contentStream)) {
  214 + throw new StreamNotSupportedException('Content Streams are not supported');
  215 + }
  216 +
  217 + // check that this is the latest version
  218 + if ($pwc->getProperty('IsLatestVersion') != true) {
  219 + throw new VersioningException('The document is not the latest version and cannot be checked in');
  220 + }
  221 +
  222 + // now do the checkin
  223 + $tempfilename = CMISUtil::createTemporaryFile($contentStream);
  224 + $response = $this->ktapi->checkin_document($documentId, $pwc->getProperty('ContentStreamFilename'), $reason, $tempfilename, $major,
  225 + $sig_username, $sig_password);
  226 +
  227 + // if there was any error in cancelling the checkout
  228 + if ($response['status_code'] == 1) {
  229 + throw new RuntimeException('There was an error checking in the document: ' . $response['message']);
  230 + }
  231 +
  232 + return CMISUtil::encodeObjectId(DOCUMENT, $documentId);
210 233 }
211 234  
212 235 }
... ...
lib/api/ktcmis/util/CMISUtil.inc.php
... ... @@ -406,30 +406,6 @@ class CMISUtil {
406 406 return (($input === true) ? 'true' : (($input === false) ? 'false' : $input));
407 407 }
408 408  
409   - /**
410   - * Creates a temporary file
411   - * Cleanup is the responsibility of the calling code
412   - *
413   - * @param string|binary $content The content to be written to the file.
414   - * @param string $uploadDir Optional upload directory. Will use the KnowledgeTree system tmp directory if not supplied.
415   - * @return string The path to the created file (for reference and cleanup.)
416   - */
417   - static public function createTemporaryFile($content, $encoding = null, $uploadDir = null)
418   - {
419   - if(is_null($uploadDir))
420   - {
421   - $oKTConfig =& KTConfig::getSingleton();
422   - $uploadDir = $oKTConfig->get('webservice/uploadDirectory');
423   - }
424   -
425   - $temp = tempnam($uploadDir, 'myfile');
426   - $fp = fopen($temp, 'wb');
427   - fwrite($fp, ($encoding == 'base64' ? base64_decode($content) : $content));
428   - fclose($fp);
429   -
430   - return $temp;
431   - }
432   -
433 409 // TODO more robust base64 encoding detection, if possible
434 410  
435 411 /**
... ... @@ -610,6 +586,33 @@ class CMISUtil {
610 586  
611 587 return $exists;
612 588 }
  589 +
  590 + /**
  591 + * Creates a temporary file
  592 + * Cleanup is the responsibility of the calling code
  593 + *
  594 + * @param string $contentStream The content to be stored (assumed to be base64)
  595 + * @return string The path to the created file (for reference and cleanup.)
  596 + */
  597 + static public function createTemporaryFile($contentStream)
  598 + {
  599 + // if contentStream is empty, cannot create file
  600 + if (empty($contentStream)) return null;
  601 +
  602 + // TODO consider checking whether content is encoded (currently we expect encoded)
  603 + // TODO choose between this and the alternative decode function (see CMISUtil class)
  604 + // this will require some basic benchmarking
  605 + $contentStream = CMISUtil::decodeChunkedContentStream($contentStream);
  606 +
  607 + // NOTE There is a function in CMISUtil to do this, written for the unit tests but since KTUploadManager exists
  608 + // and has more functionality which could come in useful at some point I decided to go with that instead
  609 + // (did not know this existed when I wrote the CMISUtil function)
  610 + $uploadManager = new KTUploadManager();
  611 + // assumes already decoded from base64, should use store_base64_file if not
  612 + $tempfilename = $uploadManager->store_file($contentStream, 'cmis_');
  613 +
  614 + return $tempfilename;
  615 + }
613 616  
614 617 }
615 618  
... ...
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 {
101 101 $feed = KT_cmis_atom_service_helper::getObjectFeed($this, $ObjectService, $repositoryId, $folderId);
102 102 }
103 103  
104   - //Expose the responseFeed
  104 + // Expose the responseFeed
105 105 $this->responseFeed = $feed;
106 106 }
107 107  
... ... @@ -140,7 +140,7 @@ class KT_cmis_atom_service_folder extends KT_cmis_atom_service {
140 140 $objectId = $this->params[2];
141 141 }
142 142  
143   - $cmisObjectProperties = KT_cmis_atom_service_helper::getCmisProperties($this->parsedXMLContent['@children']['cmis:object']);
  143 + $cmisObjectProperties = KT_cmis_atom_service_helper::getCmisProperties($this->parsedXMLContent['@children']);
144 144  
145 145 // check for existing object id as property of submitted object data
146 146 if (!empty($cmisObjectProperties['ObjectId']))
... ... @@ -159,7 +159,7 @@ class KT_cmis_atom_service_folder extends KT_cmis_atom_service {
159 159 CMISUtil::decodeObjectId($objectId, $typeId);
160 160 }
161 161  
162   - // now check for content stream
  162 + // check for content stream
163 163 $content = KT_cmis_atom_service_helper::getAtomValues($this->parsedXMLContent['@children'], 'content');
164 164  
165 165 // 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 {
207 207 $feed = KT_cmis_atom_service_helper::getErrorFeed($this, self::STATUS_SERVER_ERROR, $error);
208 208 }
209 209  
210   - //Expose the responseFeed
  210 + // Expose the responseFeed
211 211 $this->responseFeed = $feed;
212 212 }
213 213  
... ... @@ -236,7 +236,7 @@ class KT_cmis_atom_service_folder extends KT_cmis_atom_service {
236 236 if (PEAR::isError($response))
237 237 {
238 238 $feed = KT_cmis_atom_service_helper::getErrorFeed($this, self::STATUS_SERVER_ERROR, $response->getMessage());
239   - //Expose the responseFeed
  239 + // Expose the responseFeed
240 240 $this->responseFeed = $feed;
241 241 return null;
242 242 }
... ... @@ -349,7 +349,7 @@ class KT_cmis_atom_service_types extends KT_cmis_atom_service {
349 349 $type = ((empty($this->params[0])) ? 'all' : $this->params[0]);
350 350 $feed = KT_cmis_atom_service_helper::getTypeFeed($type, $types);
351 351  
352   - //Expose the responseFeed
  352 + // Expose the responseFeed
353 353 $this->responseFeed = $feed;
354 354 }
355 355  
... ... @@ -385,7 +385,7 @@ class KT_cmis_atom_service_type extends KT_cmis_atom_service {
385 385 $feed = $this->getTypeChildrenFeed($this->params[1]);
386 386 }
387 387  
388   - //Expose the responseFeed
  388 + // Expose the responseFeed
389 389 $this->responseFeed=$feed;
390 390 }
391 391  
... ... @@ -494,7 +494,7 @@ class KT_cmis_atom_service_checkedout extends KT_cmis_atom_service {
494 494 // $entry = null;
495 495 // $feed->newField('cmis:hasMoreItems', 'false', $entry, true);
496 496  
497   - //Expose the responseFeed
  497 + // Expose the responseFeed
498 498 $this->responseFeed = $feed;
499 499 }
500 500  
... ... @@ -513,7 +513,7 @@ class KT_cmis_atom_service_checkedout extends KT_cmis_atom_service {
513 513 if (empty($cmisObjectProperties['ObjectId']))
514 514 {
515 515 $feed = KT_cmis_atom_service_helper::getErrorFeed($this, self::STATUS_SERVER_ERROR, 'No object was specified for checkout');
516   - //Expose the responseFeed
  516 + // Expose the responseFeed
517 517 $this->responseFeed = $feed;
518 518 return null;
519 519 }
... ... @@ -523,7 +523,7 @@ class KT_cmis_atom_service_checkedout extends KT_cmis_atom_service {
523 523 if (PEAR::isError($response))
524 524 {
525 525 $feed = KT_cmis_atom_service_helper::getErrorFeed($this, self::STATUS_SERVER_ERROR, 'No object was specified for checkout');
526   - //Expose the responseFeed
  526 + // Expose the responseFeed
527 527 $this->responseFeed = $feed;
528 528 return null;
529 529 }
... ... @@ -531,7 +531,7 @@ class KT_cmis_atom_service_checkedout extends KT_cmis_atom_service {
531 531 $this->setStatus(self::STATUS_CREATED);
532 532 $feed = KT_cmis_atom_service_helper::getObjectFeed($this, $ObjectService, $repositoryId, $cmisObjectProperties['ObjectId'], 'POST');
533 533  
534   - //Expose the responseFeed
  534 + // Expose the responseFeed
535 535 $this->responseFeed = $feed;
536 536 }
537 537  
... ... @@ -559,13 +559,13 @@ class KT_cmis_atom_service_document extends KT_cmis_atom_service {
559 559 // this depends on $this->params[1]
560 560 if (!empty($this->params[1]))
561 561 {
562   - $this->getContentStream($ObjectService, $repositoryId);
  562 + KT_cmis_atom_service_helper::downloadContentStream($this, $ObjectService, $repositoryId);
563 563 return null;
564 564 }
565 565  
566 566 $feed = KT_cmis_atom_service_helper::getObjectFeed($this, $ObjectService, $repositoryId, $this->params[0]);
567 567  
568   - //Expose the responseFeed
  568 + // Expose the responseFeed
569 569 $this->responseFeed = $feed;
570 570 }
571 571  
... ... @@ -594,7 +594,7 @@ class KT_cmis_atom_service_document extends KT_cmis_atom_service {
594 594 if (PEAR::isError($response))
595 595 {
596 596 $feed = KT_cmis_atom_service_helper::getErrorFeed($this, self::STATUS_SERVER_ERROR, $response->getMessage());
597   - //Expose the responseFeed
  597 + // Expose the responseFeed
598 598 $this->responseFeed = $feed;
599 599 return null;
600 600 }
... ... @@ -603,46 +603,6 @@ class KT_cmis_atom_service_document extends KT_cmis_atom_service {
603 603 $this->setStatus(self::STATUS_NO_CONTENT);
604 604 }
605 605  
606   - private function getContentStream(&$ObjectService, $repositoryId)
607   - {
608   - $response = $ObjectService->getProperties($repositoryId, $this->params[0], false, false);
609   - if (PEAR::isError($response)) {
610   - $feed = KT_cmis_atom_service_helper::getErrorFeed($this, KT_cmis_atom_service::STATUS_SERVER_ERROR, $response->getMessage());
611   - $this->responseFeed = $feed;
612   - return null;
613   - }
614   -
615   - // TODO also check If-Modified-Since?
616   -// $this->headers['If-Modified-Since'] => 2009-07-24 17:16:54
617   -
618   - $this->contentDownload = true;
619   - $eTag = md5($response['properties']['LastModificationDate']['value'] . $response['properties']['ContentStreamLength']['value']);
620   -
621   - if ($this->headers['If-None-Match'] == $eTag)
622   - {
623   - $this->setStatus(self::STATUS_NOT_MODIFIED);
624   - $this->contentDownload = false;
625   - return null;
626   - }
627   -
628   - $contentStream = $ObjectService->getContentStream($repositoryId, $this->params[0]);
629   -
630   - // headers specific to output
631   - $this->setEtag($eTag);
632   - $this->setHeader('Last-Modified', $response['properties']['LastModificationDate']['value']);
633   -
634   - if (!empty($response['properties']['ContentStreamMimeType']['value'])) {
635   - $this->setHeader('Content-type', $response['properties']['ContentStreamMimeType']['value'] . ';charset=utf-8');
636   - }
637   - else {
638   - $this->setHeader('Content-type', 'text/plain;charset=utf-8');
639   - }
640   -
641   - $this->setHeader('Content-Disposition', 'attachment;filename="' . $response['properties']['ContentStreamFilename']['value'] . '"');
642   - $this->setHeader('Content-Length', $response['properties']['ContentStreamLength']['value']);
643   - $this->output = $contentStream;
644   - }
645   -
646 606 }
647 607  
648 608 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 {
665 625 // this depends on $this->params[1]
666 626 if (!empty($this->params[1]))
667 627 {
668   - $this->getContentStream($ObjectService, $repositoryId);
  628 + KT_cmis_atom_service_helper::downloadContentStream($this, $ObjectService, $repositoryId);
669 629 return null;
670 630 }
671 631  
672 632 $feed = KT_cmis_atom_service_helper::getObjectFeed($this, $ObjectService, $repositoryId, $this->params[0]);
673 633  
674   - //Expose the responseFeed
  634 + // Expose the responseFeed
675 635 $this->responseFeed = $feed;
676 636 }
677 637  
... ... @@ -696,7 +656,7 @@ class KT_cmis_atom_service_pwc extends KT_cmis_atom_service {
696 656 if (PEAR::isError($response))
697 657 {
698 658 $feed = KT_cmis_atom_service_helper::getErrorFeed($this, self::STATUS_SERVER_ERROR, $response->getMessage());
699   - //Expose the responseFeed
  659 + // Expose the responseFeed
700 660 $this->responseFeed = $feed;
701 661 return null;
702 662 }
... ... @@ -714,18 +674,52 @@ class KT_cmis_atom_service_pwc extends KT_cmis_atom_service {
714 674 $repositories = $RepositoryService->getRepositories();
715 675 $repositoryId = $repositories[0]['repositoryId'];
716 676  
717   - $response = $VersioningService->checkIn($repositoryId, $this->params[0]);
  677 + // check for content stream
  678 + // NOTE this is a hack! will not work with CMISSpaces at least, probably not with any client except RestTest and similar
  679 + // where we can manually modify the input
  680 + // first we try for an atom content tag
  681 + $content = KT_cmis_atom_service_helper::getAtomValues($this->parsedXMLContent['@children'], 'content');
  682 + if (!empty($content)) {
  683 + $contentStream = $content;
  684 + }
  685 + // not found? try for a regular content tag
  686 + else {
  687 + $content = KT_cmis_atom_service_helper::findTag('content', $this->parsedXMLContent['@children'], null, false);
  688 + $contentStream = $content['@value'];
  689 + }
  690 +
  691 + // if we haven't found it now, the real hack begins - retrieve the EXISTING content and submit this as the contentStream
  692 + // this is needed because KnowledgeTree will not accept a checkin without a content stream but CMISSpaces (and possibly
  693 + // 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)
  694 + // NOTE that if the content is INTENDED to be empty this and all the above checks will FAIL!
  695 + // FIXME this is horrible, terrible, ugly and bad!
  696 + if (empty($contentStream))
  697 + {
  698 + $ObjectService = new ObjectService(KT_cmis_atom_service_helper::getKt());
  699 + $contentStream = base64_encode(KT_cmis_atom_service_helper::getContentStream($this, $ObjectService, $repositoryId));
  700 + }
  701 +
  702 + // and if we don't have it by now, we give up...but leave the error to be generated by the underlying KnowledgeTree code
  703 +
  704 + // checkin function call
  705 + // TODO dynamically detect version change type - leaving this for now as the CMIS clients tested do not appear to
  706 + // offer the choice to the user - perhaps it will turn out that this will come from somewhere else but for now
  707 + // we assume minor version updates only
  708 + $major = false;
  709 + $response = $VersioningService->checkIn($repositoryId, $this->params[0], $major, $contentStream);
718 710  
719 711 if (PEAR::isError($response))
720 712 {
721 713 $feed = KT_cmis_atom_service_helper::getErrorFeed($this, self::STATUS_SERVER_ERROR, $response->getMessage());
722   - //Expose the responseFeed
  714 + // Expose the responseFeed
723 715 $this->responseFeed = $feed;
724 716 return null;
725 717 }
726 718  
727   - $this->setStatus(self::STATUS_NO_CONTENT);
728   - $this->responseFeed = null;
  719 + $feed = KT_cmis_atom_service_helper::getObjectFeed($this, $ObjectService, $repositoryId, $this->params[0]);
  720 +
  721 + // Expose the responseFeed
  722 + $this->responseFeed = $feed;
729 723 }
730 724  
731 725 }
... ...
webservice/atompub/cmis/KT_cmis_atom_service_helper.inc.php
... ... @@ -418,7 +418,7 @@ class KT_cmis_atom_service_helper {
418 418 while($start < $numFolders)
419 419 {
420 420 $name = $path[$numQ-$numFolders+$start];
421   - // hack to fix drupal url encoding issue
  421 + // fix for possible url encoding issue
422 422 $name = str_replace('%2520', '%20', $name);
423 423  
424 424 $folderName = urldecode($name);
... ... @@ -494,6 +494,71 @@ class KT_cmis_atom_service_helper {
494 494 return date('Y-m-d H:i:s', $time);
495 495 }
496 496  
  497 + /**
  498 + * Fetches the document content stream for internal use
  499 + *
  500 + * @param object $ObjectService
  501 + * @param string $repositoryId
  502 + * @return null | string $contentStream
  503 + */
  504 + static public function getContentStream(&$service, &$ObjectService, $repositoryId)
  505 + {
  506 + $response = $ObjectService->getProperties($repositoryId, $service->params[0], false, false);
  507 + if (PEAR::isError($response)) {
  508 + return null;
  509 + }
  510 +
  511 + $contentStream = $ObjectService->getContentStream($repositoryId, $service->params[0]);
  512 +
  513 + return $contentStream;
  514 + }
  515 + /**
  516 + * Fetches and prepares the document content stream for download/viewing
  517 + *
  518 + * @param object $ObjectService
  519 + * @param string $repositoryId
  520 + * @return null | nothing
  521 + */
  522 + static public function downloadContentStream(&$service, &$ObjectService, $repositoryId)
  523 + {
  524 + $response = $ObjectService->getProperties($repositoryId, $service->params[0], false, false);
  525 + if (PEAR::isError($response)) {
  526 + $feed = KT_cmis_atom_service_helper::getErrorFeed($service, KT_cmis_atom_service::STATUS_SERVER_ERROR, $response->getMessage());
  527 + $service->responseFeed = $feed;
  528 + return null;
  529 + }
  530 +
  531 + // TODO also check If-Modified-Since?
  532 +// $service->headers['If-Modified-Since'] => 2009-07-24 17:16:54
  533 +
  534 + $service->setContentDownload(true);
  535 + $eTag = md5($response['properties']['LastModificationDate']['value'] . $response['properties']['ContentStreamLength']['value']);
  536 +
  537 + if ($service->headers['If-None-Match'] == $eTag)
  538 + {
  539 + $service->setStatus(KT_cmis_atom_service::STATUS_NOT_MODIFIED);
  540 + $service->setContentDownload(false);
  541 + return null;
  542 + }
  543 +
  544 + $contentStream = $ObjectService->getContentStream($repositoryId, $service->params[0]);
  545 +
  546 + // headers specific to output
  547 + $service->setEtag($eTag);
  548 + $service->setHeader('Last-Modified', $response['properties']['LastModificationDate']['value']);
  549 +
  550 + if (!empty($response['properties']['ContentStreamMimeType']['value'])) {
  551 + $service->setHeader('Content-type', $response['properties']['ContentStreamMimeType']['value'] . ';charset=utf-8');
  552 + }
  553 + else {
  554 + $service->setHeader('Content-type', 'text/plain;charset=utf-8');
  555 + }
  556 +
  557 + $service->setHeader('Content-Disposition', 'attachment;filename="' . $response['properties']['ContentStreamFilename']['value'] . '"');
  558 + $service->setHeader('Content-Length', $response['properties']['ContentStreamLength']['value']);
  559 + $service->setOutput($contentStream);
  560 + }
  561 +
497 562 //TODO: Add key information to be able to find the same tag in the original struct (MarkH)
498 563 static public function findTag($tagName=NULL,$xml=array(),$tagArray=NULL,$deep=false){
499 564 $tagArray=is_array($tagArray)?$tagArray:array();
... ...
webservice/classes/atompub/KT_atom_service.inc.php
... ... @@ -147,7 +147,7 @@ class KT_atom_service{
147 147 header("HTTP/1.1 ".$status);
148 148 }
149 149  
150   - protected function setEtag($etagValue=NULL){
  150 + public function setEtag($etagValue=NULL){
151 151 if($etagValue)header('ETag: '.$etagValue);
152 152 }
153 153  
... ...
webservice/classes/atompub/cmis/KT_cmis_atom_service.inc.php
... ... @@ -8,7 +8,12 @@ class KT_cmis_atom_service extends KT_atom_service {
8 8  
9 9 protected $serviceType = null;
10 10 protected $contentDownload = false;
11   -
  11 +
  12 + public function setContentDownload($contentDownload)
  13 + {
  14 + $this->contentDownload = $contentDownload;
  15 + }
  16 +
12 17 public public function isContentDownload()
13 18 {
14 19 return $this->contentDownload;
... ... @@ -19,6 +24,11 @@ class KT_cmis_atom_service extends KT_atom_service {
19 24 return $this->status == self::STATUS_NOT_MODIFIED;
20 25 }
21 26  
  27 + public function setoutput($output)
  28 + {
  29 + $this->output = $output;
  30 + }
  31 +
22 32 public function getOutput()
23 33 {
24 34 return $this->output;
... ... @@ -29,7 +39,7 @@ class KT_cmis_atom_service extends KT_atom_service {
29 39 return $this->serviceType;
30 40 }
31 41  
32   - protected function setHeader($header = null, $value = null)
  42 + public function setHeader($header = null, $value = null)
33 43 {
34 44 if ($header) header($header . ': ' . $value);
35 45 }
... ...
webservice/classes/atompub/cmis/VersioningService.inc.php
... ... @@ -85,9 +85,9 @@ class VersioningService extends KTVersioningService {
85 85 * @param string $checkinComment [optional]
86 86 * @return string $documentId
87 87 */
88   - public function checkIn($repositoryId, $documentId, $major, $changeToken = '', $properties = array(), $contentStream = null, $checkinComment = '')
  88 + public function checkIn($repositoryId, $documentId, $major, $contentStream = null, $changeToken = '', $properties = array(), $checkinComment = '')
89 89 {
90   - $result = parent::checkIn($repositoryId, $documentId, $major, $changeToken, $properties, $contentStream, $checkinComment);
  90 + $result = parent::checkIn($repositoryId, $documentId, $major, $contentStream, $changeToken, $properties, $checkinComment);
91 91  
92 92 if ($result['status_code'] == 0) {
93 93 return $result['results'];
... ...