public function serviceDocument() { $service = new KT_cmis_atom_serviceDoc(KT_APP_BASE_URI); foreach ($this->services as $workspace => $collection) { //Creating the Default Workspace for use with standard atomPub Clients $ws = $service->newWorkspace(); $hadDetail = false; if (isset($this->workspaceDetail[$workspace])) { if (is_array($this->workspaceDetail[$workspace])) { foreach ($this->workspaceDetail[$workspace] as $wsTag => $wsValue) { $ws->appendChild($service->newElement($wsTag, $wsValue)); $hadDetail = true; } } } if (!$hadDetail) { $ws->appendChild($service->newElement('atom:title', $workspace)); } $ws->appendChild($service->newAttr('cmis:repositoryRelationship', $this->repositoryInfo['repositoryRelationship'])); // repository information $element = $service->newElement('cmis:repositoryInfo'); foreach ($this->repositoryInfo as $key => $repoData) { if ($key == 'rootFolderId') { $repoData = CMIS_APP_BASE_URI . $workspace . '/folder/' . rawurlencode($repoData); } if (!is_array($repoData)) { $element->appendChild($service->newElement('cmis:' . $key, $repoData)); } else { $elementSub = $service->newElement('cmis:' . $key); foreach ($repoData as $key2 => $data) { $elementSub->appendChild($service->newElement('cmis:' . $key2, CMISUtil::boolToString($data))); } $element->appendChild($elementSub); } } $ws->appendChild($element); foreach ($collection as $serviceName => $serviceInstance) { foreach ($serviceInstance as $instance) { $collectionStr = CMIS_APP_BASE_URI . $workspace . '/' . $serviceName . '/' . (is_array($instance['parameters']) ? implode('/', $instance['parameters']) . '/' : ''); $col = $service->newCollection($collectionStr, $instance['title'], $instance['collectionType'], $instance['accept'], $ws); } } } $this->output = $service->getAPPdoc(); }
function getObjectParents($repositoryId, $objectId, $includeAllowableActions, $includeRelationships, $filter = '') { $ancestry = array(); $objectId = CMISUtil::decodeObjectId($objectId, $typeId); // TODO - what about other types? only implementing folders and documents at the moment so ignore for now switch ($typeId) { case 'Document': $document = $this->ktapi->get_document_by_id($objectId); $parent = $document->ktapi_folder; $ancestry[] = $parent; break; case 'Folder': $folder = $this->ktapi->get_folder_by_id($objectId); $parent = $this->ktapi->get_folder_by_id($folder->get_parent_folder_id()); $ancestry[] = $parent; break; } $ancestry = CMISUtil::createParentObjectHierarchy($ancestry, $repository->getRepositoryURI, $this->ktapi); return $ancestry; }
/** * Gets the properties for the selected object * * @param string $repositoryId * @param string $objectId * @param boolean $includeAllowableActions * @param boolean $includeRelationships * @param string $returnVersion * @param string $filter * @return properties[] */ public function getProperties($repositoryId, $objectId, $includeAllowableActions, $includeRelationships, $returnVersion = false, $filter = '') { try { $propertyCollection = $this->ObjectService->getProperties($repositoryId, $objectId, $includeAllowableActions, $includeRelationships); } catch (Exception $e) { return array("status_code" => 1, "message" => $e->getMessage()); } $properties = CMISUtil::createObjectPropertiesEntry($propertyCollection); return array("status_code" => 0, "results" => $properties); }
public function setContentStream($repositoryId, $documentId, $overwriteFlag, $contentStream, $changeToken = null) { // if no document id was supplied, we are going to create the underlying physical document // NOTE while it might have been nice to keep this out of here, KTAPI has no method for creating a document without // a physical upload, so we cannot create the document first and then add the upload as a content stream, the // entire creation step needs to happen here. // Attempt to decode $documentId, use as is if not detected as encoded $tmpObjectId = $documentId; $tmpObjectId = CMISUtil::decodeObjectId($tmpObjectId, $tmpTypeId); if ($tmpTypeId != 'Unknown') { $documentId = $tmpObjectId; } // TODO deal with other types except documents // fetch type definition of supplied document $CMISDocument = new CMISDocumentObject($documentId, $this->ktapi); // if content stream is not allowed for this object type definition, throw a ConstraintViolationException if ($CMISDocument->getAttribute('contentStreamAllowed') == 'notAllowed') { // NOTE spec version 0.61c specifies both a ConstraintViolationException and a StreamNotSupportedException // for this case. Choosing to throw StreamNotSupportedException until the specification is clarified // as it is a more specific exception throw new StreamNotSupportedException('Content Streams are not allowed for this object type'); } $csFileName = $CMISDocument->getProperty('ContentStreamFilename'); if (!empty($csFileName) && !$overwriteFlag) { throw new ContentAlreadyExistsException('Unable to overwrite existing content stream'); } $tempfilename = CMISUtil::createTemporaryFile($contentStream); // update the document content from this temporary file as per usual // TODO Use checkin_document_with_metadata instead if metadata content submitted || update metadata separately? $response = $this->ktapi->checkin_document($documentId, $csFileName, 'CMIS setContentStream action', $tempfilename, false); if ($response['status_code'] != 0) { throw new StorageException('Unable to update the content stream. ' . $response['message']); } // else // { // $objectId = CMISUtil::encodeObjectId('Document', $response['results']['id']); // } @unlink($csFile); // update the CMIS document object with the content stream information // $CMISDocument->reload($document['result']['document_id']); return $CMISDocument->getProperty('ObjectId'); }
/** * Parses a hierarchy of CMIS objects to return an array format of a subset of information * required for a webservice response * * Essentially a reversal of createChildObjectHierarchy and createParentObjectHierarchy, * though the output may well be different to what went into that function * * @param array $input // input hierarchy to decode * @param string $linkText // 'child' or 'parent' - indicates direction of hierarchy => descending or ascending * @return array $hierarchy */ public static function decodeObjectHierarchy($input, $linkText) { $hierarchy = array(); // first, run through the base array to get the initial children foreach ($input as $key => $entry) { $object = $entry['object']; $properties = $object->getProperties(); $hierarchy[$key] = CMISUtil::createObjectPropertiesEntry($properties); } return $hierarchy; }
private function get($folderId) { $object = $this->ktapi->get_folder_by_id((int) $folderId); // error? if (PEAR::isError($object)) { // throw an exception? return $object; } // static $allowedChildObjectTypeIds; $objectProperties = $object->get_detail(); $this->_setPropertyInternal('ObjectId', CMISUtil::encodeObjectId($this->typeId, $objectProperties['id'])); // prevent doubled '/' chars $uri = preg_replace_callback('/([^:]\\/)\\//', create_function('$matches', 'return $matches[1];'), $this->uri . '/browse.php?fFolderId=' . $objectProperties['id']); // TODO this url is probably incorrect...needs to be checked // $this->_setPropertyInternal('Uri', $uri); $this->_setPropertyInternal('Uri', ''); // TODO what is this? Assuming it is the object type id, and not OUR document type? $this->_setPropertyInternal('ObjectTypeId', $this->getAttribute('typeId')); // Needed to distinguish type $this->_setPropertyInternal('BaseType', strtolower($this->getAttribute('typeId'))); $this->_setPropertyInternal('CreatedBy', $objectProperties['created_by']); // TODO cannot currently retrieve via ktapi or regular folder code - add as with created by $this->_setPropertyInternal('CreationDate', $objectProperties['created_date']); // TODO cannot currently retrieve via ktapi or regular folder code - add as with created by $this->_setPropertyInternal('LastModifiedBy', $objectProperties['modified_by']); // TODO cannot currently retrieve via ktapi or regular folder code - add as with created by $this->_setPropertyInternal('LastModificationDate', $objectProperties['modified_date']); $this->_setPropertyInternal('ChangeToken', null); $this->_setPropertyInternal('Name', $objectProperties['folder_name']); $this->_setPropertyInternal('ParentId', $objectProperties['parent_id']); $this->_setPropertyInternal('AllowedChildObjectTypeIds', array('Document', 'Folder')); $this->_setPropertyInternal('Author', $objectProperties['created_by']); }
/** * Deals with folder service POST actions. * This includes creation/moving of both folders and documents. */ public function POST_action() { $RepositoryService = new RepositoryService(); $repositories = $RepositoryService->getRepositories(); $repositoryId = $repositories[0]['repositoryId']; // set default action, objectId and typeId $action = 'create'; $objectId = null; $typeId = null; $folderId = $this->params[0]; $title = KT_cmis_atom_service_helper::getAtomValues($this->parsedXMLContent['@children'], 'title'); $summary = KT_cmis_atom_service_helper::getAtomValues($this->parsedXMLContent['@children'], 'summary'); $properties = array('name' => $title, 'summary' => $summary); // determine whether this is a folder or a document action // document action create will have a content tag <atom:content> or <content> containing base64 encoding of the document // move action will have an existing id supplied as a parameter - not sure how this works yet as the CMIS clients we are // testing don't support move functionality at this time (2009/07/23) and so we are presuming the following format: // /folder/<folderId>/children/<objectId> // also possible that there will be an existing ObjectId property, try to cater for both until we know how it really works // check for existing object id as parameter in url if (isset($this->params[2])) { $action = 'move'; $objectId = $this->params[2]; } $cmisObjectProperties = KT_cmis_atom_service_helper::getCmisProperties($this->parsedXMLContent['@children']['cmis:object']); // check for existing object id as property of submitted object data if (!empty($cmisObjectProperties['ObjectId'])) { $action = 'move'; $objectId = $cmisObjectProperties['ObjectId']; } // TODO there may be more to do for the checking of an existing object. // e.g. verifying that it does indeed exist, and throwing an exception if it does not: // "If the objected property is present but not valid an exception will be thrown" (from CMIS specification) // NOTE this exception should be thrown in the service API code and not here. // determine type if object is being moved if (!is_null($objectId)) { CMISUtil::decodeObjectId($objectId, $typeId); } // now check for content stream $content = KT_cmis_atom_service_helper::getAtomValues($this->parsedXMLContent['@children'], 'content'); // TODO this will possibly need to change somewhat once Relationship Objects come into play. if ($action == 'create' && is_null($content) || $typeId == 'Folder') { $type = 'folder'; } else { $type = 'document'; } $ObjectService = new ObjectService(KT_cmis_atom_service_helper::getKt()); $success = false; $error = null; if ($action == 'create') { if ($type == 'folder') { $newObjectId = $ObjectService->createFolder($repositoryId, ucwords($cmisObjectProperties['ObjectTypeId']), $properties, $folderId); } else { $newObjectId = $ObjectService->createDocument($repositoryId, ucwords($cmisObjectProperties['ObjectTypeId']), $properties, $folderId, $content); } // check if returned Object Id is a valid CMIS Object Id CMISUtil::decodeObjectId($newObjectId, $typeId); if ($typeId != 'Unknown') { $success = true; } else { $error = $newObjectId['message']; } } else { if ($action == 'move') { $response = $ObjectService->moveObject($repositoryId, $objectId, '', $folderId); if (!PEAR::isError($response)) { $success = true; } else { $error = $response->getMessage(); } // same object as before $newObjectId = $objectId; $typeId = ucwords($type); } } if ($success) { $this->setStatus($action == 'create' ? self::STATUS_CREATED : self::STATUS_UPDATED); $feed = KT_cmis_atom_service_helper::getObjectFeed($this, $ObjectService, $repositoryId, $newObjectId, 'POST'); } else { $feed = KT_cmis_atom_service_helper::getErrorFeed($this, self::STATUS_SERVER_ERROR, $error); } //Expose the responseFeed $this->responseFeed = $feed; }
function testVersioningService() { $VersioningService = new KTVersioningService($this->ktapi); $NavigationService = new KTNavigationService($this->ktapi); // set up the folder/doc tree structure with which we will be testing $this->createFolderDocStructure(); $RepositoryService = new KTRepositoryService(); $response = $RepositoryService->getRepositories(); $this->assertEqual($response['status_code'], 0); $this->assertNotNull($response['results'][0]); // // we only expect one repository $repository = $response['results'][0]; $repositoryId = $repository['repositoryId']; // test deletion of document via deleteAllVersions $versionSeriesId = 'D' . $this->docs[0]->get_documentid(); $response = $VersioningService->deleteAllVersions($repositoryId, $versionSeriesId); $this->assertEqual($response['status_code'], 0); $this->assertNotNull($response['results']); // TODO test checkout of document $documentId = CMISUtil::encodeObjectId('Document', $this->docs[1]->get_documentid()); $response = $VersioningService->checkOut($repositoryId, $documentId); $this->assertEqual($response['status_code'], 0); $this->assertNotNull($response['results']); //// // use this id for cancel checkout and checkin, not the original document id //// $pwcId = $response['results']; $pwcId = CMISUtil::encodeObjectId(DOCUMENT, $this->docs[1]->get_documentid()); // try again, this time it should fail - not working at the moment as ktapi registers the same user for download // even if already checked out, so no error is generated unless a different user attempts to do a checkout /* $response = $VersioningService->checkOut($repositoryId, $documentId); $this->assertEqual($response['status_code'], 1); $this->assertNotNull($response['message']); */ // test cancel checkout // echo "WITH: $pwcId<BR>"; $response = $VersioningService->cancelCheckOut($repositoryId, $pwcId); $this->assertEqual($response['status_code'], 0); $this->assertNotNull($response['results']); // test cancel checkout of document no longer checked out $response = $VersioningService->cancelCheckOut($repositoryId, $pwcId); $this->assertEqual($response['status_code'], 1); $this->assertNotNull($response['message']); // test listing of checked out documents // first check out the document again :) $response = $VersioningService->checkOut($repositoryId, $documentId); // now check that it appears in the listing $response = $NavigationService->getCheckedOutDocs($repositoryId, false, false); $this->assertEqual($response['status_code'], 0); $this->assertNotNull($response['results']); $this->assertTrue($this->findInPropertiesArray('ObjectId', $documentId, $response['results'])); // now let's cancel the checkout so that we can delete later during cleanup :) $response = $VersioningService->cancelCheckOut($repositoryId, $pwcId); // TODO test checkin // TODO add testing of failure conditions - e.g. checked out/immutable document (for all appropriate functions) // tear down the folder/doc tree structure with which we were testing $this->cleanupFolderDocStructure(); }
private function get($documentId) { $object = $this->ktapi->get_document_by_id((int) $documentId); // document does not exist? if (PEAR::isError($object)) { throw new ObjectNotFoundException('The document you are trying to access does not exist or is inaccessible'); } $objectProperties = $object->get_detail(); $this->_setPropertyInternal('ObjectId', CMISUtil::encodeObjectId($this->typeId, $objectProperties['document_id'])); // prevent doubled '/' chars $uri = preg_replace_callback('/([^:]\\/)\\//', create_function('$matches', 'return $matches[1];'), $this->uri . 'action.php?kt_path_info=ktnetwork.inlineview.actions.view&fDocumentId=' . $objectProperties['document_id']); // NOTE what about instead creating a downloadable version with appropriate link? see ktapi::download_document // also ktapidocument::get_download_url // $this->_setPropertyInternal('Uri', $uri); $this->_setPropertyInternal('Uri', ''); // TODO what is this? Assuming it is the object type id, and not OUR document type? $this->_setPropertyInternal('ObjectTypeId', $this->getAttribute('typeId')); // Needed to distinguish type $this->_setPropertyInternal('BaseType', strtolower($this->getAttribute('typeId'))); $this->_setPropertyInternal('CreatedBy', $objectProperties['created_by']); $this->_setPropertyInternal('CreationDate', $objectProperties['created_date']); $this->_setPropertyInternal('LastModifiedBy', $objectProperties['modified_by']); $this->_setPropertyInternal('LastModificationDate', $objectProperties['modified_date']); $this->_setPropertyInternal('ChangeToken', null); $this->_setPropertyInternal('Name', $objectProperties['title']); $this->_setPropertyInternal('ParentId', $objectProperties['folder_id']); $this->_setPropertyInternal('IsImmutable', $objectProperties['is_immutable']); // NOTE if access to older versions is allowed, this will need to be checked, else just set to yes // see ktapi::get_document_version_history // NOTE see ktapi::is_latest_version $this->_setPropertyInternal('IsLatestVersion', true); $this->_setPropertyInternal('IsMajorVersion', strstr($objectProperties['version'], '.') ? false : true); // NOTE if access to older versions is allowed, this will need to be checked, else just set to yes // see ktapi::get_document_version_history // NOTE see ktapi::is_latest_version $this->_setPropertyInternal('IsLatestMajorVersion', true); $this->_setPropertyInternal('VersionLabel', $objectProperties['version']); // VersionSeriesId should be the id of the latest version // NOTE this may change in the future but is easiest for the current implementation $this->_setPropertyInternal('VersionSeriesId', $objectProperties['version']); if ($objectProperties['checked_out_by'] != 'n/a') { $checkedOut = true; $checkedOutBy = $objectProperties['checked_out_by']; // TODO this is not what it will actually be, just a convenient placeholder $checkedOutId = $objectProperties['version']; } else { $checkedOut = false; $checkedOutBy = null; $checkedOutId = null; } $this->_setPropertyInternal('IsVersionSeriesCheckedOut', $checkedOut); $this->_setPropertyInternal('VersionSeriesCheckedOutBy', $checkedOutBy); // TODO presumably this is the ID of the Private Working Copy created on checkout? // will find out more when we do checkout/checkin $this->_setPropertyInternal('VersionSeriesCheckedOutId', $checkedOutId); // TODO currently not returned by KnowledgeTree? $this->_setPropertyInternal('CheckinComment', null); $this->_setPropertyInternal('ContentStreamLength', $objectProperties['filesize']); $this->_setPropertyInternal('ContentStreamMimeType', $objectProperties['mime_type']); $this->_setPropertyInternal('ContentStreamFilename', $objectProperties['filename']); $this->_setPropertyInternal('ContentStreamUri', $this->getProperty('ObjectId') . '/' . $objectProperties['filename']); $this->_setPropertyInternal('Author', $objectProperties['created_by']); }
public function checkIn($repositoryId, $documentId, $major, $contentStream = null, $changeToken = '', $properties = array(), $checkinComment = '') { $documentId = CMISUtil::decodeObjectId($documentId, $typeId); // throw updateConflictException if the operation is attempting to update an object that is no longer current (as determined by the repository). try { $pwc = new CMISDocumentObject($documentId, $this->ktapi); } catch (exception $e) { throw new UpdateConflictException($e->getMessage()); } // throw exception if the object is not versionable if (!$pwc->getAttribute('versionable')) { throw new ConstraintViolationException('This document is not versionable and may not be checked in'); } $RepositoryService = new CMISRepositoryService(); try { $typeDefinition = $RepositoryService->getTypeDefinition($repositoryId, $typeId); } catch (exception $e) { // if we can't get the type definition, then we can't store the content throw new StorageException($e->getMessage()); } if ($typeDefinition['attributes']['contentStreamAllowed'] == 'notAllowed' && !empty($contentStream)) { throw new StreamNotSupportedException('Content Streams are not supported'); } // check that this is the latest version if ($pwc->getProperty('IsLatestVersion') != true) { throw new VersioningException('The document is not the latest version and cannot be checked in'); } // now do the checkin $tempfilename = CMISUtil::createTemporaryFile($contentStream); $response = $this->ktapi->checkin_document($documentId, $pwc->getProperty('ContentStreamFilename'), $reason, $tempfilename, $major, $sig_username, $sig_password); // if there was any error in cancelling the checkout if ($response['status_code'] == 1) { throw new RuntimeException('There was an error checking in the document: ' . $response['message']); } return CMISUtil::encodeObjectId(DOCUMENT, $documentId); }
/** * Returns a list of checked out documents from the selected repository * * @param string $repositoryId * @param string $folderId The folder for which checked out docs are requested * @param string $filter * @param boolean $includeAllowableActions * @param boolean $includeRelationships * @param int $maxItems * @param int $skipCount * @return array $checkedout The collection of checked out documents */ function getCheckedOutDocs($repositoryId, $includeAllowableActions, $includeRelationships, $folderId = null, $filter = '', $maxItems = 0, $skipCount = 0) { $checkedout = $this->NavigationService->getCheckedOutDocs($repositoryId, $includeAllowableActions, $includeRelationships, $folderId, $filter, $maxItems, $skipCount); if (PEAR::isError($checkedout)) { return array("status_code" => 1, "message" => "Failed getting list of checked out documents"); } // convert to array format for external code $co = array(); foreach ($checkedout as $documentProperties) { $co[] = CMISUtil::createObjectPropertiesEntry($documentProperties); } return array("status_code" => 0, "results" => $co); }
public static function getFolderId($path, &$ktapi) { // lose first item array_shift($path); $numQ = count($path); $numFolders = $numQ; $folderId = 1; $start = 0; while ($start < $numFolders) { $name = $path[$numQ - $numFolders + $start]; // fix for possible url encoding issue $name = str_replace('%2520', '%20', $name); $folderName = urldecode($name); $folder = $ktapi->get_folder_by_name($folderName, $folderId); $folderId = $folder->get_folderid(); ++$start; } return CMISUtil::encodeObjectId('Folder', $folderId); }
/** * Fetches information about the selected repository * * @param string $repositoryId */ public function getRepositoryInfo($repositoryId) { $repositoryInfo = $this->RepositoryService->getRepositoryInfo($repositoryId); if (PEAR::isError($repositoryInfo)) { return array("status_code" => 1, "message" => "Failed getting repository information"); } // TODO output this manually, the function works but only for some objects so rather avoid it completely? // NOTE the problems appear to be due to recursive objects return array("status_code" => 0, "results" => CMISUtil::objectToArray($repositoryInfo)); }
public function checkIn($repositoryId, $documentId, $major, $changeToken = '', $properties = array(), $contentStream = null, $checkinComment = '') { $documentId = CMISUtil::decodeObjectId($documentId, $typeId); // throw updateConflictException if the operation is attempting to update an object that is no longer current (as determined by the repository). try { $pwc = new CMISDocumentObject($documentId, $this->ktapi); } catch (exception $e) { throw new UpdateConflictException($e->getMessage()); } // throw exception if the object is not versionable if (!$pwc->getAttribute('versionable')) { throw new ConstraintViolationException('This document is not versionable and may not be checked in'); } return $documentId; }