/**
  * @param string $projectId
  * @param string $userId
  * @return array - the DTO array
  */
 public static function encode($projectId, $userId)
 {
     $data = array();
     $user = new UserProfileModel($userId);
     $project = new LexProjectModel($projectId);
     $config = JsonEncoder::encode($project->config);
     $config['inputSystems'] = JsonEncoder::encode($project->inputSystems);
     $data['config'] = $config;
     // comment out at the moment until a refactor can be done that is more efficient (language data in the database?)
     /*
     $interfaceLanguageCode = $project->interfaceLanguageCode;
     if ($user->interfaceLanguageCode) {
         $interfaceLanguageCode = $user->interfaceLanguageCode;
     }
     $options = self::getInterfaceLanguages(APPPATH . 'angular-app/languageforge/lexicon/lang');
     asort($options);    // sort by language name
     $selectInterfaceLanguages = array(
         'optionsOrder' => array_keys($options),
         'options' => $options
     );
     $data['interfaceConfig'] = array('userLanguageCode' => $interfaceLanguageCode);
     $data['interfaceConfig']['selectLanguages'] = $selectInterfaceLanguages;
     */
     // a stand in for the code above
     $data['interfaceConfig'] = array('userLanguageCode' => 'en', 'selectLanguages' => array('options' => array('en' => 'English'), 'optionsOrder' => array('en')));
     $optionlistListModel = new LexOptionListListModel($project);
     $optionlistListModel->read();
     $data['optionlists'] = $optionlistListModel->entries;
     if ($project->hasSendReceive()) {
         $data['sendReceive'] = array();
         $data['sendReceive']['status'] = SendReceiveCommands::getProjectStatus($projectId);
     }
     return $data;
 }
 public static function run($mode = 'test')
 {
     $testMode = $mode != 'run';
     print "Fix Meaning->Definition and Example->Sentence labels.\n";
     $projectlist = new ProjectListModel();
     $projectlist->read();
     $fixCount = 0;
     $definitionLabelsUpdated = 0;
     $sentenceLabelsUpdated = 0;
     foreach ($projectlist->entries as $projectParams) {
         // foreach existing project
         $projectId = $projectParams['id'];
         $project = new ProjectModel($projectId);
         if ($project->appName == 'lexicon') {
             $project = new LexProjectModel($projectId);
             $projectChanged = false;
             $entryFieldsArray = $project->config->entry->fields->getArrayCopy();
             if (array_key_exists("senses", $entryFieldsArray)) {
                 $senseFieldsArray = $entryFieldsArray["senses"]->fields->getArrayCopy();
                 if ($senseFieldsArray["definition"]->label != "Definition") {
                     $senseFieldsArray["definition"]->label = "Definition";
                     //print "  Fixed \"Definition\" label\n";
                     $definitionLabelsUpdated++;
                     $projectChanged = true;
                 }
                 if (array_key_exists("examples", $senseFieldsArray)) {
                     $exampleFieldsArray = $senseFieldsArray["examples"]->fields->getArrayCopy();
                     if ($exampleFieldsArray["sentence"]->label != "Sentence") {
                         $exampleFieldsArray["sentence"]->label = "Sentence";
                         //print "  Fixed \"Sentence\" label\n";
                         $sentenceLabelsUpdated++;
                         $projectChanged = true;
                     }
                 }
             }
             $senseFieldsArray["examples"]->fields->exchangeArray($exampleFieldsArray);
             $entryFieldsArray["senses"]->fields->exchangeArray($senseFieldsArray);
             $project->config->entry->fields->exchangeArray($entryFieldsArray);
             if ($projectChanged) {
                 $fixCount++;
                 if (!$testMode) {
                     print "  Saving changes to project {$project->projectName}.\n";
                     $project->write();
                 }
             }
             unset($exampleFieldsArray);
             unset($senseFieldsArray);
             unset($entryFieldsArray);
         }
     }
     if ($fixCount > 0) {
         print "{$fixCount} projects were fixed\n";
         print "{$definitionLabelsUpdated} \"meaning\" labels changed to \"definition\"\n";
         print "{$sentenceLabelsUpdated} \"example\" labels changed to \"sentence\"\n";
     } else {
         print "No projects needed fixing\n";
     }
 }
 /**
  * @param LexProjectModel $project
  * @param int $newerThanTimestamp
  */
 public function __construct($project, $newerThanTimestamp = null)
 {
     if (!is_null($newerThanTimestamp)) {
         $startDate = new UTCDateTime(1000 * $newerThanTimestamp);
         parent::__construct(self::mapper($project->databaseName()), array('dateModified' => array('$gte' => $startDate)), array());
     } else {
         parent::__construct(self::mapper($project->databaseName()), array('name' => array('$regex' => '')));
     }
 }
 /**
  * Update Role Views and User Views for each custom field
  * Designed to be externally called (e.g. from LfMerge)
  *
  * @param string $projectCode
  * @param array<string> $customFieldSpecs
  * @return bool|string returns the project id on success, false otherwise
  */
 public static function updateCustomFieldViews($projectCode, $customFieldSpecs)
 {
     $project = new LexProjectModel();
     if (!$project->readByProperty('projectCode', $projectCode)) {
         return false;
     }
     self::removeDeletedCustomFieldViews($customFieldSpecs, $project->config);
     foreach ($customFieldSpecs as $customFieldSpec) {
         self::createNewCustomFieldViews($customFieldSpec['fieldName'], $customFieldSpec['fieldType'], $project->config);
     }
     return $project->write();
 }
 /**
  * Reads a MultiParagraph from the XmlNode $sxeNode given by the element 'form'
  *
  * @param \SimpleXMLElement $sxeNode
  * @return LexMultiParagraph
  */
 public function readMultiParagraph($sxeNode)
 {
     // paragraph separator character U+2029
     $paraSeparator = mb_convert_encoding('&#x2029;', 'UTF-8', 'HTML-ENTITIES');
     $multiParagraph = new LexMultiParagraph();
     $this->addAndPushSubnodeError(LiftImportNodeError::MULTIPARAGRAPH, $sxeNode->getName());
     /** @var \SimpleXMLElement $element */
     foreach ($sxeNode as $element) {
         switch ($element->getName()) {
             case 'form':
                 $inputSystemTag = (string) $element['lang'];
                 $multiParagraph->inputSystem = $inputSystemTag;
                 $value = self::sanitizeSpans(dom_import_simplexml($element->{'text'}[0]), $inputSystemTag, $this->currentNodeError());
                 foreach (explode($paraSeparator, $value) as $content) {
                     $paragraph = new LexParagraph();
                     if ($content) {
                         $paragraph->content = $content;
                     }
                     $multiParagraph->paragraphs->append($paragraph);
                 }
                 $this->project->addInputSystem($inputSystemTag);
                 break;
             default:
                 $this->currentNodeError()->addUnhandledElement($element->getName());
         }
     }
     array_pop($this->nodeErrors);
     return $multiParagraph;
 }
 /**
  * @param ProjectModel|LexProjectModel $projectModel
  * @param string       $id
  */
 public function __construct($projectModel, $id = '')
 {
     $this->setReadOnlyProp('authorInfo');
     $this->setReadOnlyProp('replies');
     $this->setReadOnlyProp('score');
     $this->setReadOnlyProp('status');
     $this->setPrivateProp('isDeleted');
     $this->id = new Id();
     $this->entryRef = new IdReference();
     $this->isDeleted = false;
     $this->replies = new ArrayOf(function () {
         return new LexCommentReply();
     });
     $this->status = self::STATUS_OPEN;
     $this->score = 0;
     $this->authorInfo = new LexAuthorInfo();
     $this->regarding = new LexCommentFieldReference();
     $databaseName = $projectModel->databaseName();
     parent::__construct(self::mapper($databaseName), $id);
 }
 public function run($userId, $mode = 'test')
 {
     $testMode = $mode != 'run';
     $message = "Remove Environment and Reversal Entries from field order config\n\n";
     // Note: LF projects that don't have config data in mongo will use the default PHP model.
     // In these cases, the migration script won't find these field orders to remove, so nothing gets written out
     $projectlist = new ProjectListModel();
     $projectlist->read();
     $projectsAffected = 0;
     foreach ($projectlist->entries as $projectParams) {
         // foreach existing project
         $projectId = $projectParams['id'];
         $project = new ProjectModel($projectId);
         if ($project->appName == 'lexicon') {
             $project = new LexProjectModel($projectId);
             $fieldOrderUpdated = 0;
             $this->RemoveFromArray("environments", $project->config->entry->fieldOrder, $message, $fieldOrderUpdated);
             $fieldsArray = $project->config->entry->fields->getArrayCopy();
             if (array_key_exists("senses", $fieldsArray)) {
                 $this->RemoveFromArray("reversalEntries", $fieldsArray["senses"]->fieldOrder, $message, $fieldOrderUpdated);
                 $project->config->entry->fields->exchangeArray($fieldsArray);
             }
             if ($fieldOrderUpdated > 0) {
                 $projectsAffected++;
                 $message .= "\tRemoved: {$fieldOrderUpdated} field orders from {$project->projectName}\n";
                 if (!$testMode) {
                     $message .= "\tSaving changes to project {$project->projectName}.\n\n";
                     $project->write();
                 }
             }
         }
         unset($project);
     }
     if ($projectsAffected > 0) {
         $message .= "{$projectsAffected} projects were fixed\n";
     } else {
         $message .= "No projects needed fixing\n";
     }
     return $message;
 }
 /**
  *
  * @param string $zipFilePath
  * @param LexProjectModel $projectModel
  * @param string $mergeRule (use LiftMergeRule const)
  * @param boolean $skipSameModTime
  * @param boolean $deleteMatchingEntry
  * @throws \Exception
  * @return LiftImport
  */
 public function importZip($zipFilePath, $projectModel, $mergeRule = LiftMergeRule::IMPORT_WINS, $skipSameModTime = false, $deleteMatchingEntry = false)
 {
     $assetsFolderPath = $projectModel->getAssetsFolderPath();
     $extractFolderPath = $assetsFolderPath . '/initialUpload_' . mt_rand();
     $this->report = new ImportErrorReport();
     $zipNodeError = new ZipImportNodeError(ZipImportNodeError::FILE, basename($zipFilePath));
     try {
         self::extractZip($zipFilePath, $extractFolderPath);
         // Now find the .lift file in the uploaded zip
         $dirIter = new \RecursiveDirectoryIterator($extractFolderPath);
         $iterIter = new \RecursiveIteratorIterator($dirIter);
         $liftIter = new \RegexIterator($iterIter, '/\\.lift$/', \RegexIterator::MATCH);
         $liftFilePaths = array();
         foreach ($liftIter as $file) {
             $liftFilePaths[] = $file->getPathname();
         }
         if (empty($liftFilePaths)) {
             throw new \Exception("Uploaded file does not contain any LIFT data");
         }
         if (count($liftFilePaths) > 1) {
             foreach (array_slice($liftFilePaths, 1) as $filename) {
                 $zipNodeError->addUnhandledLiftFile(basename($filename));
             }
         }
         // Import subfolders
         foreach (glob($extractFolderPath . '/*', GLOB_ONLYDIR) as $folderPath) {
             $folderName = basename($folderPath);
             switch ($folderName) {
                 case 'pictures':
                 case 'audio':
                 case 'others':
                 case 'WritingSystems':
                     $assetsPath = $assetsFolderPath . "/" . $folderName;
                     if (file_exists($folderPath) && is_dir($folderPath)) {
                         FileUtilities::copyFolderTree($folderPath, $assetsPath);
                     }
                     break;
                 default:
                     $zipNodeError->addUnhandledSubfolder($folderName);
             }
         }
         // Import first .lift file (only).
         $this->liftFilePath = $liftFilePaths[0];
         $this->merge($this->liftFilePath, $projectModel, $mergeRule, $skipSameModTime, $deleteMatchingEntry);
         if ($zipNodeError->hasError()) {
             error_log($zipNodeError->toString() . "\n");
         }
         foreach ($this->report->nodeErrors as $subnodeError) {
             $zipNodeError->addSubnodeError($subnodeError);
         }
         $this->report = new ImportErrorReport();
         $this->report->nodeErrors[] = $zipNodeError;
     } catch (\Exception $e) {
         if ($zipNodeError->hasError()) {
             error_log($zipNodeError->toString() . "\n");
         }
         foreach ($this->report->nodeErrors as $subnodeError) {
             $zipNodeError->addSubnodeError($subnodeError);
         }
         $this->report = new ImportErrorReport();
         $this->report->nodeErrors[] = $zipNodeError;
         throw new \Exception($e->getMessage(), $e->getCode());
     }
     return $this;
 }
 /**
  * Import a LIFT file
  *
  * @param string $projectId
  * @param string $mediaType
  * @param string $tmpFilePath
  * @throws \Exception
  * @return UploadResponse
  */
 public static function importLiftFile($projectId, $mediaType, $tmpFilePath)
 {
     if ($mediaType != 'import-lift') {
         throw new \Exception("Unsupported upload type.");
     }
     if (!$tmpFilePath) {
         throw new \Exception("Upload controller did not move the uploaded file.");
     }
     $file = $_FILES['file'];
     $fileName = $file['name'];
     $mergeRule = $_POST['mergeRule'];
     $skipSameModTime = $_POST['skipSameModTime'];
     $deleteMatchingEntry = $_POST['deleteMatchingEntry'];
     $finfo = finfo_open(FILEINFO_MIME_TYPE);
     $fileType = finfo_file($finfo, $tmpFilePath);
     finfo_close($finfo);
     $fileName = FileUtilities::replaceSpecialCharacters($fileName);
     $fileExt = false === ($pos = strrpos($fileName, '.')) ? '' : substr($fileName, $pos);
     $allowedTypes = array("text/xml", "application/xml");
     $response = new UploadResponse();
     if (in_array(strtolower($fileType), $allowedTypes) && in_array(strtolower($fileExt), self::$allowedLiftExtensions)) {
         // make the folders if they don't exist
         $project = new LexProjectModel($projectId);
         $project->createAssetsFolders();
         $folderPath = $project->getAssetsFolderPath();
         $importer = LiftImport::get()->merge($tmpFilePath, $project, $mergeRule, $skipSameModTime, $deleteMatchingEntry);
         $project->write();
         $moveOk = true;
         if (!$project->liftFilePath || $mergeRule != LiftMergeRule::IMPORT_LOSES) {
             // cleanup previous files of any allowed extension
             $cleanupFiles = glob($folderPath . '/*[' . implode(', ', self::$allowedLiftExtensions) . ']');
             foreach ($cleanupFiles as $cleanupFile) {
                 @unlink($cleanupFile);
             }
             // move uploaded LIFT file from tmp location to assets
             $filePath = $folderPath . DIRECTORY_SEPARATOR . $fileName;
             $project->liftFilePath = $filePath;
             $project->write();
             $moveOk = copy($tmpFilePath, $filePath);
             @unlink($tmpFilePath);
         }
         // construct server response
         if ($moveOk && $tmpFilePath) {
             $data = new ImportResult();
             $data->path = $project->getAssetsRelativePath();
             $data->fileName = $fileName;
             $data->stats = $importer->stats;
             $data->importErrors = $importer->getReport()->toFormattedString();
             $response->result = true;
         } else {
             $data = new ErrorResult();
             $data->errorType = 'UserMessage';
             $data->errorMessage = "{$fileName} could not be saved to the right location. Contact your Site Administrator.";
             $response->result = false;
         }
     } else {
         $allowedExtensionsStr = implode(", ", self::$allowedLiftExtensions);
         $data = new ErrorResult();
         $data->errorType = 'UserMessage';
         if (count(self::$allowedLiftExtensions) < 1) {
             $data->errorMessage = "{$fileName} of type: {$fileType} is not an allowed LIFT file. No LIFT file formats are currently enabled, contact your Site Administrator.";
         } elseif (count(self::$allowedLiftExtensions) == 1) {
             $data->errorMessage = "{$fileName} of type: {$fileType} is not an allowed LIFT file. Ensure the file is a {$allowedExtensionsStr}.";
         } else {
             $data->errorMessage = "{$fileName} of type: {$fileType} is not an allowed LIFT file. Ensure the file is one of the following types: {$allowedExtensionsStr}.";
         }
         $response->result = false;
     }
     $response->data = $data;
     return $response;
 }
 public function run($userId, $mode = 'test')
 {
     $testMode = $mode != 'run';
     $message = "Fix Lexicon View Settings (except Environments and ReversalEntries) to default to visible\n\n";
     $fixCount = 0;
     $projectlist = new ProjectListModel();
     $projectlist->read();
     foreach ($projectlist->entries as $projectParams) {
         // foreach existing project
         $projectId = $projectParams['id'];
         $project = new ProjectModel($projectId);
         if ($project->appName == 'lexicon') {
             $project = new LexProjectModel($projectId);
             //$message .= "\nInspecting project $project->projectName.\n";
             $showFieldUpdated = 0;
             $roleShowFieldUpdated = 0;
             //$hideFieldUpdated = 0;
             $roleHideFieldUpdated = 0;
             $disabledFields = array("environments", "reversalEntries");
             foreach ($project->config->roleViews as $role => $roleView) {
                 foreach ($roleView->fields as $fieldName => $field) {
                     if (in_array($fieldName, $disabledFields)) {
                         if ($field->show) {
                             // Hide disabled fields
                             //$message .= "Hiding role $role view of $fieldName\n";
                             $field->show = false;
                             $showFieldUpdated++;
                             $roleHideFieldUpdated++;
                         }
                     } else {
                         if (!$field->show) {
                             // enable all other fields by default
                             $field->show = true;
                             $showFieldUpdated++;
                             $roleShowFieldUpdated++;
                         }
                     }
                 }
             }
             $userShowFieldUpdated = 0;
             $userHideFieldUpdated = 0;
             foreach ($project->config->userViews as $userId => $userView) {
                 foreach ($userView->fields as $fieldName => $field) {
                     if (in_array($fieldName, $disabledFields)) {
                         if ($field->show) {
                             // Hide disabled fields
                             //$message .= "Hiding user $userId view of $fieldName\n";
                             $field->show = false;
                             $showFieldUpdated++;
                             $userHideFieldUpdated++;
                         }
                     } else {
                         if (!$field->show) {
                             // enable all other fields by default
                             $field->show = true;
                             $showFieldUpdated++;
                             $userShowFieldUpdated++;
                         }
                     }
                 }
             }
             if ($showFieldUpdated > 0) {
                 $fixCount++;
                 $message .= "  Toggled {$showFieldUpdated} View Settings fields. This comprised: \n";
                 if ($roleShowFieldUpdated > 0) {
                     $message .= "   - Changed {$roleShowFieldUpdated} role-based View Settings fields to be visible.\n";
                 }
                 if ($userShowFieldUpdated > 0) {
                     $message .= "   - Changed {$userShowFieldUpdated} user-based View Settings fields to be visible.\n";
                 }
                 if ($roleHideFieldUpdated > 0) {
                     $message .= "   - Changed {$roleHideFieldUpdated} role-based View Settings fields to be invisible.\n";
                 }
                 if ($userHideFieldUpdated > 0) {
                     $message .= "   - Changed {$userHideFieldUpdated} user-based View Settings fields to be invisible.\n";
                 }
                 if (!$testMode) {
                     $message .= "  Saving changes to project {$project->projectName}.\n";
                     $project->write();
                 }
             } else {
                 //$message .= "  No invisible View Settings fields found/changed.\n";
             }
         }
     }
     if ($fixCount > 0) {
         $message .= "{$fixCount} projects were fixed\n";
     } else {
         $message .= "No projects needed fixing\n";
     }
     return $message;
 }
 public function testCreateProject_NewSRProject_SRProjectWithLinks()
 {
     self::$environ = new LexiconMongoTestEnvironment();
     self::$environ->clean();
     $user1Id = self::$environ->createUser("user1name", "User1 Name", "*****@*****.**");
     $user1 = new UserModel($user1Id);
     $srProject = array('identifier' => 'srIdentifier', 'name' => 'srName', 'repository' => 'http://public.languagedepot.org', 'role' => 'manager');
     $projectId = ProjectCommands::createProject(SF_TESTPROJECT, SF_TESTPROJECTCODE, LexProjectModel::LEXICON_APP, $user1->id->asString(), self::$environ->website, $srProject);
     $project = new LexProjectModel($projectId);
     $assetImagePath = $project->getImageFolderPath();
     $assetAudioPath = $project->getAudioFolderPath();
     $this->assertTrue($project->hasSendReceive());
     $this->assertTrue(is_link($assetImagePath));
     $this->assertTrue(is_link($assetAudioPath));
     $projectWorkPath = $project->getSendReceiveWorkFolder();
     FileUtilities::removeFolderAndAllContents($project->getAssetsFolderPath());
     FileUtilities::removeFolderAndAllContents($projectWorkPath);
 }
    $text1 = TextCommands::updateText($testProjectId, array('id' => '', 'title' => $constants['testText1Title'], 'content' => $constants['testText1Content']));
    $text2 = TextCommands::updateText($testProjectId, array('id' => '', 'title' => $constants['testText2Title'], 'content' => $constants['testText2Content']));
    $question1 = QuestionCommands::updateQuestion($testProjectId, array('id' => '', 'textRef' => $text1, 'title' => $constants['testText1Question1Title'], 'description' => $constants['testText1Question1Content']));
    $question2 = QuestionCommands::updateQuestion($testProjectId, array('id' => '', 'textRef' => $text1, 'title' => $constants['testText1Question2Title'], 'description' => $constants['testText1Question2Content']));
    $template1 = QuestionTemplateCommands::updateTemplate($testProjectId, array('id' => '', 'title' => 'first template', 'description' => 'not particularly interesting'));
    $template2 = QuestionTemplateCommands::updateTemplate($testProjectId, array('id' => '', 'title' => 'second template', 'description' => 'not entirely interesting'));
    $answer1 = QuestionCommands::updateAnswer($testProjectId, $question1, array('id' => '', 'content' => $constants['testText1Question1Answer']), $managerUserId);
    $answer1Id = array_keys($answer1)[0];
    $answer2 = QuestionCommands::updateAnswer($testProjectId, $question2, array('id' => '', 'content' => $constants['testText1Question2Answer']), $managerUserId);
    $answer2Id = array_keys($answer2)[0];
    $comment1 = QuestionCommands::updateComment($testProjectId, $question1, $answer1Id, array('id' => '', 'content' => $constants['testText1Question1Answer1Comment']), $managerUserId);
    $comment2 = QuestionCommands::updateComment($testProjectId, $question2, $answer2Id, array('id' => '', 'content' => $constants['testText1Question2Answer2Comment']), $managerUserId);
} elseif ($site == 'languageforge') {
    // Set up LanguageForge E2E test envrionment here
    ProjectCommands::updateUserRole($testProjectId, $observerUserId, LexRoles::OBSERVER);
    $testProjectModel = new LexProjectModel($testProjectId);
    $testProjectModel->addInputSystem('th-fonipa', 'tipa', 'Thai');
    $testProjectModel->config->entry->fields[LexConfig::LEXEME]->inputSystems[] = 'th-fonipa';
    $testProjectModel->addInputSystem('th-Zxxx-x-audio', 'taud', 'Thai Voice');
    $testProjectModel->config->entry->fields[LexConfig::LEXEME]->inputSystems[] = 'th-Zxxx-x-audio';
    $testProjectId = $testProjectModel->write();
    // setup to mimic file upload
    $fileName = $constants['testEntry1']['lexeme']['th-Zxxx-x-audio']['value'];
    $file = array();
    $file['name'] = $fileName;
    $_FILES['file'] = $file;
    // put a copy of the test file in tmp
    $tmpFilePath = sys_get_temp_dir() . "/CopyOf{$fileName}";
    copy(TestPath . "php/common/{$fileName}", $tmpFilePath);
    $response = LexUploadCommands::uploadAudioFile($testProjectId, 'audio', $tmpFilePath);
    // cleanup tmp file if it still exists
 /**
  * @param string $projectId
  * @param string $userId
  * @param null $lastFetchTime
  * @param int $offset
  * @throws \Exception
  * @return array
  */
 public static function encode($projectId, $userId, $lastFetchTime = null, $offset = 0)
 {
     $data = array();
     $project = new LexProjectModel($projectId);
     if ($lastFetchTime) {
         $entriesModel = new LexEntryListModel($project, $lastFetchTime);
         $entriesModel->readAsModels();
         $commentsModel = new LexCommentListModel($project, $lastFetchTime);
         $commentsModel->readAsModels();
     } else {
         $entriesModel = new LexEntryListModel($project, null, self::MAX_ENTRIES_PER_REQUEST, $offset);
         $entriesModel->readAsModels();
         $commentsModel = new LexCommentListModel($project, null, self::MAX_ENTRIES_PER_REQUEST, $offset);
         $commentsModel->readAsModels();
         $data['itemTotalCount'] = $entriesModel->totalCount > $commentsModel->totalCount ? $entriesModel->totalCount : $commentsModel->totalCount;
         $data['itemCount'] = $entriesModel->count > $commentsModel->count ? $entriesModel->count : $commentsModel->count;
         $data['offset'] = $offset;
     }
     $entries = LexDbeDtoEntriesEncoder::encode($entriesModel);
     $entries = $entries['entries'];
     $encodedComments = LexDbeDtoCommentsEncoder::encode($commentsModel);
     $data['comments'] = $encodedComments['entries'];
     $votes = new UserGenericVoteModel($userId, $projectId, 'lexCommentPlusOne');
     $votesDto = array();
     foreach ($votes->votes as $vote) {
         $votesDto[$vote->ref->id] = true;
     }
     $data['commentsUserPlusOne'] = $votesDto;
     if (!is_null($lastFetchTime)) {
         $deletedEntriesModel = new LexDeletedEntryListModel($project, $lastFetchTime);
         $deletedEntriesModel->read();
         $data['deletedEntryIds'] = array_map(function ($e) {
             return $e['id'];
         }, $deletedEntriesModel->entries);
         $deletedCommentsModel = new LexDeletedCommentListModel($project, $lastFetchTime);
         $deletedCommentsModel->read();
         $data['deletedCommentIds'] = array_map(function ($c) {
             return $c['id'];
         }, $deletedCommentsModel->entries);
     }
     $lexemeInputSystems = $project->config->entry->fields[LexConfig::LEXEME]->inputSystems;
     usort($entries, function ($a, $b) use($lexemeInputSystems) {
         $lexeme1Value = '';
         if (array_key_exists(LexConfig::LEXEME, $a)) {
             $lexeme1 = $a[LexConfig::LEXEME];
             foreach ($lexemeInputSystems as $ws) {
                 if (array_key_exists($ws, $lexeme1) && $lexeme1[$ws]['value'] != '') {
                     $lexeme1Value = $lexeme1[$ws]['value'];
                     // '\P{xx} matches all characters without the Unicode property XX. L is the Unicode property "letter".
                     $lexeme1Value = preg_replace('/^\\P{L}+/', '', $lexeme1Value);
                     // Strip non-letter characters from front of word for sorting
                     break;
                 }
             }
         }
         $lexeme2Value = '';
         if (array_key_exists(LexConfig::LEXEME, $b)) {
             $lexeme2 = $b[LexConfig::LEXEME];
             foreach ($lexemeInputSystems as $ws) {
                 if (array_key_exists($ws, $lexeme2) && $lexeme2[$ws]['value'] != '') {
                     $lexeme2Value = $lexeme2[$ws]['value'];
                     $lexeme2Value = preg_replace('/^\\P{L}+/', '', $lexeme2Value);
                     // Strip non-letter characters from front of word for sorting
                     break;
                 }
             }
         }
         return strtolower($lexeme1Value) > strtolower($lexeme2Value) ? 1 : -1;
     });
     $data['entries'] = $entries;
     $data['timeOnServer'] = time();
     // for offline syncing
     if ($project->hasSendReceive()) {
         $data['sendReceive'] = array();
         $data['sendReceive']['status'] = SendReceiveCommands::getProjectStatus($projectId);
     }
     return $data;
 }
 /**
  * @param $identifier
  * @return string|bool $projectId if send receive project is linked to a LF project, false otherwise
  */
 public static function getProjectIdFromSendReceive($identifier)
 {
     $project = new LexProjectModel();
     if (!$project->readByProperty('sendReceiveProjectIdentifier', $identifier)) {
         return false;
     }
     return $project->id->asString();
 }