Commit 12e3df4d6214881520139fafa2e184b99021da3c

Authored by Paul Barrett
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
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
1   -this is some text
2 0 \ No newline at end of file
tests/ktcmis/myf7FFE.tmp deleted
1   -this is some text
2 0 \ No newline at end of file
tests/ktcmis/myf8F68.tmp deleted
1   -this is some text
2 0 \ No newline at end of file
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
... ...