Commit 2b68ca023d074734c213a81885a23ac5378efc9d

Authored by Paul Barrett
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
lib/api/ktcmis/ktcmis.inc.php
... ... @@ -538,7 +538,7 @@ class KTObjectService extends KTCMISBase {
538 538 * @param string $versioningState optional version state value: checkedout/major/minor
539 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 542 $contentStream = null, $versioningState = null)
543 543 {
544 544 $objectId = null;
... ... @@ -570,7 +570,7 @@ class KTObjectService extends KTCMISBase {
570 570 * @param string $folderId The id of the folder which will be the parent of the created folder object
571 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 575 $objectId = null;
576 576  
... ... @@ -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 623 * Deletes an object from the repository
596 624 *
597 625 * @param string $repositoryId
... ... @@ -601,10 +629,10 @@ class KTObjectService extends KTCMISBase {
601 629 */
602 630 // NOTE Invoking this service method on an object SHALL not delete the entire version series for a Document Object.
603 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 634 try {
607   - $result = $this->ObjectService->deleteObject($repositoryId, $objectId, $changeToken);
  635 + $this->ObjectService->deleteObject($repositoryId, $objectId, $changeToken);
608 636 }
609 637 catch (Exception $e)
610 638 {
... ...
lib/api/ktcmis/services/CMISObjectService.inc.php
... ... @@ -198,7 +198,7 @@ class CMISObjectService {
198 198 {
199 199 // TODO consider checking whether content is encoded (currently we expect encoded)
200 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 202 $contentStream = CMISUtil::decodeChunkedContentStream($contentStream);
203 203  
204 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 390 $response = $this->ktapi->create_folder((int)$folderId, $properties['name'], $sig_username = '', $sig_password = '', $reason = '');
391 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 395 else
396 396 {
... ... @@ -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 472 * Deletes an object from the repository
405 473 *
406 474 * @param string $repositoryId
... ... @@ -415,6 +483,7 @@ class CMISObjectService {
415 483 // determine object type and internal id
416 484 $objectId = CMISUtil::decodeObjectId($objectId, $typeId);
417 485  
  486 + // TODO this should probably be a function, it is now used in two places...
418 487 // throw updateConflictException if the operation is attempting to update an object that is no longer current (as determined by the repository).
419 488 $exists = true;
420 489 if ($typeId == 'Folder') {
... ... @@ -428,6 +497,7 @@ class CMISObjectService {
428 497 if (PEAR::isError($object)) {
429 498 $exists = false;
430 499 }
  500 + // TODO check deleted status?
431 501 }
432 502 else {
433 503 $exists = false;
... ... @@ -482,8 +552,6 @@ class CMISObjectService {
482 552 if ($result['status_code'] == 1) {
483 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 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 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 427 return $decoded;
425 428 }
426 429  
... ... @@ -438,7 +441,7 @@ class CMISUtil {
438 441 * @param object $contentStream
439 442 * @return string decoded
440 443 */
441   - static public function decodeChunkedContentStream($contentStream)
  444 + static public function decodeChunkedContentStreamLong($contentStream)
442 445 {
443 446 // check the content stream for any lines of unusual length (except the last line, which can be any length)
444 447 $count = -1;
... ... @@ -464,7 +467,7 @@ class CMISUtil {
464 467 {
465 468 // check for a new chunk
466 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 472 $lastChunk = $matches[1];
470 473 $nextChunk = $matches[2];
... ... @@ -507,6 +510,39 @@ class CMISUtil {
507 510  
508 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 . &#39;ObjectService.inc.php&#39;;
36 36 include_once CMIS_ATOM_LIB_FOLDER . 'VersioningService.inc.php';
37 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 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 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 95  
93 96 /**
94 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 100 public function POST_action()
98 101 {
... ... @@ -100,48 +103,101 @@ class KT_cmis_atom_service_folder extends KT_atom_service {
100 103 $repositories = $RepositoryService->getRepositories();
101 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 111 $folderId = $this->params[0];
104 112 $title = KT_cmis_atom_service_helper::getAtomValues($this->parsedXMLContent['@children'], 'title');
105 113 $summary = KT_cmis_atom_service_helper::getAtomValues($this->parsedXMLContent['@children'], 'summary');
106 114  
107 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 160 $type = 'folder';
119 161 }
120 162 else {
121 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 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 197 $feed = KT_cmis_atom_service_helper::getObjectFeed($ObjectService, $repositoryId, $newObjectId, 'POST');
142 198 }
143 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 203 //Expose the responseFeed
... ...
webservice/classes/atompub/cmis/ObjectService.inc.php
... ... @@ -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 63 * Creates a new folder within the repository
36 64 *
37 65 * @param string $repositoryId The repository to which the folder must be added
... ... @@ -40,7 +68,7 @@ class ObjectService extends KTObjectService {
40 68 * @param string $folderId The id of the folder which will be the parent of the created folder object
41 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 73 $result = parent::createFolder($repositoryId, $typeId, $properties, $folderId);
46 74  
... ... @@ -51,32 +79,25 @@ class ObjectService extends KTObjectService {
51 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 96 if ($result['status_code'] == 0) {
76 97 return $result['results'];
77 98 }
78 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 111 */
91 112 // NOTE Invoking this service method on an object SHALL not delete the entire version series for a Document Object.
92 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 116 $result = parent::deleteObject($repositoryId, $objectId, $changeToken);
96 117  
... ...