Commit 45796a52b3086a41339019fd3c2eb6dac29bf14f
Merge branch 'edge' of git@github.com:ktgit/knowledgetree into edge
Showing
5 changed files
with
167 additions
and
25 deletions
ktwebservice/KTUploadManager.inc.php
| @@ -134,6 +134,36 @@ class KTUploadManager | @@ -134,6 +134,36 @@ class KTUploadManager | ||
| 134 | 134 | ||
| 135 | return $tempfilename; | 135 | return $tempfilename; |
| 136 | } | 136 | } |
| 137 | + | ||
| 138 | + /** | ||
| 139 | + * | ||
| 140 | + * @param string $content file content NOT base64 encoded (may be string, may be binary) | ||
| 141 | + * @param string $prefix [optional] | ||
| 142 | + * @return $tempfilename the name of the temporary file created | ||
| 143 | + */ | ||
| 144 | + function store_file($content, $prefix= 'sa_') | ||
| 145 | + { | ||
| 146 | + $tempfilename = $this->get_temp_filename($prefix); | ||
| 147 | + if (!is_writable($tempfilename)) | ||
| 148 | + { | ||
| 149 | + return new PEAR_Error("Cannot write to file: $tempfilename"); | ||
| 150 | + } | ||
| 151 | + | ||
| 152 | + if (!$this->is_valid_temporary_file($tempfilename)) | ||
| 153 | + { | ||
| 154 | + return new PEAR_Error("Invalid temporary file: $tempfilename. There is a problem with the temporary storage path: $this->temp_dir."); | ||
| 155 | + } | ||
| 156 | + | ||
| 157 | + $fp=fopen($tempfilename, 'wb'); | ||
| 158 | + if ($fp === false) | ||
| 159 | + { | ||
| 160 | + return new PEAR_Error("Cannot write content to temporary file: $tempfilename."); | ||
| 161 | + } | ||
| 162 | + fwrite($fp, $content); | ||
| 163 | + fclose($fp); | ||
| 164 | + | ||
| 165 | + return $tempfilename; | ||
| 166 | + } | ||
| 137 | 167 | ||
| 138 | /** | 168 | /** |
| 139 | * This tells the manager to manage a file that has been uploaded. | 169 | * This tells the manager to manage a file that has been uploaded. |
lib/api/ktcmis/services/CMISObjectService.inc.php
| @@ -80,16 +80,16 @@ class CMISObjectService { | @@ -80,16 +80,16 @@ class CMISObjectService { | ||
| 80 | * @param array $properties Array of properties which must be applied to the created document object | 80 | * @param array $properties Array of properties which must be applied to the created document object |
| 81 | * @param string $folderId The id of the folder which will be the parent of the created document object | 81 | * @param string $folderId The id of the folder which will be the parent of the created document object |
| 82 | * This parameter is optional IF unfilingCapability is supported | 82 | * This parameter is optional IF unfilingCapability is supported |
| 83 | - * @param contentStream $contentStream optional content stream data | 83 | + * @param string $contentStream optional content stream data - expected as a base64 encoded string |
| 84 | * @param string $versioningState optional version state value: checkedout/major/minor | 84 | * @param string $versioningState optional version state value: checkedout/major/minor |
| 85 | * @return string $objectId The id of the created folder object | 85 | * @return string $objectId The id of the created folder object |
| 86 | */ | 86 | */ |
| 87 | // TODO throw ConstraintViolationException if: | 87 | // TODO throw ConstraintViolationException if: |
| 88 | // value of any of the properties violates the min/max/required/length constraints | 88 | // value of any of the properties violates the min/max/required/length constraints |
| 89 | - // specified in the property definition in the Object-Type. | 89 | + // specified in the property definition in the Object-Type. |
| 90 | function createDocument($repositoryId, $typeId, $properties, $folderId = null, | 90 | function createDocument($repositoryId, $typeId, $properties, $folderId = null, |
| 91 | $contentStream = null, $versioningState = null) | 91 | $contentStream = null, $versioningState = null) |
| 92 | - { | 92 | + { |
| 93 | $objectId = null; | 93 | $objectId = null; |
| 94 | 94 | ||
| 95 | // fetch type definition of supplied type and check for base type "document", if not true throw exception | 95 | // fetch type definition of supplied type and check for base type "document", if not true throw exception |
| @@ -196,11 +196,17 @@ class CMISObjectService { | @@ -196,11 +196,17 @@ class CMISObjectService { | ||
| 196 | // this check isn't strictly necessary; however it is needed for a repository which does not support content streams | 196 | // this check isn't strictly necessary; however it is needed for a repository which does not support content streams |
| 197 | if (!empty($contentStream)) | 197 | if (!empty($contentStream)) |
| 198 | { | 198 | { |
| 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) | ||
| 201 | + // The current one appears to be miles better (1/0/3 vs 14/4/57 on respective test files) | ||
| 202 | + $contentStream = CMISUtil::decodeChunkedContentStream($contentStream); | ||
| 203 | + | ||
| 199 | // 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 |
| 200 | // and has more functionality which could come in useful at some point I decided to go with that instead | 205 | // and has more functionality which could come in useful at some point I decided to go with that instead |
| 201 | // (did not know this existed when I wrote the CMISUtil function) | 206 | // (did not know this existed when I wrote the CMISUtil function) |
| 202 | $uploadManager = new KTUploadManager(); | 207 | $uploadManager = new KTUploadManager(); |
| 203 | - $tempfilename = $uploadManager->store_base64_file($contentStream, 'cmis_'); | 208 | + // assumes already decoded from base64, should use store_base64_file if not |
| 209 | + $tempfilename = $uploadManager->store_file($contentStream, 'cmis_'); | ||
| 204 | 210 | ||
| 205 | // metadata | 211 | // metadata |
| 206 | $metadata = array(); | 212 | $metadata = array(); |
lib/api/ktcmis/util/CMISUtil.inc.php
| @@ -298,10 +298,6 @@ class CMISUtil { | @@ -298,10 +298,6 @@ class CMISUtil { | ||
| 298 | $object['properties']['ObjectId'] = array('type' => $properties->getFieldType('ObjectId'), | 298 | $object['properties']['ObjectId'] = array('type' => $properties->getFieldType('ObjectId'), |
| 299 | 'value' => $properties->getValue('ObjectId')); | 299 | 'value' => $properties->getValue('ObjectId')); |
| 300 | 300 | ||
| 301 | - | ||
| 302 | - | ||
| 303 | - | ||
| 304 | - | ||
| 305 | if (strtolower($properties->getValue('ObjectTypeId')) == 'document') | 301 | if (strtolower($properties->getValue('ObjectTypeId')) == 'document') |
| 306 | { | 302 | { |
| 307 | 303 | ||
| @@ -400,6 +396,117 @@ class CMISUtil { | @@ -400,6 +396,117 @@ class CMISUtil { | ||
| 400 | 396 | ||
| 401 | return $temp; | 397 | return $temp; |
| 402 | } | 398 | } |
| 399 | + | ||
| 400 | + // TODO run evaluations on each of the following two functions and determine which | ||
| 401 | + // is generally more efficienct | ||
| 402 | + | ||
| 403 | + /** | ||
| 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.) | ||
| 407 | + * | ||
| 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 | ||
| 412 | + */ | ||
| 413 | + static public function decodeContentStream($contentStream) | ||
| 414 | + { | ||
| 415 | + $decoded = ''; | ||
| 416 | + | ||
| 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)); | ||
| 422 | + } | ||
| 423 | + | ||
| 424 | + return $decoded; | ||
| 425 | + } | ||
| 426 | + | ||
| 427 | + /** | ||
| 428 | + * Checks the contentStream and ensures that it is a correct base64 string; | ||
| 429 | + * This is purely for clients such as CMISSpaces breaking the content into | ||
| 430 | + * chunks before base64 encoding. | ||
| 431 | + * | ||
| 432 | + * If the stream is chunked, it is decoded in chunks and sent back as a single stream. | ||
| 433 | + * If it is not chunked it is decoded as is and sent back as a single stream. | ||
| 434 | + * | ||
| 435 | + * NOTE this function and the above need to be checked for efficiency. | ||
| 436 | + * The current one appears to be miles better (1/0/3 vs 14/4/57 on respective test files) | ||
| 437 | + * | ||
| 438 | + * @param object $contentStream | ||
| 439 | + * @return string decoded | ||
| 440 | + */ | ||
| 441 | + static public function decodeChunkedContentStream($contentStream) | ||
| 442 | + { | ||
| 443 | + // check the content stream for any lines of unusual length (except the last line, which can be any length) | ||
| 444 | + $count = -1; | ||
| 445 | + $length = 0; | ||
| 446 | + $b64String = ''; | ||
| 447 | + $outputStream = ''; | ||
| 448 | + $decode = array(); | ||
| 449 | + $chunks = 1; | ||
| 450 | + $decoded = ''; | ||
| 451 | + $chunked = ''; | ||
| 452 | + | ||
| 453 | + $splitStream = explode("\n", $contentStream); | ||
| 454 | + foreach ($splitStream as $line) | ||
| 455 | + { | ||
| 456 | + $curlen = strlen($line); | ||
| 457 | + | ||
| 458 | + if ($length == 0) { | ||
| 459 | + $length = $curlen; | ||
| 460 | + } | ||
| 461 | + | ||
| 462 | + // if we find one we know that we must split the line here and end the previous base64 string | ||
| 463 | + if ($curlen > $length) | ||
| 464 | + { | ||
| 465 | + // check for a new chunk | ||
| 466 | + // either we have an equals sign (or two) | ||
| 467 | + if (preg_match('/([^=]*={0,2})(.*)/', $line, $matches)) | ||
| 468 | + { | ||
| 469 | + $lastChunk = $matches[1]; | ||
| 470 | + $nextChunk = $matches[2]; | ||
| 471 | + } | ||
| 472 | + // or we need to try by line length | ||
| 473 | + else { | ||
| 474 | + $lastChunk = substr($line, 0, $curlen - $length); | ||
| 475 | + $nextChunk = substr($line, $curlen - $length); | ||
| 476 | + } | ||
| 477 | + | ||
| 478 | + $decode[++$count] = $b64String . $lastChunk; | ||
| 479 | + | ||
| 480 | + $b64String = $nextChunk . "\n"; | ||
| 481 | + $length = strlen($nextChunk); | ||
| 482 | + | ||
| 483 | + ++$chunks; | ||
| 484 | + } | ||
| 485 | + else { | ||
| 486 | + $b64String .= $line . "\n"; | ||
| 487 | + } | ||
| 488 | + } | ||
| 489 | + | ||
| 490 | + // anything left over | ||
| 491 | + if (!empty($b64String)) { | ||
| 492 | + $decode[] = $b64String; | ||
| 493 | + } | ||
| 494 | + | ||
| 495 | + if ($chunks > 1) | ||
| 496 | + { | ||
| 497 | + foreach($decode as $code) { | ||
| 498 | + // decode, append to output to be re-encoded | ||
| 499 | + $chunked .= base64_decode($code); | ||
| 500 | + } | ||
| 501 | + | ||
| 502 | + $decoded = $chunked; | ||
| 503 | + } | ||
| 504 | + else { | ||
| 505 | + $decoded = base64_decode($decode[0]); | ||
| 506 | + } | ||
| 507 | + | ||
| 508 | + return $decoded; | ||
| 509 | + } | ||
| 403 | 510 | ||
| 404 | } | 511 | } |
| 405 | 512 |
webservice/atompub/cmis/KT_cmis_atom_server.services.inc.php
| @@ -103,10 +103,17 @@ class KT_cmis_atom_service_folder extends KT_atom_service { | @@ -103,10 +103,17 @@ class KT_cmis_atom_service_folder extends KT_atom_service { | ||
| 103 | // determine whether this is a folder or a document create | 103 | // determine whether this is a folder or a document create |
| 104 | // document create will have a content tag <atom:content> or <content> containing base64 encoding of the document | 104 | // document create will have a content tag <atom:content> or <content> containing base64 encoding of the document |
| 105 | $content = KT_cmis_atom_service_helper::getAtomValues($this->parsedXMLContent['@children'], 'content'); | 105 | $content = KT_cmis_atom_service_helper::getAtomValues($this->parsedXMLContent['@children'], 'content'); |
| 106 | - if (is_null($content)) | 106 | + |
| 107 | + // check content for weird chars | ||
| 108 | + $matches = array(); | ||
| 109 | + preg_match('/[^\w\d\/\+\n]*/', $content, $matches); | ||
| 110 | + | ||
| 111 | + if (is_null($content)) { | ||
| 107 | $type = 'folder'; | 112 | $type = 'folder'; |
| 108 | - else | 113 | + } |
| 114 | + else { | ||
| 109 | $type = 'document'; | 115 | $type = 'document'; |
| 116 | + } | ||
| 110 | 117 | ||
| 111 | $cmisObjectProperties = KT_cmis_atom_service_helper::getCmisProperties($this->parsedXMLContent['@children']['cmis:object'] | 118 | $cmisObjectProperties = KT_cmis_atom_service_helper::getCmisProperties($this->parsedXMLContent['@children']['cmis:object'] |
| 112 | [0]['@children']['cmis:properties'] | 119 | [0]['@children']['cmis:properties'] |
| @@ -124,20 +131,17 @@ class KT_cmis_atom_service_folder extends KT_atom_service { | @@ -124,20 +131,17 @@ class KT_cmis_atom_service_folder extends KT_atom_service { | ||
| 124 | 131 | ||
| 125 | if ($typeId != 'Unknown') | 132 | if ($typeId != 'Unknown') |
| 126 | { | 133 | { |
| 134 | + /*$f = fopen('c:\kt-stuff\here.txt', 'w'); | ||
| 135 | + fwrite($f, 'fgfgfgfg'); | ||
| 136 | + fclose($f);*/ | ||
| 127 | $this->setStatus(self::STATUS_CREATED); | 137 | $this->setStatus(self::STATUS_CREATED); |
| 128 | -// if ($type == 'folder') | ||
| 129 | -// { | ||
| 130 | - $feed = KT_cmis_atom_service_helper::getObjectFeed($ObjectService, $repositoryId, $newObjectId, 'POST'); | ||
| 131 | -// } | ||
| 132 | -// else | ||
| 133 | -// { | ||
| 134 | -// $NavigationService = new NavigationService(KT_cmis_atom_service_helper::getKt()); | ||
| 135 | -// $cmisEntry = $ObjectService->getProperties($repositoryId, $folderId, false, false); | ||
| 136 | -// $feed = $this->getFolderChildrenFeed($NavigationService, $repositoryId, $folderId, $cmisEntry['properties']['Name']['value'], 'POST'); | ||
| 137 | -// } | 138 | + $feed = KT_cmis_atom_service_helper::getObjectFeed($ObjectService, $repositoryId, $newObjectId, 'POST'); |
| 138 | } | 139 | } |
| 139 | else | 140 | else |
| 140 | { | 141 | { |
| 142 | + /*$f = fopen('c:\kt-stuff\failed.txt', 'w'); | ||
| 143 | + fwrite($f, 'fgfgfgfg'); | ||
| 144 | + fclose($f);*/ | ||
| 141 | $feed = KT_cmis_atom_service_helper::getErrorFeed($this, self::STATUS_SERVER_ERROR, $newObjectId['message']); | 145 | $feed = KT_cmis_atom_service_helper::getErrorFeed($this, self::STATUS_SERVER_ERROR, $newObjectId['message']); |
| 142 | } | 146 | } |
| 143 | 147 |
webservice/atompub/cmis/index.php
| @@ -72,11 +72,6 @@ if(!KT_atom_HTTPauth::isLoggedIn()) { | @@ -72,11 +72,6 @@ if(!KT_atom_HTTPauth::isLoggedIn()) { | ||
| 72 | KT_atom_HTTPauth::login('KnowledgeTree DMS', 'You must authenticate to enter this realm'); | 72 | KT_atom_HTTPauth::login('KnowledgeTree DMS', 'You must authenticate to enter this realm'); |
| 73 | } | 73 | } |
| 74 | 74 | ||
| 75 | -//$username = $_SERVER['PHP_AUTH_USER']; | ||
| 76 | -//$password = $_SERVER['PHP_AUTH_PW']; | ||
| 77 | -//// fetch user name and password (check auth include for proper method) | ||
| 78 | -//KT_cmis_atom_service_helper::login($username, $password); | ||
| 79 | - | ||
| 80 | //Start the AtomPubProtocol Routing Engine | 75 | //Start the AtomPubProtocol Routing Engine |
| 81 | $APP = new KT_cmis_atom_server(); | 76 | $APP = new KT_cmis_atom_server(); |
| 82 | 77 |