Commit 12e3df4d6214881520139fafa2e184b99021da3c
1 parent
6e49a0ce
Added CMIS API and AtomPub code to return a content stream for a document object…
…, allowing vuiewing of document content through a CMIS client. Story ID: 966928. In order to view content from the repository using CMIS, as a developer, I would like KnowledgeTree's CMIS interface to handle fetching content streams Committed by: Paul Barrett Reviewed by: Jarrett Jordaan
Showing
17 changed files
with
396 additions
and
95 deletions
ktapi/KTAPIDocument.inc.php
| ... | ... | @@ -2099,6 +2099,25 @@ class KTAPI_Document extends KTAPI_FolderItem |
| 2099 | 2099 | $oDocumentTransaction = new DocumentTransaction($this->document, 'Document downloaded', 'ktcore.transactions.download', $aOptions); |
| 2100 | 2100 | $oDocumentTransaction->create(); |
| 2101 | 2101 | } |
| 2102 | + | |
| 2103 | + /** | |
| 2104 | + * Function to fetch the actual file content of a document | |
| 2105 | + * | |
| 2106 | + * @return $content the document file content | |
| 2107 | + */ | |
| 2108 | + function get_document_content() | |
| 2109 | + { | |
| 2110 | + // fetch the content | |
| 2111 | + $content = KTDocumentUtil::getDocumentContent($this->document); | |
| 2112 | + | |
| 2113 | + // TODO what if the file could not be found? | |
| 2114 | + | |
| 2115 | + // Log the transaction | |
| 2116 | + $this->download(); | |
| 2117 | + | |
| 2118 | + // return the document content | |
| 2119 | + return $content; | |
| 2120 | + } | |
| 2102 | 2121 | |
| 2103 | 2122 | /** |
| 2104 | 2123 | * This returns the transaction history for the document. | ... | ... |
lib/api/ktcmis/classes/CMISBaseObject.inc.php
| ... | ... | @@ -56,6 +56,7 @@ abstract class CMISBaseObject { |
| 56 | 56 | protected $queryable; |
| 57 | 57 | protected $includedInSupertypeQuery; |
| 58 | 58 | protected $controllable; // NOTE deprecated? part of policy objects specification, policy objects are indicated as TODO remove |
| 59 | + protected $contentStreamAllowed = 'notAllowed'; | |
| 59 | 60 | |
| 60 | 61 | protected $properties; // list of property objects which define the additional properties for this object |
| 61 | 62 | ... | ... |
lib/api/ktcmis/classes/CMISPropertyCollection.inc.php
| ... | ... | @@ -67,8 +67,11 @@ abstract class CMISPropertyCollection { |
| 67 | 67 | 'LastModifiedBy' => 'propertyString', |
| 68 | 68 | 'LastModificationDate' => 'propertyDateTime', |
| 69 | 69 | 'Name' => 'propertyString', |
| 70 | + 'ContentStreamAllowed' => 'propertyString', | |
| 70 | 71 | 'ContentStreamLength' => 'propertyInteger', |
| 71 | 72 | 'ContentStreamMimeType' => 'propertyString', |
| 73 | + 'ContentStreamFilename' => 'propertyString', | |
| 74 | + 'ContentStreamUri' => 'propertyUri', | |
| 72 | 75 | 'Uri' => 'propertyUri', |
| 73 | 76 | 'AllowedChildObjectTypeIds' => 'propertyId', |
| 74 | 77 | 'CreatedBy' => 'propertyString', | ... | ... |
lib/api/ktcmis/ktcmis.inc.php
| ... | ... | @@ -592,6 +592,32 @@ class KTObjectService extends KTCMISBase { |
| 592 | 592 | } |
| 593 | 593 | |
| 594 | 594 | /** |
| 595 | + * Fetches the content stream data for an object | |
| 596 | + * | |
| 597 | + * @param string $repositoryId | |
| 598 | + * @param string $objectId | |
| 599 | + * @return string $contentStream (binary or text data) | |
| 600 | + */ | |
| 601 | + function getContentStream($repositoryId, $objectId) | |
| 602 | + { | |
| 603 | + try { | |
| 604 | + $contentStream = $this->ObjectService->getContentStream($repositoryId, $objectId); | |
| 605 | + } | |
| 606 | + catch (Exception $e) | |
| 607 | + { | |
| 608 | + return array( | |
| 609 | + "status_code" => 1, | |
| 610 | + "message" => $e->getMessage() | |
| 611 | + ); | |
| 612 | + } | |
| 613 | + | |
| 614 | + return array( | |
| 615 | + 'status_code' => 0, | |
| 616 | + 'results' => $contentStream | |
| 617 | + ); | |
| 618 | + } | |
| 619 | + | |
| 620 | + /** | |
| 595 | 621 | * Moves a fileable object from one folder to another. |
| 596 | 622 | * |
| 597 | 623 | * @param object $repositoryId | ... | ... |
lib/api/ktcmis/objecttypes/CMISDocumentObject.inc.php
| ... | ... | @@ -49,7 +49,6 @@ require_once(CMIS_DIR . '/util/CMISUtil.inc.php'); |
| 49 | 49 | class CMISDocumentObject extends CMISBaseObject { |
| 50 | 50 | |
| 51 | 51 | protected $versionable; |
| 52 | - protected $contentStreamAllowed; | |
| 53 | 52 | private $ktapi; |
| 54 | 53 | private $uri; |
| 55 | 54 | |
| ... | ... | @@ -167,11 +166,10 @@ class CMISDocumentObject extends CMISBaseObject { |
| 167 | 166 | $this->_setPropertyInternal('VersionSeriesCheckedOutId', $checkedOutId); |
| 168 | 167 | // TODO currently not returned by KnowledgeTree? |
| 169 | 168 | $this->_setPropertyInternal('CheckinComment', null); |
| 170 | - // TODO if we implement content streams | |
| 171 | 169 | $this->_setPropertyInternal('ContentStreamLength', $objectProperties['filesize']); |
| 172 | 170 | $this->_setPropertyInternal('ContentStreamMimeType', $objectProperties['mime_type']); |
| 173 | - $this->_setPropertyInternal('ContentStreamFilename', null); | |
| 174 | - $this->_setPropertyInternal('ContentStreamUri', null); | |
| 171 | + $this->_setPropertyInternal('ContentStreamFilename', $objectProperties['filename']); | |
| 172 | + $this->_setPropertyInternal('ContentStreamUri', $this->getProperty('ObjectId') . '/' . $objectProperties['filename']); | |
| 175 | 173 | $this->_setPropertyInternal('Author', $objectProperties['created_by']); |
| 176 | 174 | } |
| 177 | 175 | ... | ... |
lib/api/ktcmis/services/CMISObjectService.inc.php
| ... | ... | @@ -31,55 +31,6 @@ class CMISObjectService { |
| 31 | 31 | } |
| 32 | 32 | |
| 33 | 33 | /** |
| 34 | - * Fetches the properties for the specified object | |
| 35 | - * | |
| 36 | - * @param string $repositoryId | |
| 37 | - * @param string $objectId | |
| 38 | - * @param boolean $includeAllowableActions | |
| 39 | - * @param boolean $includeRelationships | |
| 40 | - * @param boolean $returnVersion | |
| 41 | - * @param string $filter | |
| 42 | - * @return object CMIS object properties | |
| 43 | - */ | |
| 44 | - // TODO optional parameter support | |
| 45 | - // TODO FilterNotValidException: The Repository SHALL throw this exception if this property filter input parameter is not valid | |
| 46 | - public function getProperties($repositoryId, $objectId, $includeAllowableActions, $includeRelationships, | |
| 47 | - $returnVersion = false, $filter = '') | |
| 48 | - { | |
| 49 | - $repository = new CMISRepository($repositoryId); | |
| 50 | - | |
| 51 | - // TODO a better default value? | |
| 52 | - $properties = array(); | |
| 53 | - | |
| 54 | - $objectId = CMISUtil::decodeObjectId($objectId, $typeId); | |
| 55 | - | |
| 56 | - if ($typeId == 'Unknown') | |
| 57 | - { | |
| 58 | - throw new ObjectNotFoundException('The type of the requested object could not be determined'); | |
| 59 | - } | |
| 60 | - | |
| 61 | - switch($typeId) | |
| 62 | - { | |
| 63 | - case 'Document': | |
| 64 | - $CMISObject = new CMISDocumentObject($objectId, $this->ktapi, $repository->getRepositoryURI()); | |
| 65 | - break; | |
| 66 | - case 'Folder': | |
| 67 | - $CMISObject = new CMISFolderObject($objectId, $this->ktapi, $repository->getRepositoryURI()); | |
| 68 | - break; | |
| 69 | - } | |
| 70 | - | |
| 71 | - // check that we were actually able to retrieve a real object | |
| 72 | - $objectId = $CMISObject->getProperty('ObjectId'); | |
| 73 | - if (empty($objectId)) { | |
| 74 | - throw new ObjectNotFoundException('The requested object could not be found'); | |
| 75 | - } | |
| 76 | - | |
| 77 | - $properties = $CMISObject->getProperties(); | |
| 78 | - | |
| 79 | - return $properties; | |
| 80 | - } | |
| 81 | - | |
| 82 | - /** | |
| 83 | 34 | * Creates a new document within the repository |
| 84 | 35 | * |
| 85 | 36 | * @param string $repositoryId The repository to which the document must be added |
| ... | ... | @@ -232,7 +183,6 @@ class CMISObjectService { |
| 232 | 183 | ); |
| 233 | 184 | } |
| 234 | 185 | |
| 235 | - // TODO fetch author name based on logged in user | |
| 236 | 186 | $user = $this->ktapi->get_user(); |
| 237 | 187 | if (!PEAR::isError($user)) |
| 238 | 188 | { |
| ... | ... | @@ -257,7 +207,7 @@ class CMISObjectService { |
| 257 | 207 | ); |
| 258 | 208 | |
| 259 | 209 | /** |
| 260 | - * Try to determine mime type which maps to one of the following: | |
| 210 | + * Try to determine mime type which maps to one of the following KnowledgetTree document types: | |
| 261 | 211 | * |
| 262 | 212 | * Audio |
| 263 | 213 | * Image |
| ... | ... | @@ -408,6 +358,104 @@ class CMISObjectService { |
| 408 | 358 | } |
| 409 | 359 | |
| 410 | 360 | /** |
| 361 | + * Fetches the properties for the specified object | |
| 362 | + * | |
| 363 | + * @param string $repositoryId | |
| 364 | + * @param string $objectId | |
| 365 | + * @param boolean $includeAllowableActions | |
| 366 | + * @param boolean $includeRelationships | |
| 367 | + * @param boolean $returnVersion | |
| 368 | + * @param string $filter | |
| 369 | + * @return object CMIS object properties | |
| 370 | + */ | |
| 371 | + // TODO optional parameter support | |
| 372 | + // TODO FilterNotValidException: The Repository SHALL throw this exception if this property filter input parameter is not valid | |
| 373 | + public function getProperties($repositoryId, $objectId, $includeAllowableActions, $includeRelationships, | |
| 374 | + $returnVersion = false, $filter = '') | |
| 375 | + { | |
| 376 | + $repository = new CMISRepository($repositoryId); | |
| 377 | + | |
| 378 | + // TODO a better default value? | |
| 379 | + $properties = array(); | |
| 380 | + | |
| 381 | + $objectId = CMISUtil::decodeObjectId($objectId, $typeId); | |
| 382 | + | |
| 383 | + if ($typeId == 'Unknown') | |
| 384 | + { | |
| 385 | + throw new ObjectNotFoundException('The type of the requested object could not be determined'); | |
| 386 | + } | |
| 387 | + | |
| 388 | + switch($typeId) | |
| 389 | + { | |
| 390 | + case 'Document': | |
| 391 | + $CMISObject = new CMISDocumentObject($objectId, $this->ktapi, $repository->getRepositoryURI()); | |
| 392 | + break; | |
| 393 | + case 'Folder': | |
| 394 | + $CMISObject = new CMISFolderObject($objectId, $this->ktapi, $repository->getRepositoryURI()); | |
| 395 | + break; | |
| 396 | + } | |
| 397 | + | |
| 398 | + // check that we were actually able to retrieve a real object | |
| 399 | + $objectId = $CMISObject->getProperty('ObjectId'); | |
| 400 | + if (empty($objectId)) { | |
| 401 | + throw new ObjectNotFoundException('The requested object could not be found'); | |
| 402 | + } | |
| 403 | + | |
| 404 | + $properties = $CMISObject->getProperties(); | |
| 405 | + | |
| 406 | + return $properties; | |
| 407 | + } | |
| 408 | + | |
| 409 | + /** | |
| 410 | + * Fetches the content stream data for an object | |
| 411 | + * | |
| 412 | + * @param string $repositoryId | |
| 413 | + * @param string $objectId | |
| 414 | + * @return string $contentStream (binary or text data) | |
| 415 | + */ | |
| 416 | + // NOTE streamNotSupportedException: The Repository SHALL throw this exception if the Object-Type definition | |
| 417 | + // specified by the objectId parameter’s “contentStreamAllowed” attribute is set to “not allowed”. | |
| 418 | + // | |
| 419 | + function getContentStream($repositoryId, $objectId) | |
| 420 | + { | |
| 421 | + $contentStream = null; | |
| 422 | + | |
| 423 | + // decode $objectId | |
| 424 | + $objectId = CMISUtil::decodeObjectId($objectId, $typeId); | |
| 425 | + | |
| 426 | + // unknown object type? | |
| 427 | + if ($typeId == 'Unknown') { | |
| 428 | + throw new ObjectNotFoundException('The type of the requested object could not be determined'); | |
| 429 | + } | |
| 430 | + | |
| 431 | + // fetch type definition of supplied object type | |
| 432 | + $objectClass = 'CMIS' . $typeId . 'Object'; | |
| 433 | + $CMISObject = new $objectClass($objectId, $this->ktapi); | |
| 434 | + | |
| 435 | + // if content stream is not allowed for this object type definition, throw a ConstraintViolationException | |
| 436 | + if (($CMISObject->getAttribute('contentStreamAllowed') == 'notAllowed')) | |
| 437 | + { | |
| 438 | + // NOTE spec version 0.61c specifies both a ConstraintViolationException and a StreamNotSupportedException | |
| 439 | + // for this case. Choosing to throw StreamNotSupportedException until the specification is clarified | |
| 440 | + // as it is a more specific exception | |
| 441 | + throw new StreamNotSupportedException('Content Streams are not allowed for this object type'); | |
| 442 | + } | |
| 443 | + | |
| 444 | + // now go on to fetching the content stream | |
| 445 | + // TODO allow fetching of partial streams | |
| 446 | + // from the CMIS specification (0.61): | |
| 447 | + // "Each CMIS protocol binding SHALL provide a way for fetching a sub-range within a content stream, in a manner appropriate to that protocol." | |
| 448 | + | |
| 449 | + // steps to fetch content stream: | |
| 450 | + // 1. find actual physical document (see zip/download code) | |
| 451 | + // TODO move this into a ktapi function | |
| 452 | + $document = $this->ktapi->get_document_by_id($objectId); | |
| 453 | + $contentStream = $document->get_document_content(); | |
| 454 | + | |
| 455 | + return $contentStream; | |
| 456 | + } | |
| 457 | + | |
| 458 | + /** | |
| 411 | 459 | * Moves a fileable object from one folder to another. |
| 412 | 460 | * |
| 413 | 461 | * @param object $repositoryId |
| ... | ... | @@ -675,6 +723,8 @@ class CMISObjectService { |
| 675 | 723 | if ($tmpTypeId != 'Unknown') |
| 676 | 724 | $documentId = $tmpObjectId; |
| 677 | 725 | |
| 726 | + // TODO deal with other types except documents | |
| 727 | + | |
| 678 | 728 | // fetch type definition of supplied document |
| 679 | 729 | $CMISDocument = new CMISDocumentObject($documentId, $this->ktapi); |
| 680 | 730 | ... | ... |
lib/api/ktcmis/util/CMISUtil.inc.php
| ... | ... | @@ -252,14 +252,21 @@ class CMISUtil { |
| 252 | 252 | return $hierarchy; |
| 253 | 253 | } |
| 254 | 254 | |
| 255 | + /** | |
| 256 | + * Creates a properties entry array for the given entry | |
| 257 | + * | |
| 258 | + * TODO make this just dynamically convert all properties instead of selected. | |
| 259 | + * NOTE current version is a legacy of how we thought things needed to be for the SOAP webservices. | |
| 260 | + * | |
| 261 | + * @param object $properties | |
| 262 | + * @return | |
| 263 | + */ | |
| 255 | 264 | static public function createObjectPropertiesEntry($properties) |
| 256 | 265 | { |
| 257 | - // TODO better dynamic style fetching of object properties into array for output | |
| 258 | 266 | $object = array(); |
| 259 | 267 | |
| 260 | 268 | $object['Author'] = array('value' => $properties->getValue('Author')); |
| 261 | 269 | |
| 262 | - // TODO additional properties to be returned | |
| 263 | 270 | $object['properties']['BaseType'] = array('type' => $properties->getFieldType('BaseType'), |
| 264 | 271 | 'value' => $properties->getValue('BaseType')); |
| 265 | 272 | |
| ... | ... | @@ -300,29 +307,32 @@ class CMISUtil { |
| 300 | 307 | |
| 301 | 308 | if (strtolower($properties->getValue('ObjectTypeId')) == 'document') |
| 302 | 309 | { |
| 303 | - | |
| 304 | - $object['properties']['ChangeToken'] = array('type' => $properties->getFieldType('ChangeToken'), | |
| 310 | + $object['properties']['ChangeToken'] = array('type' => $properties->getFieldType('ChangeToken'), | |
| 305 | 311 | 'value' => $properties->getValue('ChangeToken')); |
| 306 | 312 | $contentStreamLength = $properties->getValue('ContentStreamLength'); |
| 307 | 313 | if (!empty($contentStreamLength)) |
| 308 | 314 | { |
| 309 | 315 | $contentStreamLength = $properties->getValue('ContentStreamLength'); |
| 316 | + $object['properties']['ContentStreamAllowed'] = array('type' => $properties->getFieldType('ContentStreamAllowed'), | |
| 317 | + 'value' => $properties->getValue('ContentStreamAllowed')); | |
| 310 | 318 | $object['properties']['ContentStreamLength'] = array('type' => $properties->getFieldType('ContentStreamLength'), |
| 311 | 319 | 'value' => $properties->getValue('ContentStreamLength')); |
| 312 | 320 | $object['properties']['ContentStreamMimeType'] = array('type' => $properties->getFieldType('ContentStreamMimeType'), |
| 313 | 321 | 'value' => $properties->getValue('ContentStreamMimeType')); |
| 322 | + $object['properties']['ContentStreamFilename'] = array('type' => $properties->getFieldType('ContentStreamFilename'), | |
| 323 | + 'value' => $properties->getValue('ContentStreamFilename')); | |
| 324 | + $object['properties']['ContentStreamUri'] = array('type' => $properties->getFieldType('ContentStreamUri'), | |
| 325 | + 'value' => $properties->getValue('ContentStreamUri')); | |
| 314 | 326 | } |
| 315 | 327 | } |
| 316 | 328 | |
| 317 | 329 | // if we have found a child/parent with one or more children/parents, recurse into the child/parent object |
| 318 | - if (count($entry['items']) > 0) | |
| 319 | - { | |
| 330 | + if (count($entry['items']) > 0) { | |
| 320 | 331 | $object[$linkText] = CMISUtil::decodeObjectHierarchy($entry['items'], $linkText); |
| 321 | 332 | } |
| 322 | 333 | // NOTE may need to set a null value here in case webservices don't like it unset |
| 323 | 334 | // so we'll set it just in case... |
| 324 | - else | |
| 325 | - { | |
| 335 | + else { | |
| 326 | 336 | $object[$linkText] = null; |
| 327 | 337 | } |
| 328 | 338 | ... | ... |
lib/documentmanagement/documentutil.inc.php
| ... | ... | @@ -1564,6 +1564,40 @@ $sourceDocument->getName(), |
| 1564 | 1564 | |
| 1565 | 1565 | DBUtil::commit(); |
| 1566 | 1566 | } |
| 1567 | + | |
| 1568 | + public static function getDocumentContent($oDocument) | |
| 1569 | + { | |
| 1570 | + global $default; | |
| 1571 | + | |
| 1572 | + //get the path to the document on the server | |
| 1573 | + //$docRoot = $default->documentRoot; | |
| 1574 | + $oConfig =& KTConfig::getSingleton(); | |
| 1575 | + $docRoot = $oConfig->get('urls/documentRoot'); | |
| 1576 | + | |
| 1577 | + $path = $docRoot .'/'. $oDocument->getStoragePath(); | |
| 1578 | + | |
| 1579 | + // Ensure the file exists | |
| 1580 | + if (file_exists($path)) | |
| 1581 | + { | |
| 1582 | + // Get the mime type - this is not relevant at the moment... | |
| 1583 | + $mimeId = $oDocument->getMimeTypeID(); | |
| 1584 | + $mimetype = KTMime::getMimeTypeName($mimeId); | |
| 1585 | + | |
| 1586 | + if ($bIsCheckout && $default->fakeMimetype) { | |
| 1587 | + // note this does not work for "image" types in some browsers | |
| 1588 | + $mimetype = 'application/x-download'; | |
| 1589 | + } | |
| 1590 | + | |
| 1591 | + $sFileName = $oDocument->getFileName( ); | |
| 1592 | + $iFileSize = $oDocument->getFileSize(); | |
| 1593 | + } else { | |
| 1594 | + return null; | |
| 1595 | + } | |
| 1596 | + | |
| 1597 | + $content = file_get_contents($path); | |
| 1598 | + | |
| 1599 | + return $content; | |
| 1600 | + } | |
| 1567 | 1601 | } |
| 1568 | 1602 | |
| 1569 | 1603 | class KTMetadataValidationError extends PEAR_Error { | ... | ... |
tests/ktcmis/myf5429.tmp deleted
tests/ktcmis/myf7FFE.tmp deleted
tests/ktcmis/myf8F68.tmp deleted
webservice/atompub/cmis/KT_cmis_atom_server.services.inc.php
| ... | ... | @@ -42,7 +42,7 @@ include_once 'KT_cmis_atom_service_helper.inc.php'; |
| 42 | 42 | /** |
| 43 | 43 | * AtomPub Service: folder |
| 44 | 44 | */ |
| 45 | -class KT_cmis_atom_service_folder extends KT_atom_service { | |
| 45 | +class KT_cmis_atom_service_folder extends KT_cmis_atom_service { | |
| 46 | 46 | |
| 47 | 47 | /** |
| 48 | 48 | * Deals with GET actions for folders. |
| ... | ... | @@ -337,7 +337,7 @@ class KT_cmis_atom_service_folder extends KT_atom_service { |
| 337 | 337 | /** |
| 338 | 338 | * AtomPub Service: types |
| 339 | 339 | */ |
| 340 | -class KT_cmis_atom_service_types extends KT_atom_service { | |
| 340 | +class KT_cmis_atom_service_types extends KT_cmis_atom_service { | |
| 341 | 341 | |
| 342 | 342 | public function GET_action() |
| 343 | 343 | { |
| ... | ... | @@ -358,7 +358,7 @@ class KT_cmis_atom_service_types extends KT_atom_service { |
| 358 | 358 | /** |
| 359 | 359 | * AtomPub Service: type |
| 360 | 360 | */ |
| 361 | -class KT_cmis_atom_service_type extends KT_atom_service { | |
| 361 | +class KT_cmis_atom_service_type extends KT_cmis_atom_service { | |
| 362 | 362 | |
| 363 | 363 | public function GET_action() |
| 364 | 364 | { |
| ... | ... | @@ -433,7 +433,7 @@ class KT_cmis_atom_service_type extends KT_atom_service { |
| 433 | 433 | * AtomPub Service: checkedout |
| 434 | 434 | */ |
| 435 | 435 | // NOTE this is always an empty document, underlying API code still to be implemented |
| 436 | -class KT_cmis_atom_service_checkedout extends KT_atom_service { | |
| 436 | +class KT_cmis_atom_service_checkedout extends KT_cmis_atom_service { | |
| 437 | 437 | |
| 438 | 438 | /** |
| 439 | 439 | * Deals with GET actions for checkedout documents. |
| ... | ... | @@ -507,6 +507,14 @@ class KT_cmis_atom_service_document extends KT_cmis_atom_service { |
| 507 | 507 | |
| 508 | 508 | $repositories = $RepositoryService->getRepositories(); |
| 509 | 509 | $repositoryId = $repositories[0]['repositoryId']; |
| 510 | + | |
| 511 | + // determine whether we want the document entry feed or the actual physical document content. | |
| 512 | + // this depends on $this->params[1] | |
| 513 | + if (!empty($this->params[1])) | |
| 514 | + { | |
| 515 | + $this->getContentStream($ObjectService, $repositoryId); | |
| 516 | + return null; | |
| 517 | + } | |
| 510 | 518 | |
| 511 | 519 | $feed = KT_cmis_atom_service_helper::getObjectFeed($this, $ObjectService, $repositoryId, $this->params[0]); |
| 512 | 520 | |
| ... | ... | @@ -548,6 +556,46 @@ class KT_cmis_atom_service_document extends KT_cmis_atom_service { |
| 548 | 556 | $this->setStatus(self::STATUS_NO_CONTENT); |
| 549 | 557 | } |
| 550 | 558 | |
| 559 | + private function getContentStream(&$ObjectService, $repositoryId) | |
| 560 | + { | |
| 561 | + $response = $ObjectService->getProperties($repositoryId, $this->params[0], false, false); | |
| 562 | + if (PEAR::isError($response)) { | |
| 563 | + $feed = KT_cmis_atom_service_helper::getErrorFeed($this, KT_cmis_atom_service::STATUS_SERVER_ERROR, $response->getMessage()); | |
| 564 | + $this->responseFeed = $feed; | |
| 565 | + return null; | |
| 566 | + } | |
| 567 | + | |
| 568 | + // TODO also check If-Modified-Since? | |
| 569 | +// $this->headers['If-Modified-Since'] => 2009-07-24 17:16:54 | |
| 570 | + | |
| 571 | + $this->contentDownload = true; | |
| 572 | + $eTag = md5($response['properties']['LastModificationDate']['value'] . $response['properties']['ContentStreamLength']['value']); | |
| 573 | + | |
| 574 | + if ($this->headers['If-None-Match'] == $eTag) | |
| 575 | + { | |
| 576 | + $this->setStatus(self::STATUS_NOT_MODIFIED); | |
| 577 | + $this->contentDownload = false; | |
| 578 | + return null; | |
| 579 | + } | |
| 580 | + | |
| 581 | + $contentStream = $ObjectService->getContentStream($repositoryId, $this->params[0]); | |
| 582 | + | |
| 583 | + // headers specific to output | |
| 584 | + $this->setEtag($eTag); | |
| 585 | + $this->setHeader('Last-Modified', $response['properties']['LastModificationDate']['value']); | |
| 586 | + | |
| 587 | + if (!empty($response['properties']['ContentStreamMimeType']['value'])) { | |
| 588 | + $this->setHeader('Content-type', $response['properties']['ContentStreamMimeType']['value'] . ';charset=utf-8'); | |
| 589 | + } | |
| 590 | + else { | |
| 591 | + $this->setHeader('Content-type', 'text/plain;charset=utf-8'); | |
| 592 | + } | |
| 593 | + | |
| 594 | + $this->setHeader('Content-Disposition', 'attachment;filename="' . $response['properties']['ContentStreamFilename']['value'] . '"'); | |
| 595 | + $this->setHeader('Content-Length', $response['properties']['ContentStreamLength']['value']); | |
| 596 | + $this->output = $contentStream; | |
| 597 | + } | |
| 598 | + | |
| 551 | 599 | } |
| 552 | 600 | |
| 553 | 601 | ?> |
| 554 | 602 | \ No newline at end of file | ... | ... |
webservice/atompub/cmis/KT_cmis_atom_service_helper.inc.php
| ... | ... | @@ -52,6 +52,7 @@ class KT_cmis_atom_service_helper { |
| 52 | 52 | static public function createObjectEntry(&$response, $cmisEntry, $parent, $method = 'GET') |
| 53 | 53 | { |
| 54 | 54 | $workspace = $response->getWorkspace(); |
| 55 | + $type = strtolower($cmisEntry['properties']['ObjectTypeId']['value']); | |
| 55 | 56 | |
| 56 | 57 | // create entry |
| 57 | 58 | $entry = $response->newEntry(); |
| ... | ... | @@ -68,24 +69,53 @@ class KT_cmis_atom_service_helper { |
| 68 | 69 | $responseElement = $response->newField('author'); |
| 69 | 70 | $element = $response->newField('name', 'admin', $responseElement); |
| 70 | 71 | $entry->appendChild($responseElement); |
| 72 | + | |
| 73 | + if (!empty($cmisEntry['properties']['ContentStreamLength']['value'])) | |
| 74 | + { | |
| 75 | + $field = $response->newElement('content'); | |
| 76 | + $field->appendChild($response->newAttr('type', $cmisEntry['properties']['ContentStreamMimeType']['value'])); | |
| 77 | + $field->appendChild($response->newAttr('href', CMIS_APP_BASE_URI . $workspace . '/' . $type | |
| 78 | + . '/' . $cmisEntry['properties']['ObjectId']['value'] | |
| 79 | + . '/' . $cmisEntry['properties']['ContentStreamFilename']['value'])); | |
| 80 | + $entry->appendChild($field); | |
| 81 | + } | |
| 71 | 82 | |
| 72 | 83 | // content & id tags |
| 73 | 84 | $id = $cmisEntry['properties']['ObjectId']['value']; |
| 74 | - $entry->appendChild($response->newField('content', $id)); | |
| 85 | + | |
| 75 | 86 | $response->newField('id', 'urn:uuid:' . $id, $entry); |
| 76 | 87 | |
| 77 | - $type = strtolower($cmisEntry['properties']['ObjectTypeId']['value']); | |
| 78 | - | |
| 79 | 88 | // links |
| 80 | 89 | $link = $response->newElement('link'); |
| 81 | - $link->appendChild($response->newAttr('rel','self')); | |
| 90 | + $link->appendChild($response->newAttr('rel', 'self')); | |
| 82 | 91 | $link->appendChild($response->newAttr('href', CMIS_APP_BASE_URI . $workspace . '/' . $type . '/' . $cmisEntry['properties']['ObjectId']['value'])); |
| 83 | 92 | $entry->appendChild($link); |
| 84 | 93 | |
| 85 | 94 | $link = $response->newElement('link'); |
| 86 | - $link->appendChild($response->newAttr('rel','edit')); | |
| 87 | - $link->appendChild($response->newAttr('href', CMIS_APP_BASE_URI . $workspace . '/' . $type . '/' . $cmisEntry['properties']['ObjectId']['value'])); | |
| 95 | + $link->appendChild($response->newAttr('rel', 'edit')); | |
| 96 | + $link->appendChild($response->newAttr('href', CMIS_APP_BASE_URI . $workspace . '/' . $type | |
| 97 | + . '/' . $cmisEntry['properties']['ObjectId']['value'])); | |
| 88 | 98 | $entry->appendChild($link); |
| 99 | + | |
| 100 | + if ((strtolower($cmisEntry['properties']['ObjectTypeId']['value']) == 'document') | |
| 101 | + && (!empty($cmisEntry['properties']['ContentStreamLength']['value']))) | |
| 102 | + { | |
| 103 | + $link = $response->newElement('link'); | |
| 104 | + $link->appendChild($response->newAttr('rel', 'edit-media')); | |
| 105 | + $link->appendChild($response->newAttr('type', $cmisEntry['properties']['ContentStreamMimeType']['value'])); | |
| 106 | + $link->appendChild($response->newAttr('href', CMIS_APP_BASE_URI . $workspace . '/' . $type | |
| 107 | + . '/' . $cmisEntry['properties']['ObjectId']['value'] | |
| 108 | + . '/' . $cmisEntry['properties']['ContentStreamFilename']['value'])); | |
| 109 | + $entry->appendChild($link); | |
| 110 | + | |
| 111 | + $link = $response->newElement('link'); | |
| 112 | + $link->appendChild($response->newAttr('rel', 'enclosure')); | |
| 113 | + $link->appendChild($response->newAttr('type', $cmisEntry['properties']['ContentStreamMimeType']['value'])); | |
| 114 | + $link->appendChild($response->newAttr('href', CMIS_APP_BASE_URI . $workspace . '/' . $type | |
| 115 | + . '/' . $cmisEntry['properties']['ObjectId']['value'] | |
| 116 | + . '/' . $cmisEntry['properties']['ContentStreamFilename']['value'])); | |
| 117 | + $entry->appendChild($link); | |
| 118 | + } | |
| 89 | 119 | |
| 90 | 120 | // according to spec this MUST be present, but spec says that links for function which are not supported |
| 91 | 121 | // do not need to be present, so unsure for the moment |
| ... | ... | @@ -115,14 +145,14 @@ class KT_cmis_atom_service_helper { |
| 115 | 145 | if (strtolower($cmisEntry['properties']['ObjectTypeId']['value']) == 'folder') |
| 116 | 146 | { |
| 117 | 147 | $link = $response->newElement('link'); |
| 118 | - $link->appendChild($response->newAttr('rel','children')); | |
| 148 | + $link->appendChild($response->newAttr('rel', 'children')); | |
| 119 | 149 | $link->appendChild($response->newAttr('href', CMIS_APP_BASE_URI . $workspace . '/' |
| 120 | 150 | . $type |
| 121 | 151 | . '/' . $cmisEntry['properties']['ObjectId']['value'] |
| 122 | 152 | . '/children')); |
| 123 | 153 | $entry->appendChild($link); |
| 124 | 154 | $link = $response->newElement('link'); |
| 125 | - $link->appendChild($response->newAttr('rel','descendants')); | |
| 155 | + $link->appendChild($response->newAttr('rel', 'descendants')); | |
| 126 | 156 | $link->appendChild($response->newAttr('href', CMIS_APP_BASE_URI . $workspace . '/' |
| 127 | 157 | . $type |
| 128 | 158 | . '/' . $cmisEntry['properties']['ObjectId']['value'] |
| ... | ... | @@ -151,8 +181,10 @@ class KT_cmis_atom_service_helper { |
| 151 | 181 | { |
| 152 | 182 | $link = $response->newElement('link'); |
| 153 | 183 | $link->appendChild($response->newAttr('rel', 'stream')); |
| 154 | - $link->appendChild($response->newAttr('href', CMIS_APP_BASE_URI . $workspace . '/' . $type . '/' | |
| 155 | - . $cmisEntry['properties']['ObjectId']['value'])); | |
| 184 | + $link->appendChild($response->newAttr('type', $cmisEntry['properties']['ContentStreamMimeType']['value'])); | |
| 185 | + $link->appendChild($response->newAttr('href', CMIS_APP_BASE_URI . $workspace . '/' . $type | |
| 186 | + . '/' . $cmisEntry['properties']['ObjectId']['value'] | |
| 187 | + . '/' . $cmisEntry['properties']['ContentStreamFilename']['value'])); | |
| 156 | 188 | $entry->appendChild($link); |
| 157 | 189 | } |
| 158 | 190 | |
| ... | ... | @@ -164,15 +196,21 @@ class KT_cmis_atom_service_helper { |
| 164 | 196 | // $link->appendChild($response->newAttr('href', CMIS_APP_BASE_URI . $workspace . '/' . $type . '/' . $cmisEntry['properties']['ParentId']['value'])); |
| 165 | 197 | // $entry->appendChild($link); |
| 166 | 198 | // } |
| 199 | +// | |
| 200 | +// $link = $response->newElement('link'); | |
| 201 | +// $link->appendChild($response->newAttr('rel', 'stream')); | |
| 202 | +// $link->appendChild($response->newAttr('href', CMIS_APP_BASE_URI . $workspace . '/' . $type | |
| 203 | +// . '/' . $cmisEntry['properties']['ObjectId']['value'] | |
| 204 | +// . '/' . $cmisEntry['properties']['ContentStreamFilename']['value'])); | |
| 167 | 205 | } |
| 168 | 206 | |
| 169 | 207 | $link = $response->newElement('link'); |
| 170 | - $link->appendChild($response->newAttr('rel','type')); | |
| 208 | + $link->appendChild($response->newAttr('rel', 'type')); | |
| 171 | 209 | $link->appendChild($response->newAttr('href', CMIS_APP_BASE_URI . $workspace . '/type/' . $type)); |
| 172 | 210 | $entry->appendChild($link); |
| 173 | 211 | |
| 174 | 212 | $link = $response->newElement('link'); |
| 175 | - $link->appendChild($response->newAttr('rel','repository')); | |
| 213 | + $link->appendChild($response->newAttr('rel', 'repository')); | |
| 176 | 214 | $link->appendChild($response->newAttr('href', CMIS_APP_BASE_URI . '/servicedocument')); |
| 177 | 215 | $entry->appendChild($link); |
| 178 | 216 | |
| ... | ... | @@ -198,7 +236,11 @@ class KT_cmis_atom_service_helper { |
| 198 | 236 | { |
| 199 | 237 | $propElement = $response->newElement('cmis:' . $property['type']); |
| 200 | 238 | $propElement->appendChild($response->newAttr('cmis:name', $propertyName)); |
| 201 | - if (!empty($property['value'])) { | |
| 239 | + if (!empty($property['value'])) | |
| 240 | + { | |
| 241 | + if ($propertyName == 'ContentStreamUri') { | |
| 242 | + $property['value'] = CMIS_APP_BASE_URI . $workspace . '/' . $type . '/' .$property['value']; | |
| 243 | + } | |
| 202 | 244 | $response->newField('cmis:value', CMISUtil::boolToString($property['value']), $propElement); |
| 203 | 245 | } |
| 204 | 246 | $propertiesElement->appendChild($propElement); | ... | ... |
webservice/atompub/cmis/index.php
| ... | ... | @@ -82,7 +82,7 @@ if ($workspace == 'servicedocument') |
| 82 | 82 | // CMIS service document setup |
| 83 | 83 | $APP->initServiceDocument(); |
| 84 | 84 | // User defined title tag |
| 85 | - $APP->addWorkspaceTag('dms','atom:title',$APP->repositoryInfo['repositoryName']); | |
| 85 | + $APP->addWorkspaceTag('dms','atom:title', $APP->repositoryInfo['repositoryName']); | |
| 86 | 86 | } |
| 87 | 87 | |
| 88 | 88 | /** |
| ... | ... | @@ -108,13 +108,10 @@ $APP->registerService('dms', 'checkedout', 'KT_cmis_atom_service_checkedout', 'C |
| 108 | 108 | $APP->registerService('dms', 'types', 'KT_cmis_atom_service_types', 'Object Type Collection', null, 'typeschildren'); |
| 109 | 109 | $APP->registerService('dms', 'types', 'KT_cmis_atom_service_types', 'Object Type Collection', null, 'typesdescendants'); |
| 110 | 110 | |
| 111 | -// NOTE $requestParams is meaningless if not actually requesting this service, so not a good way to register the service really | |
| 112 | 111 | if ($workspace != 'servicedocument') |
| 113 | 112 | { |
| 114 | - // should check this per workspace??? | |
| 115 | - $APP->registerService('dms', 'type', 'KT_cmis_atom_service_type', 'Object Type Collection', explode('/', $requestParams), 'typesdescendants'); | |
| 116 | - // FIXME HACK! see above, this one for documents | |
| 117 | - $APP->registerService('dms', 'document', 'KT_cmis_atom_service_document', 'Object Type Collection', explode('/', $requestParams), 'typesdescendants'); | |
| 113 | + $APP->registerService('dms', 'type', 'KT_cmis_atom_service_type', 'Object Type Entry', null, 'type'); | |
| 114 | + $APP->registerService('dms', 'document', 'KT_cmis_atom_service_document', 'Document Entry', null, 'document'); | |
| 118 | 115 | } |
| 119 | 116 | |
| 120 | 117 | //Execute the current url/header request | ... | ... |
webservice/classes/atompub/cmis/KT_cmis_atom_server.inc.php
| ... | ... | @@ -7,6 +7,37 @@ class KT_cmis_atom_server extends KT_atom_server { |
| 7 | 7 | |
| 8 | 8 | // override and extend as needed |
| 9 | 9 | public $repositoryInfo; |
| 10 | + public $headersSet = false; | |
| 11 | + | |
| 12 | + protected function hook_beforeDocRender($doc) | |
| 13 | + { | |
| 14 | + if ($doc->isContentDownload()) | |
| 15 | + { | |
| 16 | + // not going to create a feed/entry response, just returning the raw data | |
| 17 | + $this->output = $doc->getOutput(); | |
| 18 | + | |
| 19 | + // generic headers for all content downloads | |
| 20 | + header('Cache-Control: must-revalidate'); | |
| 21 | + // these two are to override the default header values | |
| 22 | + header('Expires:'); | |
| 23 | + header('Pragma:'); | |
| 24 | + | |
| 25 | + // prevent output of standard text/xml header | |
| 26 | + $this->headersSet = true; | |
| 27 | + | |
| 28 | + return false; | |
| 29 | + } | |
| 30 | + else if ($doc->notModified()) | |
| 31 | + { | |
| 32 | + // prevent output of standard text/xml header | |
| 33 | + $this->headersSet = true; | |
| 34 | + $this->setNoContent(true); | |
| 35 | + | |
| 36 | + return false; | |
| 37 | + } | |
| 38 | + | |
| 39 | + return true; | |
| 40 | + } | |
| 10 | 41 | |
| 11 | 42 | public function initServiceDocument() |
| 12 | 43 | { |
| ... | ... | @@ -56,13 +87,11 @@ class KT_cmis_atom_server extends KT_atom_server { |
| 56 | 87 | $element = $service->newElement('cmis:repositoryInfo'); |
| 57 | 88 | foreach($this->repositoryInfo as $key => $repoData) |
| 58 | 89 | { |
| 59 | - if ($key == 'rootFolderId') | |
| 60 | - { | |
| 90 | + if ($key == 'rootFolderId') { | |
| 61 | 91 | $repoData = CMIS_APP_BASE_URI . $workspace . '/folder/' . rawurlencode($repoData); |
| 62 | 92 | } |
| 63 | 93 | |
| 64 | - if (!is_array($repoData)) | |
| 65 | - { | |
| 94 | + if (!is_array($repoData)) { | |
| 66 | 95 | $element->appendChild($service->newElement('cmis:' . $key, $repoData)); |
| 67 | 96 | } |
| 68 | 97 | else |
| ... | ... | @@ -112,13 +141,19 @@ class KT_cmis_atom_server extends KT_atom_server { |
| 112 | 141 | public function getRegisteredService($workspace, $serviceName = NULL) |
| 113 | 142 | { |
| 114 | 143 | $serviceName = strtolower(trim($serviceName)); |
| 115 | - if(isset($this->services[$workspace][$serviceName])) | |
| 116 | - { | |
| 144 | + if(isset($this->services[$workspace][$serviceName])) { | |
| 117 | 145 | return $this->services[$workspace][$serviceName][0]; |
| 118 | 146 | } |
| 119 | 147 | |
| 120 | 148 | return false; |
| 121 | 149 | } |
| 150 | + | |
| 151 | + public function render() | |
| 152 | + { | |
| 153 | + ob_end_clean(); | |
| 154 | + if (!$this->headersSet) header('Content-type: text/xml'); | |
| 155 | + if ($this->renderBody) echo $this->output; | |
| 156 | + } | |
| 122 | 157 | |
| 123 | 158 | } |
| 124 | 159 | ... | ... |
webservice/classes/atompub/cmis/KT_cmis_atom_service.inc.php
| ... | ... | @@ -5,6 +5,28 @@ include_once(KT_ATOM_LIB_FOLDER.'KT_atom_service.inc.php'); |
| 5 | 5 | class KT_cmis_atom_service extends KT_atom_service { |
| 6 | 6 | |
| 7 | 7 | // override and extend as needed |
| 8 | + | |
| 9 | + protected $contentDownload = false; | |
| 10 | + | |
| 11 | + public public function isContentDownload() | |
| 12 | + { | |
| 13 | + return $this->contentDownload; | |
| 14 | + } | |
| 15 | + | |
| 16 | + public function notModified() | |
| 17 | + { | |
| 18 | + return $this->status == self::STATUS_NOT_MODIFIED; | |
| 19 | + } | |
| 20 | + | |
| 21 | + public function getOutput() | |
| 22 | + { | |
| 23 | + return $this->output; | |
| 24 | + } | |
| 25 | + | |
| 26 | + protected function setHeader($header = null, $value = null) | |
| 27 | + { | |
| 28 | + if ($header) header($header . ': ' . $value); | |
| 29 | + } | |
| 8 | 30 | |
| 9 | 31 | } |
| 10 | 32 | ?> |
| 11 | 33 | \ No newline at end of file | ... | ... |
webservice/classes/atompub/cmis/ObjectService.inc.php
| ... | ... | @@ -84,6 +84,25 @@ class ObjectService extends KTObjectService { |
| 84 | 84 | } |
| 85 | 85 | |
| 86 | 86 | /** |
| 87 | + * Fetches the content stream data for an object | |
| 88 | + * | |
| 89 | + * @param string $repositoryId | |
| 90 | + * @param string $objectId | |
| 91 | + * @return string $contentStream (binary or text data) | |
| 92 | + */ | |
| 93 | + function getContentStream($repositoryId, $objectId) | |
| 94 | + { | |
| 95 | + $result = parent::getContentStream($repositoryId, $objectId); | |
| 96 | + | |
| 97 | + if ($result['status_code'] == 0) { | |
| 98 | + return $result['results']; | |
| 99 | + } | |
| 100 | + else { | |
| 101 | + return new PEAR_Error($result['message']); | |
| 102 | + } | |
| 103 | + } | |
| 104 | + | |
| 105 | + /** | |
| 87 | 106 | * Moves a fileable object from one folder to another. |
| 88 | 107 | * |
| 89 | 108 | * @param object $repositoryId | ... | ... |