Commit 2b68ca023d074734c213a81885a23ac5378efc9d
1 parent
bf8f7fb7
Story ID: 966930. Added support for move via AtomPub and CMIS
In order to be able to move content using a CMIS client, as a user, I would like KnowledgeTree's CMIS interface to support move Committed by: Paul Barrett Reviewed by: Jarrett Jordaan
Showing
5 changed files
with
279 additions
and
70 deletions
lib/api/ktcmis/ktcmis.inc.php
| @@ -538,7 +538,7 @@ class KTObjectService extends KTCMISBase { | @@ -538,7 +538,7 @@ class KTObjectService extends KTCMISBase { | ||
| 538 | * @param string $versioningState optional version state value: checkedout/major/minor | 538 | * @param string $versioningState optional version state value: checkedout/major/minor |
| 539 | * @return string $objectId The id of the created folder object | 539 | * @return string $objectId The id of the created folder object |
| 540 | */ | 540 | */ |
| 541 | - function createDocument($repositoryId, $typeId, $properties, $folderId = null, | 541 | + public function createDocument($repositoryId, $typeId, $properties, $folderId = null, |
| 542 | $contentStream = null, $versioningState = null) | 542 | $contentStream = null, $versioningState = null) |
| 543 | { | 543 | { |
| 544 | $objectId = null; | 544 | $objectId = null; |
| @@ -570,7 +570,7 @@ class KTObjectService extends KTCMISBase { | @@ -570,7 +570,7 @@ class KTObjectService extends KTCMISBase { | ||
| 570 | * @param string $folderId The id of the folder which will be the parent of the created folder object | 570 | * @param string $folderId The id of the folder which will be the parent of the created folder object |
| 571 | * @return string $objectId The id of the created folder object | 571 | * @return string $objectId The id of the created folder object |
| 572 | */ | 572 | */ |
| 573 | - function createFolder($repositoryId, $typeId, $properties, $folderId) | 573 | + public function createFolder($repositoryId, $typeId, $properties, $folderId) |
| 574 | { | 574 | { |
| 575 | $objectId = null; | 575 | $objectId = null; |
| 576 | 576 | ||
| @@ -592,6 +592,34 @@ class KTObjectService extends KTCMISBase { | @@ -592,6 +592,34 @@ class KTObjectService extends KTCMISBase { | ||
| 592 | } | 592 | } |
| 593 | 593 | ||
| 594 | /** | 594 | /** |
| 595 | + * Moves a fileable object from one folder to another. | ||
| 596 | + * | ||
| 597 | + * @param object $repositoryId | ||
| 598 | + * @param object $objectId | ||
| 599 | + * @param object $changeToken [optional] | ||
| 600 | + * @param object $targetFolderId | ||
| 601 | + * @param object $sourceFolderId [optional] | ||
| 602 | + */ | ||
| 603 | + public function moveObject($repositoryId, $objectId, $changeToken = '', $targetFolderId, $sourceFolderId = null) | ||
| 604 | + { | ||
| 605 | + try { | ||
| 606 | + $this->ObjectService->moveObject($repositoryId, $objectId, $changeToken, $targetFolderId, $sourceFolderId); | ||
| 607 | + } | ||
| 608 | + catch (Exception $e) | ||
| 609 | + { | ||
| 610 | + return array( | ||
| 611 | + "status_code" => 1, | ||
| 612 | + "message" => $e->getMessage() | ||
| 613 | + ); | ||
| 614 | + } | ||
| 615 | + | ||
| 616 | + return array( | ||
| 617 | + 'status_code' => 0, | ||
| 618 | + 'results' => $objectId | ||
| 619 | + ); | ||
| 620 | + } | ||
| 621 | + | ||
| 622 | + /** | ||
| 595 | * Deletes an object from the repository | 623 | * Deletes an object from the repository |
| 596 | * | 624 | * |
| 597 | * @param string $repositoryId | 625 | * @param string $repositoryId |
| @@ -601,10 +629,10 @@ class KTObjectService extends KTCMISBase { | @@ -601,10 +629,10 @@ class KTObjectService extends KTCMISBase { | ||
| 601 | */ | 629 | */ |
| 602 | // NOTE Invoking this service method on an object SHALL not delete the entire version series for a Document Object. | 630 | // NOTE Invoking this service method on an object SHALL not delete the entire version series for a Document Object. |
| 603 | // To delete an entire version series, use the deleteAllVersions() service | 631 | // To delete an entire version series, use the deleteAllVersions() service |
| 604 | - function deleteObject($repositoryId, $objectId, $changeToken = null) | 632 | + public function deleteObject($repositoryId, $objectId, $changeToken = null) |
| 605 | { | 633 | { |
| 606 | try { | 634 | try { |
| 607 | - $result = $this->ObjectService->deleteObject($repositoryId, $objectId, $changeToken); | 635 | + $this->ObjectService->deleteObject($repositoryId, $objectId, $changeToken); |
| 608 | } | 636 | } |
| 609 | catch (Exception $e) | 637 | catch (Exception $e) |
| 610 | { | 638 | { |
lib/api/ktcmis/services/CMISObjectService.inc.php
| @@ -198,7 +198,7 @@ class CMISObjectService { | @@ -198,7 +198,7 @@ class CMISObjectService { | ||
| 198 | { | 198 | { |
| 199 | // TODO consider checking whether content is encoded (currently we expect encoded) | 199 | // TODO consider checking whether content is encoded (currently we expect encoded) |
| 200 | // TODO choose between this and the alternative decode function (see CMISUtil class) | 200 | // TODO choose between this and the alternative decode function (see CMISUtil class) |
| 201 | - // The current one appears to be miles better (1/0/3 vs 14/4/57 on respective test files) | 201 | + // this will require some basic benchmarking |
| 202 | $contentStream = CMISUtil::decodeChunkedContentStream($contentStream); | 202 | $contentStream = CMISUtil::decodeChunkedContentStream($contentStream); |
| 203 | 203 | ||
| 204 | // NOTE There is a function in CMISUtil to do this, written for the unit tests but since KTUploadManager exists | 204 | // NOTE There is a function in CMISUtil to do this, written for the unit tests but since KTUploadManager exists |
| @@ -390,7 +390,7 @@ class CMISObjectService { | @@ -390,7 +390,7 @@ class CMISObjectService { | ||
| 390 | $response = $this->ktapi->create_folder((int)$folderId, $properties['name'], $sig_username = '', $sig_password = '', $reason = ''); | 390 | $response = $this->ktapi->create_folder((int)$folderId, $properties['name'], $sig_username = '', $sig_password = '', $reason = ''); |
| 391 | if ($response['status_code'] != 0) | 391 | if ($response['status_code'] != 0) |
| 392 | { | 392 | { |
| 393 | - throw new StorageException('The repository was unable to create the folder - ' . $response['message']); | 393 | + throw new StorageException('The repository was unable to create the folder: ' . $response['message']); |
| 394 | } | 394 | } |
| 395 | else | 395 | else |
| 396 | { | 396 | { |
| @@ -401,6 +401,74 @@ class CMISObjectService { | @@ -401,6 +401,74 @@ class CMISObjectService { | ||
| 401 | } | 401 | } |
| 402 | 402 | ||
| 403 | /** | 403 | /** |
| 404 | + * Moves a fileable object from one folder to another. | ||
| 405 | + * | ||
| 406 | + * @param object $repositoryId | ||
| 407 | + * @param object $objectId | ||
| 408 | + * @param object $changeToken [optional] | ||
| 409 | + * @param object $targetFolderId | ||
| 410 | + * @param object $sourceFolderId [optional] | ||
| 411 | + */ | ||
| 412 | + // TODO versioningException: The repository MAY throw this exception if the object is a non-current Document Version. | ||
| 413 | + // TODO check whether object is in fact fileable? not strictly needed, but possibly should be here. | ||
| 414 | + public function moveObject($repositoryId, $objectId, $changeToken = '', $targetFolderId, $sourceFolderId = null) | ||
| 415 | + { | ||
| 416 | + // The $sourceFolderId parameter SHALL be specified if the Repository supports the optional 'unfiling' capability | ||
| 417 | + if (is_null($sourceFolderId)) | ||
| 418 | + { | ||
| 419 | + $RepositoryService = new CMISRepositoryService(); | ||
| 420 | + $info = $RepositoryService->getRepositoryInfo($repositoryId); | ||
| 421 | + $capabilities = $info->getCapabilities(); | ||
| 422 | + // check for unfiling capability | ||
| 423 | + // NOTE this is only required once/if KnowledgeTree allows the source folder id to be optional, | ||
| 424 | + // but it is required for CMIS specification compliance. | ||
| 425 | + if ($capabilities->hasCapabilityUnfiling() === 'true') { | ||
| 426 | + throw new RuntimeException('The source folder id MUST be supplied when unfiling is supported.'); | ||
| 427 | + } | ||
| 428 | + } | ||
| 429 | + | ||
| 430 | + // Attempt to decode $objectId, use as is if not detected as encoded | ||
| 431 | + $tmpObjectId = $objectId; | ||
| 432 | + $tmpObjectId = CMISUtil::decodeObjectId($tmpObjectId, $typeId); | ||
| 433 | + if ($tmpTypeId != 'Unknown') $objectId = $tmpObjectId; | ||
| 434 | + | ||
| 435 | + $targetFolderId = CMISUtil::decodeObjectId($targetFolderId); | ||
| 436 | + | ||
| 437 | + // check type id of object against allowed child types for destination folder | ||
| 438 | + $CMISFolder = new CMISFolderObject($targetFolderId, $this->ktapi); | ||
| 439 | + $allowed = $CMISFolder->getProperty('AllowedChildObjectTypeIds'); | ||
| 440 | + if (!is_array($allowed) || !in_array($typeId, $allowed)) | ||
| 441 | + { | ||
| 442 | + throw new ConstraintViolationException('Parent folder may not hold objects of this type (' . $typeId . ')'); | ||
| 443 | + } | ||
| 444 | + | ||
| 445 | + // throw updateConflictException if the operation is attempting to update an object that is no longer current (as determined by the repository). | ||
| 446 | + $exists = CMISUtil::contentExists($typeId, $objectId, $this->ktapi); | ||
| 447 | + if (!$exists) { | ||
| 448 | + throw new updateConflictException('Unable to move the object as it cannot be found.'); | ||
| 449 | + } | ||
| 450 | + | ||
| 451 | + // TODO add reasons and sig data | ||
| 452 | + // attempt to move object | ||
| 453 | + if ($typeId == 'Folder') { | ||
| 454 | + $response = $this->ktapi->move_folder($objectId, $targetFolderId, $reason, $sig_username, $sig_password); | ||
| 455 | + } | ||
| 456 | + else if ($typeId == 'Document') { | ||
| 457 | + $response = $this->ktapi->move_document($objectId, $targetFolderId, $reason, null, null, $sig_username, $sig_password); | ||
| 458 | + } | ||
| 459 | + else { | ||
| 460 | + $response['status_code'] = 1; | ||
| 461 | + $response['message'] = 'The object type could not be determined.'; | ||
| 462 | + } | ||
| 463 | + | ||
| 464 | + // if failed, throw StorageException | ||
| 465 | + if ($response['status_code'] != 0) | ||
| 466 | + { | ||
| 467 | + throw new StorageException('The repository was unable to move the object: ' . $response['message']); | ||
| 468 | + } | ||
| 469 | + } | ||
| 470 | + | ||
| 471 | + /** | ||
| 404 | * Deletes an object from the repository | 472 | * Deletes an object from the repository |
| 405 | * | 473 | * |
| 406 | * @param string $repositoryId | 474 | * @param string $repositoryId |
| @@ -415,6 +483,7 @@ class CMISObjectService { | @@ -415,6 +483,7 @@ class CMISObjectService { | ||
| 415 | // determine object type and internal id | 483 | // determine object type and internal id |
| 416 | $objectId = CMISUtil::decodeObjectId($objectId, $typeId); | 484 | $objectId = CMISUtil::decodeObjectId($objectId, $typeId); |
| 417 | 485 | ||
| 486 | + // TODO this should probably be a function, it is now used in two places... | ||
| 418 | // throw updateConflictException if the operation is attempting to update an object that is no longer current (as determined by the repository). | 487 | // throw updateConflictException if the operation is attempting to update an object that is no longer current (as determined by the repository). |
| 419 | $exists = true; | 488 | $exists = true; |
| 420 | if ($typeId == 'Folder') { | 489 | if ($typeId == 'Folder') { |
| @@ -428,6 +497,7 @@ class CMISObjectService { | @@ -428,6 +497,7 @@ class CMISObjectService { | ||
| 428 | if (PEAR::isError($object)) { | 497 | if (PEAR::isError($object)) { |
| 429 | $exists = false; | 498 | $exists = false; |
| 430 | } | 499 | } |
| 500 | + // TODO check deleted status? | ||
| 431 | } | 501 | } |
| 432 | else { | 502 | else { |
| 433 | $exists = false; | 503 | $exists = false; |
| @@ -482,8 +552,6 @@ class CMISObjectService { | @@ -482,8 +552,6 @@ class CMISObjectService { | ||
| 482 | if ($result['status_code'] == 1) { | 552 | if ($result['status_code'] == 1) { |
| 483 | throw new RuntimeException('There was an error deleting the object: ' . $result['message']); | 553 | throw new RuntimeException('There was an error deleting the object: ' . $result['message']); |
| 484 | } | 554 | } |
| 485 | - | ||
| 486 | - return true; | ||
| 487 | } | 555 | } |
| 488 | 556 | ||
| 489 | /** | 557 | /** |
lib/api/ktcmis/util/CMISUtil.inc.php
| @@ -397,30 +397,33 @@ class CMISUtil { | @@ -397,30 +397,33 @@ class CMISUtil { | ||
| 397 | return $temp; | 397 | return $temp; |
| 398 | } | 398 | } |
| 399 | 399 | ||
| 400 | - // TODO run evaluations on each of the following two functions and determine which | ||
| 401 | - // is generally more efficienct | ||
| 402 | - | ||
| 403 | /** | 400 | /** |
| 404 | - * Alternative function for decoding chunked streams, this will decode in blocks of 4. | ||
| 405 | - * Not sure which method is more efficient, this or the function below (this does not | ||
| 406 | - * re-encode but I am working on removing that step for the other function.) | 401 | + * Checks the contentStream and ensures that it is a correct base64 string; |
| 402 | + * This is purely for clients such as CMISSpaces breaking the content into | ||
| 403 | + * chunks before base64 encoding. | ||
| 404 | + * | ||
| 405 | + * If the stream is chunked, it is decoded in chunks and sent back as a single stream. | ||
| 406 | + * If it is not chunked it is decoded as is and sent back as a single stream. | ||
| 407 | + * | ||
| 408 | + * NOTE there is an alternative version of this function called decodeChunkedContentStreamLong. | ||
| 409 | + * that version checks line lengths, which should not be necessary. | ||
| 410 | + * this version merely splits on one or two "=" which is less complex and possibly faster (test this assumption) | ||
| 411 | + * (one or two "=" signs is the specified padding used for base64 encoding at the end of an encoded string, when needed) | ||
| 407 | * | 412 | * |
| 408 | - * NOTE The current one appears to be much slower (14/4/57 vs 1/0/3 on respective test files) | ||
| 409 | - * | ||
| 410 | - * @param string $contentStream the base64 encoded content stream | ||
| 411 | - * @return string $decoded the decoded content stream | 413 | + * @param object $contentStream |
| 414 | + * @return string decoded | ||
| 412 | */ | 415 | */ |
| 413 | - static public function decodeContentStream($contentStream) | 416 | + static public function decodeChunkedContentStream($contentStream) |
| 414 | { | 417 | { |
| 415 | $decoded = ''; | 418 | $decoded = ''; |
| 416 | 419 | ||
| 417 | - $contentStream = preg_replace('/\r?\n+/', '', $contentStream); | ||
| 418 | - | ||
| 419 | - // decode in chunks or 4 chars at a time | ||
| 420 | - for($i = 0, $len = strlen($contentStream); $i < $len; $i += 4) { | ||
| 421 | - $decoded .= base64_decode(substr($contentStream, $i, 4)); | 420 | + // split the content stream on ={1,2} |
| 421 | + $parts = preg_split('/={1,2}/', $contentStream, null, PREG_SPLIT_NO_EMPTY); | ||
| 422 | + foreach($parts as $part) { | ||
| 423 | + // decode, append to output to be re-encoded | ||
| 424 | + $decoded .= base64_decode($part); | ||
| 422 | } | 425 | } |
| 423 | - | 426 | + |
| 424 | return $decoded; | 427 | return $decoded; |
| 425 | } | 428 | } |
| 426 | 429 | ||
| @@ -438,7 +441,7 @@ class CMISUtil { | @@ -438,7 +441,7 @@ class CMISUtil { | ||
| 438 | * @param object $contentStream | 441 | * @param object $contentStream |
| 439 | * @return string decoded | 442 | * @return string decoded |
| 440 | */ | 443 | */ |
| 441 | - static public function decodeChunkedContentStream($contentStream) | 444 | + static public function decodeChunkedContentStreamLong($contentStream) |
| 442 | { | 445 | { |
| 443 | // check the content stream for any lines of unusual length (except the last line, which can be any length) | 446 | // check the content stream for any lines of unusual length (except the last line, which can be any length) |
| 444 | $count = -1; | 447 | $count = -1; |
| @@ -464,7 +467,7 @@ class CMISUtil { | @@ -464,7 +467,7 @@ class CMISUtil { | ||
| 464 | { | 467 | { |
| 465 | // check for a new chunk | 468 | // check for a new chunk |
| 466 | // either we have an equals sign (or two) | 469 | // either we have an equals sign (or two) |
| 467 | - if (preg_match('/([^=]*={0,2})(.*)/', $line, $matches)) | 470 | + if (preg_match('/([^=]*={1,2})(.*)/', $line, $matches)) |
| 468 | { | 471 | { |
| 469 | $lastChunk = $matches[1]; | 472 | $lastChunk = $matches[1]; |
| 470 | $nextChunk = $matches[2]; | 473 | $nextChunk = $matches[2]; |
| @@ -507,6 +510,39 @@ class CMISUtil { | @@ -507,6 +510,39 @@ class CMISUtil { | ||
| 507 | 510 | ||
| 508 | return $decoded; | 511 | return $decoded; |
| 509 | } | 512 | } |
| 513 | + | ||
| 514 | + /** | ||
| 515 | + * Function to check whether a specified object exists within the KnowledgeTree system | ||
| 516 | + * | ||
| 517 | + * @param string $typeId | ||
| 518 | + * @param string $objectId | ||
| 519 | + * @param object $ktapi | ||
| 520 | + * @return boolean | ||
| 521 | + */ | ||
| 522 | + public function contentExists($typeId, $objectId, &$ktapi) | ||
| 523 | + { | ||
| 524 | + $exists = true; | ||
| 525 | + if ($typeId == 'Folder') | ||
| 526 | + { | ||
| 527 | + $object = $ktapi->get_folder_by_id($objectId); | ||
| 528 | + if (PEAR::isError($object)) { | ||
| 529 | + $exists = false; | ||
| 530 | + } | ||
| 531 | + } | ||
| 532 | + else if ($typeId == 'Document') | ||
| 533 | + { | ||
| 534 | + $object = $ktapi->get_document_by_id($objectId); | ||
| 535 | + if (PEAR::isError($object)) { | ||
| 536 | + $exists = false; | ||
| 537 | + } | ||
| 538 | + // TODO check deleted status? | ||
| 539 | + } | ||
| 540 | + else { | ||
| 541 | + $exists = false; | ||
| 542 | + } | ||
| 543 | + | ||
| 544 | + return $exists; | ||
| 545 | + } | ||
| 510 | 546 | ||
| 511 | } | 547 | } |
| 512 | 548 |
webservice/atompub/cmis/KT_cmis_atom_server.services.inc.php
| @@ -36,11 +36,14 @@ include_once CMIS_ATOM_LIB_FOLDER . 'ObjectService.inc.php'; | @@ -36,11 +36,14 @@ include_once CMIS_ATOM_LIB_FOLDER . 'ObjectService.inc.php'; | ||
| 36 | include_once CMIS_ATOM_LIB_FOLDER . 'VersioningService.inc.php'; | 36 | include_once CMIS_ATOM_LIB_FOLDER . 'VersioningService.inc.php'; |
| 37 | include_once 'KT_cmis_atom_service_helper.inc.php'; | 37 | include_once 'KT_cmis_atom_service_helper.inc.php'; |
| 38 | 38 | ||
| 39 | -// TODO auth failed response requires WWW-Authenticate: Basic realm="KnowledgeTree DMS" header | 39 | +// TODO consider changing all responses from the webservice layer to return PEAR errors or success results instead of the half/half we have at the moment. |
| 40 | +// the half/half occurred because on initial services PEAR Error seemed unnecessary, but it has proven useful for some of the newer functions :) | ||
| 40 | 41 | ||
| 41 | /** | 42 | /** |
| 42 | * AtomPub Service: folder | 43 | * AtomPub Service: folder |
| 43 | */ | 44 | */ |
| 45 | +// TODO implement failure responses for documents and folders not found. (KTS-4364: http://issues.knowledgetree.com/browse/KTS-4364) | ||
| 46 | +// NOTE what about documents which are deleted, need to respond that those are also not found. | ||
| 44 | class KT_cmis_atom_service_folder extends KT_atom_service { | 47 | class KT_cmis_atom_service_folder extends KT_atom_service { |
| 45 | 48 | ||
| 46 | /** | 49 | /** |
| @@ -92,7 +95,7 @@ class KT_cmis_atom_service_folder extends KT_atom_service { | @@ -92,7 +95,7 @@ class KT_cmis_atom_service_folder extends KT_atom_service { | ||
| 92 | 95 | ||
| 93 | /** | 96 | /** |
| 94 | * Deals with folder service POST actions. | 97 | * Deals with folder service POST actions. |
| 95 | - * This includes creation of both folders and documents. | 98 | + * This includes creation/moving of both folders and documents. |
| 96 | */ | 99 | */ |
| 97 | public function POST_action() | 100 | public function POST_action() |
| 98 | { | 101 | { |
| @@ -100,48 +103,101 @@ class KT_cmis_atom_service_folder extends KT_atom_service { | @@ -100,48 +103,101 @@ class KT_cmis_atom_service_folder extends KT_atom_service { | ||
| 100 | $repositories = $RepositoryService->getRepositories(); | 103 | $repositories = $RepositoryService->getRepositories(); |
| 101 | $repositoryId = $repositories[0]['repositoryId']; | 104 | $repositoryId = $repositories[0]['repositoryId']; |
| 102 | 105 | ||
| 106 | + // set default action, objectId and typeId | ||
| 107 | + $action = 'create'; | ||
| 108 | + $objectId = null; | ||
| 109 | + $typeId = null; | ||
| 110 | + | ||
| 103 | $folderId = $this->params[0]; | 111 | $folderId = $this->params[0]; |
| 104 | $title = KT_cmis_atom_service_helper::getAtomValues($this->parsedXMLContent['@children'], 'title'); | 112 | $title = KT_cmis_atom_service_helper::getAtomValues($this->parsedXMLContent['@children'], 'title'); |
| 105 | $summary = KT_cmis_atom_service_helper::getAtomValues($this->parsedXMLContent['@children'], 'summary'); | 113 | $summary = KT_cmis_atom_service_helper::getAtomValues($this->parsedXMLContent['@children'], 'summary'); |
| 106 | 114 | ||
| 107 | $properties = array('name' => $title, 'summary' => $summary); | 115 | $properties = array('name' => $title, 'summary' => $summary); |
| 108 | 116 | ||
| 109 | - // determine whether this is a folder or a document create | ||
| 110 | - // document create will have a content tag <atom:content> or <content> containing base64 encoding of the document | ||
| 111 | - $content = KT_cmis_atom_service_helper::getAtomValues($this->parsedXMLContent['@children'], 'content'); | 117 | + // determine whether this is a folder or a document action |
| 118 | + // document action create will have a content tag <atom:content> or <content> containing base64 encoding of the document | ||
| 119 | + // move action will have an existing id supplied as a parameter - not sure how this works yet as the CMIS clients we are | ||
| 120 | + // testing don't support move functionality at this time (2009/07/23) and so we are presuming the following format: | ||
| 121 | + // /folder/<folderId>/children/<objectId> | ||
| 122 | + // also possible that there will be an existing ObjectId property, try to cater for both until we know how it really works | ||
| 123 | + | ||
| 124 | + // check for existing object id as parameter in url | ||
| 125 | + if (isset($this->params[2])) | ||
| 126 | + { | ||
| 127 | + $action = 'move'; | ||
| 128 | + $objectId = $this->params[2]; | ||
| 129 | + } | ||
| 130 | + | ||
| 131 | + $cmisObjectProperties = KT_cmis_atom_service_helper::getCmisProperties($this->parsedXMLContent['@children']['cmis:object'] | ||
| 132 | + [0]['@children']['cmis:properties'] | ||
| 133 | + [0]['@children']); | ||
| 134 | + | ||
| 135 | + // check for existing object id as property of submitted object data | ||
| 136 | + if (!empty($cmisObjectProperties['ObjectId'])) | ||
| 137 | + { | ||
| 138 | + $action = 'move'; | ||
| 139 | + $objectId = $cmisObjectProperties['ObjectId']; | ||
| 140 | + } | ||
| 141 | + | ||
| 142 | + // TODO there may be more to do for the checking of an existing object. | ||
| 143 | + // e.g. verifying that it does indeed exist, and throwing an exception if it does not: | ||
| 144 | + // "If the objected property is present but not valid an exception will be thrown" (from CMIS specification) | ||
| 145 | + // NOTE this exception should be thrown in the service API code and not here. | ||
| 146 | + | ||
| 147 | + // determine type if object is being moved | ||
| 148 | + if (!is_null($objectId)) { | ||
| 149 | + CMISUtil::decodeObjectId($objectId, $typeId); | ||
| 150 | + } | ||
| 112 | 151 | ||
| 113 | - // check content for weird chars | ||
| 114 | - $matches = array(); | ||
| 115 | - preg_match('/[^\w\d\/\+\n]*/', $content, $matches); | 152 | + // now check for content stream |
| 153 | + $content = KT_cmis_atom_service_helper::getAtomValues($this->parsedXMLContent['@children'], 'content'); | ||
| 116 | 154 | ||
| 117 | - if (is_null($content)) { | 155 | + // check content for weird chars - don't think this serves a purpose any longer, should probably be removed. |
| 156 | + // was meant to check for any non-base64 characters in the content string. | ||
| 157 | + // preg_match('/[^\w\d\/\+=\n]*/', $content); | ||
| 158 | + // TODO this will possibly need to change somewhat once Relationship Objects come into play. | ||
| 159 | + if ((($action == 'create') && (is_null($content))) || ($typeId == 'Folder')) { | ||
| 118 | $type = 'folder'; | 160 | $type = 'folder'; |
| 119 | } | 161 | } |
| 120 | else { | 162 | else { |
| 121 | $type = 'document'; | 163 | $type = 'document'; |
| 122 | } | 164 | } |
| 123 | 165 | ||
| 124 | - $cmisObjectProperties = KT_cmis_atom_service_helper::getCmisProperties($this->parsedXMLContent['@children']['cmis:object'] | ||
| 125 | - [0]['@children']['cmis:properties'] | ||
| 126 | - [0]['@children']); | ||
| 127 | - | ||
| 128 | $ObjectService = new ObjectService(KT_cmis_atom_service_helper::getKt()); | 166 | $ObjectService = new ObjectService(KT_cmis_atom_service_helper::getKt()); |
| 129 | 167 | ||
| 130 | - if ($type == 'folder') | ||
| 131 | - $newObjectId = $ObjectService->createFolder($repositoryId, ucwords($cmisObjectProperties['ObjectTypeId']), $properties, $folderId); | ||
| 132 | - else | ||
| 133 | - $newObjectId = $ObjectService->createDocument($repositoryId, ucwords($cmisObjectProperties['ObjectTypeId']), $properties, $folderId, $content); | ||
| 134 | - | ||
| 135 | - // check if returned Object Id is a valid CMIS Object Id | ||
| 136 | - CMISUtil::decodeObjectId($newObjectId, $typeId); | 168 | + $success = false; |
| 169 | + $error = null; | ||
| 170 | + if ($action == 'create') | ||
| 171 | + { | ||
| 172 | + if ($type == 'folder') | ||
| 173 | + $newObjectId = $ObjectService->createFolder($repositoryId, ucwords($cmisObjectProperties['ObjectTypeId']), $properties, $folderId); | ||
| 174 | + else | ||
| 175 | + $newObjectId = $ObjectService->createDocument($repositoryId, ucwords($cmisObjectProperties['ObjectTypeId']), $properties, $folderId, $content); | ||
| 176 | + | ||
| 177 | + // check if returned Object Id is a valid CMIS Object Id | ||
| 178 | + CMISUtil::decodeObjectId($newObjectId, $typeId); | ||
| 179 | + if ($typeId != 'Unknown') $success = true; | ||
| 180 | + else $error = $newObjectId['message']; | ||
| 181 | + } | ||
| 182 | + else if ($action == 'move') | ||
| 183 | + { | ||
| 184 | + $result = $ObjectService->moveObject($repositoryId, $objectId, '', $folderId); | ||
| 185 | + | ||
| 186 | + if (!PEAR::isError($result)) $success = true; | ||
| 187 | + else $error = $result->getMessage(); | ||
| 188 | + | ||
| 189 | + // same object as before | ||
| 190 | + $newObjectId = $objectId; | ||
| 191 | + $typeId = ucwords($type); | ||
| 192 | + } | ||
| 137 | 193 | ||
| 138 | - if ($typeId != 'Unknown') | 194 | + if ($success) |
| 139 | { | 195 | { |
| 140 | - $this->setStatus(self::STATUS_CREATED); | 196 | + $this->setStatus(($action == 'create') ? self::STATUS_CREATED : self::STATUS_UPDATED); |
| 141 | $feed = KT_cmis_atom_service_helper::getObjectFeed($ObjectService, $repositoryId, $newObjectId, 'POST'); | 197 | $feed = KT_cmis_atom_service_helper::getObjectFeed($ObjectService, $repositoryId, $newObjectId, 'POST'); |
| 142 | } | 198 | } |
| 143 | else { | 199 | else { |
| 144 | - $feed = KT_cmis_atom_service_helper::getErrorFeed($this, self::STATUS_SERVER_ERROR, $newObjectId['message']); | 200 | + $feed = KT_cmis_atom_service_helper::getErrorFeed($this, self::STATUS_SERVER_ERROR, $error); |
| 145 | } | 201 | } |
| 146 | 202 | ||
| 147 | //Expose the responseFeed | 203 | //Expose the responseFeed |
webservice/classes/atompub/cmis/ObjectService.inc.php
| @@ -32,6 +32,34 @@ class ObjectService extends KTObjectService { | @@ -32,6 +32,34 @@ class ObjectService extends KTObjectService { | ||
| 32 | } | 32 | } |
| 33 | 33 | ||
| 34 | /** | 34 | /** |
| 35 | + * Creates a new document within the repository | ||
| 36 | + * | ||
| 37 | + * @param string $repositoryId The repository to which the document must be added | ||
| 38 | + * @param string $typeId Object Type id for the document object being created | ||
| 39 | + * @param array $properties Array of properties which must be applied to the created document object | ||
| 40 | + * @param string $folderId The id of the folder which will be the parent of the created document object | ||
| 41 | + * This parameter is optional IF unfilingCapability is supported | ||
| 42 | + * @param contentStream $contentStream optional content stream data | ||
| 43 | + * @param string $versioningState optional version state value: checkedout/major/minor | ||
| 44 | + * @return string $objectId The id of the created folder object | ||
| 45 | + */ | ||
| 46 | + // TODO throw ConstraintViolationException if: | ||
| 47 | + // value of any of the properties violates the min/max/required/length constraints | ||
| 48 | + // specified in the property definition in the Object-Type. | ||
| 49 | + public function createDocument($repositoryId, $typeId, $properties, $folderId = null, | ||
| 50 | + $contentStream = null, $versioningState = null) | ||
| 51 | + { | ||
| 52 | + $result = parent::createDocument($repositoryId, $typeId, $properties, $folderId, $contentStream, $versioningState); | ||
| 53 | + | ||
| 54 | + if ($result['status_code'] == 0) { | ||
| 55 | + return $result['results']; | ||
| 56 | + } | ||
| 57 | + else { | ||
| 58 | + return $result; | ||
| 59 | + } | ||
| 60 | + } | ||
| 61 | + | ||
| 62 | + /** | ||
| 35 | * Creates a new folder within the repository | 63 | * Creates a new folder within the repository |
| 36 | * | 64 | * |
| 37 | * @param string $repositoryId The repository to which the folder must be added | 65 | * @param string $repositoryId The repository to which the folder must be added |
| @@ -40,7 +68,7 @@ class ObjectService extends KTObjectService { | @@ -40,7 +68,7 @@ class ObjectService extends KTObjectService { | ||
| 40 | * @param string $folderId The id of the folder which will be the parent of the created folder object | 68 | * @param string $folderId The id of the folder which will be the parent of the created folder object |
| 41 | * @return string $objectId The id of the created folder object | 69 | * @return string $objectId The id of the created folder object |
| 42 | */ | 70 | */ |
| 43 | - function createFolder($repositoryId, $typeId, $properties, $folderId) | 71 | + public function createFolder($repositoryId, $typeId, $properties, $folderId) |
| 44 | { | 72 | { |
| 45 | $result = parent::createFolder($repositoryId, $typeId, $properties, $folderId); | 73 | $result = parent::createFolder($repositoryId, $typeId, $properties, $folderId); |
| 46 | 74 | ||
| @@ -51,32 +79,25 @@ class ObjectService extends KTObjectService { | @@ -51,32 +79,25 @@ class ObjectService extends KTObjectService { | ||
| 51 | return $result; | 79 | return $result; |
| 52 | } | 80 | } |
| 53 | } | 81 | } |
| 54 | - | 82 | + |
| 55 | /** | 83 | /** |
| 56 | - * Creates a new document within the repository | ||
| 57 | - * | ||
| 58 | - * @param string $repositoryId The repository to which the document must be added | ||
| 59 | - * @param string $typeId Object Type id for the document object being created | ||
| 60 | - * @param array $properties Array of properties which must be applied to the created document object | ||
| 61 | - * @param string $folderId The id of the folder which will be the parent of the created document object | ||
| 62 | - * This parameter is optional IF unfilingCapability is supported | ||
| 63 | - * @param contentStream $contentStream optional content stream data | ||
| 64 | - * @param string $versioningState optional version state value: checkedout/major/minor | ||
| 65 | - * @return string $objectId The id of the created folder object | 84 | + * Moves a fileable object from one folder to another. |
| 85 | + * | ||
| 86 | + * @param object $repositoryId | ||
| 87 | + * @param object $objectId | ||
| 88 | + * @param object $changeToken [optional] | ||
| 89 | + * @param object $targetFolderId | ||
| 90 | + * @param object $sourceFolderId [optional] | ||
| 66 | */ | 91 | */ |
| 67 | - // TODO throw ConstraintViolationException if: | ||
| 68 | - // value of any of the properties violates the min/max/required/length constraints | ||
| 69 | - // specified in the property definition in the Object-Type. | ||
| 70 | - function createDocument($repositoryId, $typeId, $properties, $folderId = null, | ||
| 71 | - $contentStream = null, $versioningState = null) | 92 | + public function moveObject($repositoryId, $objectId, $changeToken = '', $targetFolderId, $sourceFolderId = null) |
| 72 | { | 93 | { |
| 73 | - $result = parent::createDocument($repositoryId, $typeId, $properties, $folderId, $contentStream, $versioningState); | 94 | + $result = parent::moveObject($repositoryId, $objectId, $changeToken, $targetFolderId, $sourceFolderId); |
| 74 | 95 | ||
| 75 | if ($result['status_code'] == 0) { | 96 | if ($result['status_code'] == 0) { |
| 76 | return $result['results']; | 97 | return $result['results']; |
| 77 | } | 98 | } |
| 78 | else { | 99 | else { |
| 79 | - return $result; | 100 | + return new PEAR_Error($result['message']); |
| 80 | } | 101 | } |
| 81 | } | 102 | } |
| 82 | 103 | ||
| @@ -90,7 +111,7 @@ class ObjectService extends KTObjectService { | @@ -90,7 +111,7 @@ class ObjectService extends KTObjectService { | ||
| 90 | */ | 111 | */ |
| 91 | // NOTE Invoking this service method on an object SHALL not delete the entire version series for a Document Object. | 112 | // NOTE Invoking this service method on an object SHALL not delete the entire version series for a Document Object. |
| 92 | // To delete an entire version series, use the deleteAllVersions() service | 113 | // To delete an entire version series, use the deleteAllVersions() service |
| 93 | - function deleteObject($repositoryId, $objectId, $changeToken = null) | 114 | + public function deleteObject($repositoryId, $objectId, $changeToken = null) |
| 94 | { | 115 | { |
| 95 | $result = parent::deleteObject($repositoryId, $objectId, $changeToken); | 116 | $result = parent::deleteObject($repositoryId, $objectId, $changeToken); |
| 96 | 117 |