Commit 8d56157abf064bb4477a3b193132ffa4d502bcd8
1 parent
d82d4ae8
Update CMIS Object Attributes
Story ID:2295472. Update KT CMIS implementation to 1.0 compliance Committed by: Paul Barrett
Showing
6 changed files
with
113 additions
and
83 deletions
lib/api/ktcmis/classes/CMISObject.inc.php
| ... | ... | @@ -44,22 +44,33 @@ |
| 44 | 44 | * @version Version 0.1 |
| 45 | 45 | */ |
| 46 | 46 | |
| 47 | -abstract class CMISObject { | |
| 47 | +// NOTE designation "opaque" means the value may not be changed | |
| 48 | + | |
| 49 | +// TODO consider attributes as a class similar to property definitions, in order to more easily extract without having | |
| 50 | +// to have attribute specific code | |
| 48 | 51 | |
| 49 | - protected $typeId; | |
| 50 | - protected $queryName; | |
| 51 | - protected $displayName; | |
| 52 | - protected $baseType; | |
| 53 | - protected $baseTypeQueryName; | |
| 54 | - protected $parentId; | |
| 55 | - protected $description; | |
| 56 | - protected $creatable; | |
| 57 | - protected $fileable; | |
| 58 | - protected $queryable; | |
| 59 | - protected $includedInSupertypeQuery; | |
| 60 | - protected $controllable; // NOTE deprecated? part of policy objects specification, policy objects are indicated as TODO remove | |
| 61 | - protected $contentStreamAllowed = 'notAllowed'; | |
| 52 | +abstract class CMISObject { | |
| 62 | 53 | |
| 54 | + protected $id; // ID (opaque); identifies the object-type in the repository | |
| 55 | + protected $localName; // String (opaque, optional); local name for object-type - need not be set | |
| 56 | + protected $localNamespace; // String (opaque, optional); optional local namespace for object-type - need not be set | |
| 57 | + // NOTE queryName should not contain characters that negatively interact with BNF grammar | |
| 58 | + protected $queryName; // String (opaque); used for query and filter operations on object-types | |
| 59 | + protected $displayName; // String (optional); used for presentation by application | |
| 60 | + protected $baseId; // Enum; indicates base type | |
| 61 | + protected $parentId; // ID; id of immediate parent type; must be "not set" for a base type (Document, Folder, Relationship, Policy) | |
| 62 | + protected $description; // String (optional); used for presentation by application | |
| 63 | + protected $creatable; // Boolean; indicates whether new objects of this type may be created | |
| 64 | + protected $fileable; // Boolean; indicates whether objects of this type are fileable | |
| 65 | + protected $queryable; // Boolean; indicates whether this object-type can appear inthe FROM clause of a query statement | |
| 66 | + protected $controllablePolicy; // Boolean; indicates whether objects of this type are controllable via policies | |
| 67 | + protected $controllableACL; // Boolean; indicates whether objects of this type are controllable by ACLs | |
| 68 | + protected $fulltextIndexed; // Boolean; indicates whether objects of this type are indexed for full-text search | |
| 69 | + // for querying via the CONTAINS() query predicate | |
| 70 | + protected $includedInSupertypeQuery; // Boolean; indicates whether this type and sub-types appear in a query of this type's ancestor types | |
| 71 | + // For example: if Invoice is a sub-type of cmis:document, if this is TRUE on Invoice then for a query | |
| 72 | + // 391 on cmis:document, instances of Invoice will be returned if they match. | |
| 73 | + | |
| 63 | 74 | protected $properties; // list of property objects which define the additional properties for this object |
| 64 | 75 | |
| 65 | 76 | // TODO all we have here so far is getAttributes & getProperties |
| ... | ... | @@ -82,18 +93,21 @@ abstract class CMISObject { |
| 82 | 93 | |
| 83 | 94 | // TODO look at how chemistry does this and implement something similar |
| 84 | 95 | // for now this is fine as we are just trying to get things up and running :) |
| 85 | - $attributes['typeId'] = $this->typeId; | |
| 96 | + $attributes['id'] = $this->id; | |
| 97 | + $attributes['localName'] = $this->localName; | |
| 98 | + $attributes['localNamespace'] = $this->localNamespace; | |
| 86 | 99 | $attributes['queryName'] = $this->queryName; |
| 87 | 100 | $attributes['displayName'] = $this->displayName; |
| 88 | - $attributes['baseType'] = $this->baseType; | |
| 89 | - $attributes['baseTypeQueryName'] = $this->baseTypeQueryName; | |
| 101 | + $attributes['baseId'] = $this->baseId; | |
| 90 | 102 | $attributes['parentId'] = $this->parentId; |
| 91 | 103 | $attributes['description'] = $this->description; |
| 92 | 104 | $attributes['creatable'] = $this->creatable; |
| 93 | 105 | $attributes['fileable'] = $this->fileable; |
| 94 | 106 | $attributes['queryable'] = $this->queryable; |
| 107 | + $attributes['controllablePolicy'] = $this->controllablePolicy; | |
| 108 | + $attributes['controllableACL'] = $this->controllableACL; | |
| 109 | + $attributes['fulltextIndexed'] = $this->fulltextIndexed; | |
| 95 | 110 | $attributes['includedInSupertypeQuery'] = $this->includedInSupertypeQuery; |
| 96 | - $attributes['controllable'] = $this->includedInSupertypeQuery; | |
| 97 | 111 | |
| 98 | 112 | return $attributes; |
| 99 | 113 | } |
| ... | ... | @@ -136,12 +150,12 @@ abstract class CMISObject { |
| 136 | 150 | return $this->properties->getValue($property); |
| 137 | 151 | } |
| 138 | 152 | |
| 139 | - public function reload($documentId) | |
| 153 | + public function reload($objectId) | |
| 140 | 154 | { |
| 141 | - $this->_get($documentId); | |
| 155 | + $this->_get($objectId); | |
| 142 | 156 | } |
| 143 | 157 | |
| 144 | - private function _get($documentId) | |
| 158 | + private function _get($objectId) | |
| 145 | 159 | { |
| 146 | 160 | // override in child classes |
| 147 | 161 | } | ... | ... |
lib/api/ktcmis/objecttypes/CMISDocumentObject.inc.php
| ... | ... | @@ -52,41 +52,34 @@ require_once(CMIS_DIR . '/util/CMISUtil.inc.php'); |
| 52 | 52 | |
| 53 | 53 | class CMISDocumentObject extends CMISObject { |
| 54 | 54 | |
| 55 | - protected $versionable; | |
| 56 | - private $ktapi; | |
| 57 | - private $uri; | |
| 58 | - | |
| 55 | + protected $versionable; // Bolean; indicates whether objects of this type are versionable | |
| 56 | + protected $contentStreamAllowed; // Enum; notallowed, allowed, required | |
| 57 | + protected $ktapi; | |
| 58 | + | |
| 59 | 59 | // TODO some of this should probably come from configuration files as it is repository specific |
| 60 | 60 | function __construct($documentId = null, &$ktapi = null, $uri = null) |
| 61 | 61 | { |
| 62 | 62 | $this->ktapi = $ktapi; |
| 63 | - // uri to use for document links | |
| 64 | - $this->uri = $uri; | |
| 65 | 63 | |
| 66 | 64 | // attributes |
| 67 | - $this->typeId = 'Document'; // <repository-specific> | |
| 68 | - $this->queryName = 'Document'; | |
| 69 | - $this->displayName = ''; // <repository-specific> | |
| 70 | - $this->baseType = 'document'; | |
| 71 | - $this->baseTypeQueryName = 'Document'; | |
| 72 | - $this->parentId = null; // MUST NOT be set | |
| 73 | - $this->description = ''; // <repository-specific> | |
| 74 | - $this->creatable = ''; // <repository-specific> | |
| 75 | - /* | |
| 76 | - * fileable SHOULD be set as follows: | |
| 77 | - * If the repository does NOT support the รขโฌลun-filingรขโฌ? capability: | |
| 78 | - * TRUE | |
| 79 | - * If the repository does support the รขโฌลun-filingรขโฌ? capability: | |
| 80 | - * <repository-specific>, but SHOULD be TRUE | |
| 81 | - */ | |
| 82 | - $this->fileable = true; // TODO implement check for whether un-filing is supported | |
| 65 | + $this->id = 'cmis:document'; | |
| 66 | + $this->localName = null; // <repository-specific> | |
| 67 | + $this->localNamespace = null; // <repository-specific> | |
| 68 | + $this->queryName = 'cmis:document'; | |
| 69 | + $this->displayName = 'Document'; // <repository-specific> | |
| 70 | + $this->baseId = 'cmis:document'; | |
| 71 | + $this->parentId = null; // MUST NOT be set for base document object-type | |
| 72 | + $this->description = null; // <repository-specific> | |
| 73 | + $this->creatable = true; // <repository-specific> | |
| 74 | + $this->fileable = true; | |
| 83 | 75 | $this->queryable = true; // SHOULD be true |
| 84 | - $this->includedInSupertypeQuery = true; // | |
| 85 | - // TODO determine what these next 3 should be | |
| 86 | - $this->controllable = false; // <repository-specific> | |
| 76 | + $this->controllablePolicy = false; // <repository-specific> | |
| 77 | + $this->includedInSupertypeQuery = true; // <repository-specific> | |
| 87 | 78 | $this->versionable = true; // <repository-specific> |
| 88 | - $this->contentStreamAllowed = 'required'; // <repository-specific> notAllowed/allowed/required | |
| 89 | - | |
| 79 | + $this->contentStreamAllowed = 'required'; // <repository-specific> notallowed/allowed/required | |
| 80 | + $this->controllableACL = false; // <repository-specific> | |
| 81 | + $this->fulltextIndexed = false; // <repository-specific> | |
| 82 | + | |
| 90 | 83 | // properties |
| 91 | 84 | $this->properties = new CMISDocumentPropertyCollection(); |
| 92 | 85 | |
| ... | ... | @@ -98,7 +91,7 @@ class CMISDocumentObject extends CMISObject { |
| 98 | 91 | if (!is_null($documentId)) |
| 99 | 92 | { |
| 100 | 93 | try { |
| 101 | - $this->get($documentId); | |
| 94 | + $this->_get($documentId); | |
| 102 | 95 | } |
| 103 | 96 | catch (exception $e) { |
| 104 | 97 | throw new ObjectNotFoundException($e->getMessage()); |
| ... | ... | @@ -107,8 +100,9 @@ class CMISDocumentObject extends CMISObject { |
| 107 | 100 | |
| 108 | 101 | // TODO throw exception if unable to create? |
| 109 | 102 | } |
| 110 | - | |
| 111 | - private function get($documentId) | |
| 103 | + | |
| 104 | + // TODO abstract shared stuff to base class where possible | |
| 105 | + private function _get($documentId) | |
| 112 | 106 | { |
| 113 | 107 | $object = $this->ktapi->get_document_by_id((int)$documentId); |
| 114 | 108 | |
| ... | ... | @@ -131,9 +125,9 @@ class CMISDocumentObject extends CMISObject { |
| 131 | 125 | // $this->_setPropertyInternal('uri', $uri); |
| 132 | 126 | $this->_setPropertyInternal('uri', ''); |
| 133 | 127 | // TODO what is this? Assuming it is the object type id, and not OUR document type? |
| 134 | - $this->_setPropertyInternal('objectTypeId', 'cmis:' . strtolower($this->getAttribute('typeId'))); | |
| 128 | + $this->_setPropertyInternal('objectTypeId', strtolower($this->getAttribute('id'))); | |
| 135 | 129 | // Needed to distinguish type |
| 136 | - $this->_setPropertyInternal('baseTypeId', 'cmis:' . strtolower($this->getAttribute('typeId'))); | |
| 130 | + $this->_setPropertyInternal('baseTypeId', strtolower($this->getAttribute('id'))); | |
| 137 | 131 | $this->_setPropertyInternal('createdBy', $objectProperties['created_by']); |
| 138 | 132 | $this->_setPropertyInternal('creationDate', $objectProperties['created_date']); |
| 139 | 133 | $this->_setPropertyInternal('lastModifiedBy', $objectProperties['modified_by']); |
| ... | ... | @@ -181,6 +175,22 @@ class CMISDocumentObject extends CMISObject { |
| 181 | 175 | $this->_setPropertyInternal('contentStreamUri', $this->getProperty('objectId') . '/' . $objectProperties['filename']); |
| 182 | 176 | $this->_setPropertyInternal('author', $objectProperties['created_by']); |
| 183 | 177 | } |
| 178 | + | |
| 179 | + /** | |
| 180 | + * Returns a listing of all attributes in an array | |
| 181 | + * | |
| 182 | + * @return array $attributes | |
| 183 | + */ | |
| 184 | + public function getAttributes() | |
| 185 | + { | |
| 186 | + $attributes = parent::getAttributes(); | |
| 187 | + | |
| 188 | + // add document object-type specific attributes | |
| 189 | + $attributes['versionable'] = $this->versionable; | |
| 190 | + $attributes['contentStreamAllowed'] = $this->contentStreamAllowed; | |
| 191 | + | |
| 192 | + return $attributes; | |
| 193 | + } | |
| 184 | 194 | |
| 185 | 195 | } |
| 186 | 196 | ... | ... |
lib/api/ktcmis/objecttypes/CMISFolderObject.inc.php
| ... | ... | @@ -50,34 +50,36 @@ require_once(CMIS_DIR . '/util/CMISUtil.inc.php'); |
| 50 | 50 | |
| 51 | 51 | class CMISFolderObject extends CMISObject { |
| 52 | 52 | |
| 53 | - private $ktapi; | |
| 54 | - private $uri; | |
| 53 | + protected $ktapi; | |
| 55 | 54 | |
| 56 | 55 | public function __construct($folderId = null, &$ktapi = null, $uri = null) |
| 57 | 56 | { |
| 58 | 57 | $this->ktapi = $ktapi; |
| 59 | - $this->uri = $uri; | |
| 60 | 58 | |
| 61 | - $this->typeId = 'Folder'; // <repository-specific> | |
| 62 | - $this->queryName = 'Folder'; | |
| 63 | - $this->displayName = ''; // <repository-specific> | |
| 64 | - $this->baseType = 'folder'; | |
| 65 | - $this->baseTypeQueryName = 'Folder'; | |
| 59 | + $this->id = 'cmis:folder'; // <repository-specific> | |
| 60 | + $this->localName = null; // <repository-specific> | |
| 61 | + $this->localNamespace = null; // <repository-specific> | |
| 62 | + $this->queryName = 'cmis:folder'; | |
| 63 | + $this->displayName = 'Folder'; // <repository-specific> | |
| 64 | + $this->baseId = 'cmis:folder'; | |
| 66 | 65 | $this->parentId = null; // MUST NOT be set |
| 67 | - $this->description = ''; // <repository-specific> | |
| 68 | - $this->creatable = ''; // <repository-specific> | |
| 66 | + $this->description = null; // <repository-specific> | |
| 67 | + $this->creatable = true; // <repository-specific> | |
| 69 | 68 | $this->fileable = true; |
| 70 | 69 | $this->queryable = true; // SHOULD be true |
| 71 | - $this->includedInSupertypeQuery = true; // | |
| 72 | - $this->controllable = ''; // <repository-specific> | |
| 73 | - | |
| 70 | + $this->controllablePolicy = false; // <repository-specific> | |
| 71 | + $this->includedInSupertypeQuery = true; // <repository-specific> | |
| 72 | + $this->contentStreamAllowed = 'required'; // <repository-specific> notallowed/allowed/required | |
| 73 | + $this->controllableACL = false; // <repository-specific> | |
| 74 | + $this->fulltextIndexed = false; // <repository-specific> | |
| 75 | + | |
| 74 | 76 | // properties |
| 75 | 77 | $this->properties = new CMISFolderPropertyCollection(); |
| 76 | 78 | |
| 77 | 79 | if (!is_null($folderId)) |
| 78 | 80 | { |
| 79 | 81 | try { |
| 80 | - $this->get($folderId); | |
| 82 | + $this->_get($folderId); | |
| 81 | 83 | } |
| 82 | 84 | catch (exception $e) { |
| 83 | 85 | throw new ObjectNotFoundException($e->getMessage()); |
| ... | ... | @@ -85,7 +87,8 @@ class CMISFolderObject extends CMISObject { |
| 85 | 87 | } |
| 86 | 88 | } |
| 87 | 89 | |
| 88 | - private function get($folderId) | |
| 90 | + // TODO abstract shared stuff to base class where possible | |
| 91 | + private function _get($folderId) | |
| 89 | 92 | { |
| 90 | 93 | $object = $this->ktapi->get_folder_by_id((int)$folderId); |
| 91 | 94 | |
| ... | ... | @@ -109,9 +112,9 @@ class CMISFolderObject extends CMISObject { |
| 109 | 112 | // $this->_setPropertyInternal('uri', $uri); |
| 110 | 113 | $this->_setPropertyInternal('uri', ''); |
| 111 | 114 | // TODO what is this? Assuming it is the object type id, and not OUR document type? |
| 112 | - $this->_setPropertyInternal('objectTypeId', 'cmis:' . strtolower($this->getAttribute('typeId'))); | |
| 115 | + $this->_setPropertyInternal('objectTypeId', strtolower($this->getAttribute('id'))); | |
| 113 | 116 | // Needed to distinguish type |
| 114 | - $this->_setPropertyInternal('baseTypeId', 'cmis:' . strtolower($this->getAttribute('typeId'))); | |
| 117 | + $this->_setPropertyInternal('baseTypeId', strtolower($this->getAttribute('id'))); | |
| 115 | 118 | $this->_setPropertyInternal('createdBy', $objectProperties['created_by']); |
| 116 | 119 | // TODO cannot currently retrieve via ktapi or regular folder code - add as with created by |
| 117 | 120 | $this->_setPropertyInternal('creationDate', $objectProperties['created_date']); | ... | ... |
lib/api/ktcmis/services/CMISObjectService.inc.php
| ... | ... | @@ -64,7 +64,7 @@ class CMISObjectService { |
| 64 | 64 | throw new ConstraintViolationException('Object base type could not be determined. ' . $e->getMessage()); |
| 65 | 65 | } |
| 66 | 66 | |
| 67 | - if ($typeDefinition['attributes']['baseType'] != 'document') | |
| 67 | + if ($typeDefinition['attributes']['baseId'] != 'cmis:document') | |
| 68 | 68 | { |
| 69 | 69 | throw new ConstraintViolationException('Object is not of base type document'); |
| 70 | 70 | } |
| ... | ... | @@ -244,7 +244,7 @@ class CMISObjectService { |
| 244 | 244 | throw new StorageException('The repository was unable to create the document. ' . $response['message']); |
| 245 | 245 | } |
| 246 | 246 | else { |
| 247 | - $objectId = CMISUtil::encodeObjectId('Document', $response['results']['document_id']); | |
| 247 | + $objectId = CMISUtil::encodeObjectId(DOCUMENT, $response['results']['document_id']); | |
| 248 | 248 | } |
| 249 | 249 | |
| 250 | 250 | // remove temporary file |
| ... | ... | @@ -294,7 +294,7 @@ class CMISObjectService { |
| 294 | 294 | throw new ConstraintViolationException('Object is not of base type folder. ' . $e->getMessage()); |
| 295 | 295 | } |
| 296 | 296 | |
| 297 | - if ($typeDefinition['attributes']['baseType'] != 'folder') { | |
| 297 | + if ($typeDefinition['attributes']['baseId'] != 'cmis:folder') { | |
| 298 | 298 | throw new ConstraintViolationException('Object is not of base type folder'); |
| 299 | 299 | } |
| 300 | 300 | |
| ... | ... | @@ -322,7 +322,7 @@ class CMISObjectService { |
| 322 | 322 | } |
| 323 | 323 | else |
| 324 | 324 | { |
| 325 | - $objectId = CMISUtil::encodeObjectId('Folder', $response['results']['id']); | |
| 325 | + $objectId = CMISUtil::encodeObjectId(FOLDER, $response['results']['id']); | |
| 326 | 326 | } |
| 327 | 327 | |
| 328 | 328 | return $objectId; |
| ... | ... | @@ -633,7 +633,7 @@ class CMISObjectService { |
| 633 | 633 | // TODO consider sending back full properties on each object? |
| 634 | 634 | // Not sure yet what this output may be used for by a client, and the current specification (0.61c) says: |
| 635 | 635 | // "A list of identifiers of objects in the folder tree that were not deleted", so let's leave it returning just ids for now. |
| 636 | - $failedToDelete[] = CMISUtil::encodeObjectId('Folder', $objectId); | |
| 636 | + $failedToDelete[] = CMISUtil::encodeObjectId(FOLDER, $objectId); | |
| 637 | 637 | $folderContents = $object->get_full_listing(); |
| 638 | 638 | foreach($folderContents as $folderObject) |
| 639 | 639 | { |
| ... | ... | @@ -723,7 +723,7 @@ class CMISObjectService { |
| 723 | 723 | } |
| 724 | 724 | // else |
| 725 | 725 | // { |
| 726 | -// $objectId = CMISUtil::encodeObjectId('Document', $response['results']['id']); | |
| 726 | +// $objectId = CMISUtil::encodeObjectId(DOCUMENT, $response['results']['id']); | |
| 727 | 727 | // } |
| 728 | 728 | |
| 729 | 729 | @unlink($csFile); | ... | ... |
lib/api/ktcmis/services/CMISVersioningService.inc.php
| ... | ... | @@ -117,7 +117,7 @@ class CMISVersioningService { |
| 117 | 117 | |
| 118 | 118 | // if successful, set $contentCopied = true; unless contentStream is not set |
| 119 | 119 | if ($pwc->getProperty('contentStreamFilename') != '') $contentCopied = true; |
| 120 | - $documentId = CMISUtil::encodeObjectId('Document', $documentId); | |
| 120 | + $documentId = CMISUtil::encodeObjectId(DOCUMENT, $documentId); | |
| 121 | 121 | |
| 122 | 122 | // mark document object as checked out |
| 123 | 123 | $pwc->setProperty('isVersionSeriesCheckedOut', true); | ... | ... |
lib/api/ktcmis/util/CMISUtil.inc.php
| ... | ... | @@ -307,8 +307,11 @@ class CMISUtil { |
| 307 | 307 | * via var_export (which returns a useable PHP string for creating the object from array content) |
| 308 | 308 | * and regular expressions to extract the array definitions and structure without the class specific code |
| 309 | 309 | * |
| 310 | - * NOTE this function is not reliable for objects which contain ktapi instances, as it appears there is a recursive reference | |
| 311 | - * TODO attempt to deal with recursive references? | |
| 310 | + * NOTE this function is not reliable for objects which contain ktapi instances, as it appears there is a recursive reference; | |
| 311 | + * this will apply to any class which contains recursive references (which is bad practice and should not be done - the | |
| 312 | + * problem is with the object and not this code | |
| 313 | + * | |
| 314 | + * TODO attempt to deal with recursive references? - better to fix the objects in question, if possible | |
| 312 | 315 | * |
| 313 | 316 | * @param object $data |
| 314 | 317 | * @return array $array |
| ... | ... | @@ -318,7 +321,7 @@ class CMISUtil { |
| 318 | 321 | $array = array(); |
| 319 | 322 | |
| 320 | 323 | $stringdata = var_export($data, true); |
| 321 | - // clean up ", )" - NOTE this may not be necessary | |
| 324 | + // clean up ", )" - NOTE this may not be necessary, but is included for safety | |
| 322 | 325 | $stringdata = preg_replace('/, *\r?\n? *\)/', ')', $stringdata); |
| 323 | 326 | |
| 324 | 327 | // NOTE is this while loop even needed? |
| ... | ... | @@ -351,14 +354,14 @@ class CMISUtil { |
| 351 | 354 | /** |
| 352 | 355 | * Checks the contentStream and ensures that it is a correct base64 string; |
| 353 | 356 | * This is purely for clients such as CMISSpaces breaking the content into |
| 354 | - * chunks before base64 encoding. | |
| 357 | + * chunks before base64 encoding each and this coming through as a single string containing multiple base64 chunks. | |
| 355 | 358 | * |
| 356 | 359 | * If the stream is chunked, it is decoded in chunks and sent back as a single stream. |
| 357 | 360 | * If it is not chunked it is decoded as is and sent back as a single stream. |
| 358 | 361 | * |
| 359 | 362 | * NOTE there is an alternative version of this function called decodeChunkedContentStreamLong. |
| 360 | 363 | * that version checks line lengths, which should not be necessary. |
| 361 | - * this version merely splits on one or two "=" which is less complex and possibly faster (test this assumption) | |
| 364 | + * this version merely splits on one or two "=" which is less complex and appears to be faster | |
| 362 | 365 | * (one or two "=" signs is the specified padding used for base64 encoding at the end of an encoded string, when needed) |
| 363 | 366 | * |
| 364 | 367 | * @param object $contentStream |
| ... | ... | @@ -394,7 +397,7 @@ class CMISUtil { |
| 394 | 397 | } |
| 395 | 398 | } |
| 396 | 399 | |
| 397 | - // decode, append to output to be re-encoded | |
| 400 | + // decode, append to output | |
| 398 | 401 | $decoded .= base64_decode($part); |
| 399 | 402 | } |
| 400 | 403 | ... | ... |