public function createDocument($repositoryId, $typeId, $properties, $folderId = null, $contentStream = null, $versioningState = null)
 {
     $objectId = null;
     // fetch type definition of supplied type and check for base type "document", if not true throw exception
     $RepositoryService = new CMISRepositoryService();
     try {
         $typeDefinition = $RepositoryService->getTypeDefinition($repositoryId, $typeId);
     } catch (Exception $e) {
         throw new ConstraintViolationException('Object is not of base type document. ' . $e->getMessage());
     }
     if ($typeDefinition['attributes']['baseType'] != 'document') {
         throw new ConstraintViolationException('Object is not of base type document');
     }
     // if no $folderId submitted and repository does not support "unfiling" throw exception
     if (empty($folderId)) {
         $repositoryInfo = $RepositoryService->getRepositoryInfo($repositoryId);
         $capabilities = $repositoryInfo->getCapabilities();
         if (!$capabilities->hasCapabilityUnfiling()) {
             throw new ConstraintViolationException('Repository does not support the Unfiling capability and no folder id was supplied');
         }
     }
     // Attempt to decode $folderId, use as is if not detected as encoded
     $tmpObjectId = $folderId;
     $tmpObjectId = CMISUtil::decodeObjectId($tmpObjectId, $tmpTypeId);
     if ($tmpTypeId != 'Unknown') {
         $folderId = $tmpObjectId;
     }
     // if parent folder is not allowed to hold this type, throw exception
     $CMISFolder = new CMISFolderObject($folderId, $this->ktapi);
     $allowed = $CMISFolder->getProperty('AllowedChildObjectTypeIds');
     $typeAllowed = false;
     if (is_array($allowed)) {
         foreach ($allowed as $type) {
             if (strtolower($type) == strtolower($typeId)) {
                 $typeAllowed = true;
                 break;
             }
         }
     }
     if (!$typeAllowed) {
         throw new ConstraintViolationException('Parent folder may not hold objects of this type (' . $typeId . ')');
     }
     // if content stream is required and no content stream is supplied, throw a ConstraintViolationException
     if ($typeDefinition['attributes']['contentStreamAllowed'] == 'required' && is_null($contentStream)) {
         throw new ConstraintViolationException('This repository requires a content stream for document creation.  ' . 'Refusing to create an empty document');
     } else {
         if ($typeDefinition['attributes']['contentStreamAllowed'] == 'notAllowed' && !empty($contentStream)) {
             throw new StreamNotSupportedException('Content Streams are not supported');
         }
     }
     // if versionable attribute is set to false and versioningState is supplied, throw a ConstraintViolationException
     if (!$typeDefinition['attributes']['versionable'] && !empty($versioningState)) {
         throw new ConstraintViolationException('This repository does not support versioning');
     }
     // TODO deal with $versioningState when supplied
     // set title and name identical if only one submitted
     if ($properties['title'] == '') {
         $properties['title'] = $properties['name'];
     } else {
         if ($properties['name'] == '') {
             $properties['name'] = $properties['title'];
         }
     }
     // if name is blank throw exception (check type) - using invalidArgument Exception for now
     if (trim($properties['name']) == '') {
         throw new InvalidArgumentException('Refusing to create an un-named document');
     }
     // TODO also set to Default if a non-supported type is submitted
     if ($properties['type'] == '') {
         $properties['type'] = 'Default';
     }
     // create the content stream from the supplied data
     // NOTE since the repository is set to require a content stream and we don't currently have another way to get the document data
     //      this check isn't strictly necessary;  however it is needed for a repository which does not support content streams
     if (!is_null($contentStream)) {
         // TODO consider checking whether content is encoded (currently we expect encoded)
         // TODO choose between this and the alternative decode function (see CMISUtil class)
         //      this will require some basic benchmarking
         $contentStream = CMISUtil::decodeChunkedContentStream($contentStream);
         // NOTE There is a function in CMISUtil to do this, written for the unit tests but since KTUploadManager exists
         //      and has more functionality which could come in useful at some point I decided to go with that instead
         //      (did not know this existed when I wrote the CMISUtil function)
         $uploadManager = new KTUploadManager();
         // assumes already decoded from base64, should use store_base64_file if not
         $tempfilename = $uploadManager->store_file($contentStream, 'cmis_');
         // metadata
         $metadata = array();
         $metaFields = array();
         $sysdata = array();
         if (!empty($properties['summary'])) {
             $metadata[] = array('fieldset' => 'Tag Cloud', 'fields' => array(array('name' => 'Tag', 'value' => $properties['summary'])));
         }
         $user = $this->ktapi->get_user();
         if (!PEAR::isError($user)) {
             $metaFields['General Information'][] = array('name' => 'Document Author', 'value' => $user->getName());
         }
         if (!empty($properties['category'])) {
             $category = $properties['category'];
         } else {
             $category = 'Miscellaneous';
         }
         $metaFields['General Information'][] = array('name' => 'Category', 'value' => $category);
         /**
          * Try to determine mime type which maps to one of the following KnowledgetTree document types:
          *
          * Audio
          * Image
          * Text
          * Video
          *
          * example mime types:
          *
          * text/plain
          * image/gif
          * application/x-dosexec
          * application/pdf
          * application/msword
          * audio/mpeg
          * application/octet-stream
          * application/zip
          */
         // TODO check extension for types which are not obvious?  e.g. wmv video returns application/octet-stream
         $mediatype = null;
         include_once KT_LIB_DIR . '/mime.inc.php';
         $KTMime = new KTMime();
         $mimetype = $KTMime->getMimeTypeFromFile($tempfilename);
         preg_match('/^([^\\/]*)\\/([^\\/]*)/', $mimetype, $matches);
         if ($matches[1] == 'text' || $matches[1] == 'image' || $matches[1] == 'audio') {
             $mediatype = ucwords($matches[1]);
         } else {
             if ($matches[2] == 'pdf' || $matches[2] == 'msword') {
                 $mediatype = 'Text';
             }
         }
         if (!is_null($mediatype)) {
             $metaFields['General Information'][] = array('name' => 'Media Type', 'value' => $mediatype);
         }
         if (count($metaFields['General Information']) > 0) {
             foreach ($metaFields['General Information'] as $field) {
                 $fields[] = $field;
             }
             $metadata[] = array('fieldset' => 'General Information', 'fields' => $fields);
         }
         $response = $this->ktapi->add_document_with_metadata((int) $folderId, $properties['title'], $properties['name'], $properties['type'], $tempfilename, $metadata, $sysdata);
         if ($response['status_code'] != 0) {
             throw new StorageException('The repository was unable to create the document.  ' . $response['message']);
         } else {
             $objectId = CMISUtil::encodeObjectId('Document', $response['results']['document_id']);
         }
         // remove temporary file
         @unlink($tempfilename);
     } else {
         // TODO creation of document without content.  leaving this for now as we require content streams and any code
         //      here will therefore never be executed; if we implement some form of template based document creation
         //      then we may have something else to do here;
         //      for now we just throw a general RuntimeException, since we should not
         //      actually reach this code unless something is wrong; this may be removed or replaced later
         throw new RuntimeException('Cannot create document without a content stream');
     }
     return $objectId;
 }