diff --git a/lib/Converter.php b/lib/Converter.php index c287110..e52146b 100644 --- a/lib/Converter.php +++ b/lib/Converter.php @@ -17,7 +17,7 @@ class Converter extends PhpObject { parent::__construct(__CLASS__); } - public static function convertToGeoJson($sCodeName) { + public static function convertToGeoJson(string $sCodeName) { $oGpx = new Gpx($sCodeName); $oGeoJson = new GeoJson($sCodeName); @@ -32,11 +32,17 @@ class Converter extends PhpObject { ]; } - public static function isGeoJsonValid($sCodeName) { + public static function hasGpxFile(string $sCodeName) { + return ($sCodeName != '' && file_exists(Gpx::getBackendFilePath($sCodeName))); + } + + public static function isGeoJsonValid(string $sCodeName) { $sGpxFilePath = Gpx::getBackendFilePath($sCodeName); $sGeoJsonFilePath = GeoJson::getBackendFilePath($sCodeName); - //No need to generate if gpx is missing - return !file_exists($sGpxFilePath) || file_exists($sGeoJsonFilePath) && filemtime($sGeoJsonFilePath) >= filemtime($sGpxFilePath); + return + !self::hasGpxFile($sCodeName) //No GPX, no need for geoJSON + || + file_exists($sGeoJsonFilePath) && filemtime($sGeoJsonFilePath) >= filemtime($sGpxFilePath); //geoJSON needs to be (re)generated } -} \ No newline at end of file +} diff --git a/lib/Feed.php b/lib/Feed.php index 42de64e..d3ea836 100644 --- a/lib/Feed.php +++ b/lib/Feed.php @@ -278,12 +278,12 @@ class Feed extends PhpObject { $sWeatherIcon = 'unknown'; } - //Get Condition ID - $sCondKey = (new Translator(self::WEATHER_PARAM['lang']))->getTranslationKey($sWeatherCond); + //Get Condition Language ID + $sCondLangId = (new Translator(self::WEATHER_PARAM['lang']))->getTranslationKey($sWeatherCond); return array( 'weather_icon' => $sWeatherIcon, - 'weather_cond' => $sCondKey, + 'weather_cond' => $sCondLangId, 'weather_temp' => floatval($sWeatherTemp) ); } @@ -318,16 +318,21 @@ class Feed extends PhpObject { } public function delete() { - $asResult = array(); - if($this->getFeedId() > 0) { - $asResult = array( - 'id' => $this->getFeedId(), - 'del' => $this->oDb->deleteRow(self::FEED_TABLE, $this->getFeedId()), - 'desc' => $this->oDb->getLastError() - ); - } - else $asResult = array('del'=>false, 'desc'=>'Error while setting project: no Feed ID'); + $bSuccess = false; + $sLangId = ''; + $asLangParams = array(); + $asData = array(); - return $asResult; + if($this->getFeedId() > 0) { + $asData['id'] = $this->getFeedId(); + $bSuccess = $this->oDb->deleteRow(self::FEED_TABLE, $this->getFeedId()); + if(!$bSuccess) $sLangId = 'error.commit_db'; + } + else { + $sLangId = 'error.impossible_value'; + $asLangParams = array($this->getFeedId(), 'feed ID'); + } + + return Livetrail::getResult($bSuccess, $sLangId, $asData, $asLangParams); } } diff --git a/lib/Livetrail.php b/lib/Livetrail.php index 1db374c..e7e3402 100755 --- a/lib/Livetrail.php +++ b/lib/Livetrail.php @@ -259,7 +259,7 @@ class Livetrail extends Main public function updateProject() { $bNewMsg = false; $bSuccess = true; - $sDesc = ''; + $sLangId = ''; //Update all feeds belonging to the project $asFeeds = $this->oProject->getFeedIds(); @@ -271,11 +271,11 @@ class Livetrail extends Main //Send Update Email if($bNewMsg) { $bSuccess = $this->sendEmail(); - $sDesc = Mask::LANG_PREFIX.($bSuccess?'email.sent':'email.failure'); + $sLangId = $bSuccess?'email.sent':'email.failure'; } - else $sDesc = Mask::LANG_PREFIX.'spot.no_new_msg'; + else $sLangId = 'spot.no_new_msg'; - return self::getJsonResult($bSuccess, $sDesc); + return self::getJsonResult($bSuccess, $sLangId); } private function sendEmail() { @@ -378,13 +378,13 @@ class Livetrail extends Main public function login($sEmail, $sPassword, $sNickName) { $asResult = $this->oUser->login($sEmail, $sPassword, $this->oLang->getLanguage(), date_default_timezone_get(), $sNickName); - if($asResult['result'] && $asResult['desc'] == 'subscribe_user') return $this->subscribe(); - else return self::getJsonResult($asResult['result'], $asResult['desc'], $this->oUser->getUserInfo()); + if($asResult['result'] && $asResult['data']['subscribe']) return $this->subscribe(); + else return self::getJsonResult($asResult['result'], $asResult['desc_lang_id'], $this->oUser->getUserInfo(), $asResult['desc_lang_params']); } public function logout() { $asResult = $this->oUser->logout(); - return self::getJsonResult($asResult['result'], $asResult['desc'], User::DEFAULT_USER); + return self::getJsonResult($asResult['result'], $asResult['desc_lang_id'], User::DEFAULT_USER, $asResult['desc_lang_params']); } public function subscribe() { @@ -395,12 +395,12 @@ class Livetrail extends Main $oConfEmail->setDestInfo($asUserInfo); $oConfEmail->send(); } - return self::getJsonResult($asResult['result'], $asResult['desc'], $asUserInfo); + return self::getJsonResult($asResult['result'], $asResult['desc_lang_id'], $asUserInfo, $asResult['desc_lang_params']); } public function unsubscribe() { $asResult = $this->oUser->setSubscription(false); - return self::getJsonResult($asResult['result'], $asResult['desc'], $this->oUser->getUserInfo()); + return self::getJsonResult($asResult['result'], $asResult['desc_lang_id'], $this->oUser->getUserInfo(), $asResult['desc_lang_params']); } private function getSpotMessages($asMsgIds=array()) @@ -555,7 +555,7 @@ class Livetrail extends Main public function getNewFeed($iRefIdFirst) { $asResult = array(); - $sDesc = ''; + $sLangId = ''; if($this->oProject->isEditable()) { $asMessageIds = $asMediaIds = array(); @@ -582,9 +582,9 @@ class Livetrail extends Main $asResult = array_merge($asResult, $asMarkers); } - else $sDesc = 'project.modes.histo'; + else $sLangId = 'project.modes.histo'; - return self::getJsonResult(true, $sDesc, $asResult); + return self::getJsonResult(true, $sLangId, $asResult); } public function getNextFeed($iRefIdLast=0, $bInternal=false) { @@ -671,7 +671,7 @@ class Livetrail extends Main public function addPost($sName, $sPost) { $iPostId = 0; - $sDesc = ''; + $sLangId = ''; if($this->oProject->isEditable()) { $asData = array( @@ -683,18 +683,19 @@ class Livetrail extends Main ); if($this->oUser->getUserId() > 0) $asData[Db::getId(User::USER_TABLE)] = $this->oUser->getUserId(); - $iPostId = $this->oDb->insertRow(self::POST_TABLE, $asData); + $iPostId = $this->oDb->insertRow(self::POST_TABLE, $asData); + if($iPostId == 0) $sLangId = 'error.commit_db'; - $this->oUser->updateNickname($sName); + $this->oUser->updateNickname($sName); } - else $sDesc = 'project.modes.histo'; + else $sLangId = 'project.modes.histo'; - return self::getJsonResult(($iPostId > 0), $sDesc); + return self::getJsonResult(($iPostId > 0), $sLangId); } public function upload() { - $oUploader = new Uploader($this->oMedia, $this->oLang); + $oUploader = new Uploader($this->oMedia); return $oUploader->sBody; } @@ -702,7 +703,7 @@ class Livetrail extends Main public function addComment($iMediaId, $sComment) { $oMedia = new Media($this->oDb, $this->oProject, $iMediaId); $asResult = $oMedia->setComment($sComment); - return self::getJsonResult($asResult['result'], $asResult['desc'], $asResult['data']); + return self::getJsonResult($asResult['result'], $asResult['desc_lang_id'], $asResult['data'], $asResult['desc_lang_params']); } public function addPosition($sLat, $sLng, $iTimestamp) { @@ -711,11 +712,11 @@ class Livetrail extends Main if($bSuccess) { $bSuccess = $this->sendEmail(); - $sDesc = Mask::LANG_PREFIX.($bSuccess?'email.sent':'email.failure'); + $sLangId = $bSuccess?'email.sent':'email.failure'; } - else $sDesc = 'error.commit_db'; + else $sLangId = 'error.commit_db'; - return self::getJsonResult($bSuccess, $sDesc); + return self::getJsonResult($bSuccess, $sLangId); } public function getAdminSettings() { @@ -737,14 +738,16 @@ class Livetrail extends Main public function setAdminSettings($sType, $iId, $sField, $sValue) { $bSuccess = false; - $sDesc = ''; + $sLangId = ''; + $asLangParams = array(); $asResult = array(); - if($this->oDb->isId($sField) && $sValue <= 0) return self::getJsonResult(false, $this->oLang->getTranslation('error.impossible_value', [$sValue, $sField])); + if($this->oDb->isId($sField) && $sValue <= 0) return self::getJsonResult(false, 'error.impossible_value', array(), array($sValue, $sField)); switch($sType) { case 'project': $oProject = new Project($this->oDb, $iId); + switch($sField) { case 'name': $bSuccess = $oProject->setProjectName($sValue); @@ -759,8 +762,18 @@ class Livetrail extends Main $bSuccess = $oProject->setActivePeriod($sValue.' 23:59:59', 'to'); break; default: - $sDesc = $this->oLang->getTranslation('error.unknown_field', $sField); + $sLangId = 'error.unknown_field'; + $asLangParams = array($sField); } + + //Identify missing GPX file + $sProjectCodeName = ($sField == 'codename')?$sValue:$oProject->getProjectCodeName(); + if(!Converter::hasGpxFile($sProjectCodeName)) { + $bSuccess = true; + $sLangId = 'error.file_missing'; + $asLangParams = array('GPX', $sProjectCodeName.Gpx::EXT); + } + $asResult = $oProject->getProject(); $asResult['active_from'] = substr($asResult['active_from'], 0, 10); $asResult['active_to'] = substr($asResult['active_to'], 0, 10); @@ -778,7 +791,8 @@ class Livetrail extends Main $bSuccess = $oFeed->setProjectId($sValue); break; default: - $sDesc = $this->oLang->getTranslation('error.unknown_field', $sField); + $sLangId = 'error.unknown_field'; + $asLangParams = array($sField); } $asResult = $oFeed->getFeed(); break; @@ -787,22 +801,24 @@ class Livetrail extends Main case 'clearance': $asReturnCode = $this->oUser->setUserClearance($iId, $sValue); $bSuccess = $asReturnCode['result']; - $sDesc = $asReturnCode['desc']; + $sLangId = $asReturnCode['desc_lang_id']; + $asLangParams = $asReturnCode['desc_lang_params']; break; default: - $sDesc = $this->oLang->getTranslation('error.unknown_field', $sField); + $sLangId = 'error.unknown_field'; + $asLangParams = array($sField); } $asResult = $this->oUser->getUserById($iId); break; } - if(!$bSuccess && $sDesc=='') $sDesc = Mask::LANG_PREFIX.'error.commit_db'; + if(!$bSuccess && $sLangId=='') $sLangId = 'error.commit_db'; - return self::getJsonResult($bSuccess, $sDesc, array($sType=>array($asResult))); + return self::getJsonResult($bSuccess, $sLangId, array($sType=>array($asResult)), $asLangParams); } public function createAdminSettings($sType) { $bSuccess = false; - $sDesc = ''; + $sLangId = ''; $asResult = array(); switch($sType) { @@ -828,31 +844,36 @@ class Livetrail extends Main ); break; } + if(!$bSuccess && $sLangId=='') $sLangId = 'error.commit_db'; - return self::getJsonResult($bSuccess, $sDesc, $asResult); + return self::getJsonResult($bSuccess, $sLangId, $asResult); } public function deleteAdminSettings($sType, $iId) { $bSuccess = false; - $sDesc = ''; + $sLangId = ''; + $asLangParams = array(); $asResult = array(); switch($sType) { case 'project': $oProject = new Project($this->oDb, $iId); $asResult = $oProject->delete(); - $sDesc = $asResult['project'][0]['desc']; + $sLangId = $asResult['project'][0]['desc_lang_id']; + $asLangParams = $asResult['project'][0]['desc_lang_params']; $bSuccess = $asResult['project'][0]['del']; break; case 'feed': $oFeed = new Feed($this->oDb, $iId); $asResult = array('feed' => array($oFeed->delete())); - $sDesc = $asResult['feed'][0]['desc']; - $bSuccess = $asResult['feed'][0]['del']; + $sLangId = $asResult['feed'][0]['desc_lang_id']; + $asLangParams = $asResult['feed'][0]['desc_lang_params']; + $bSuccess = $asResult['feed'][0]['result']; break; } + if(!$bSuccess && $sLangId=='') $sLangId = 'error.commit_db'; - return self::getJsonResult($bSuccess, $sDesc, $asResult); + return self::getJsonResult($bSuccess, $sLangId, $asResult, $asLangParams); } public static function decToDms($dValue, $sType) { diff --git a/lib/Media.php b/lib/Media.php index af70c48..5c67a7a 100644 --- a/lib/Media.php +++ b/lib/Media.php @@ -45,16 +45,16 @@ class Media extends PhpObject { } public function setComment($sComment) { - $sError = ''; + $sLangId = ''; $asData = array(); if($this->iMediaId > 0) { $bResult = $this->oDb->updateRow(self::MEDIA_TABLE, $this->iMediaId, array('comment'=>$sComment)); - if(!$bResult) $sError = 'error.commit_db'; + if(!$bResult) $sLangId = 'error.commit_db'; else $asData = $this->getInfo(); } - else $sError = 'media.no_id'; + else $sLangId = 'media.no_id'; - return Livetrail::getResult(($sError==''), $sError, $asData); + return Livetrail::getResult(($sLangId==''), $sLangId, $asData); } public function getMediasInfo($oMediaIds=null) { @@ -95,14 +95,14 @@ class Media extends PhpObject { } public function addMedia($sMediaName, $sMethod='upload') { - $sError = ''; + $sLangId = ''; $asParams = array(); if(!$this->isProjectEditable() && $sMethod!='sync') { - $sError = 'upload.mode_archived'; + $sLangId = 'upload.mode_archived'; $asParams[] = $this->oProject->getProjectCodeName(); } elseif($this->oDb->pingValue(self::MEDIA_TABLE, array('filename'=>$sMediaName)) && $sMethod!='sync') { - $sError = 'upload.media.exists'; + $sLangId = 'upload.media.exists'; $asParams[] = $sMediaName; } else { @@ -128,14 +128,14 @@ class Media extends PhpObject { if($sMethod=='sync') $iMediaId = $this->oDb->insertUpdateRow(self::MEDIA_TABLE, $asDbInfo, array('filename')); else $iMediaId = $this->oDb->insertRow(self::MEDIA_TABLE, $asDbInfo); - if(!$iMediaId) $sError = 'error.commit_db'; + if(!$iMediaId) $sLangId = 'error.commit_db'; else { $this->setMediaId($iMediaId); $asParams = $this->getInfo(); //Creates thumbnail } } - return Livetrail::getResult(($sError==''), $sError, $asParams); + return Livetrail::getResult(($sLangId==''), $sLangId, $asParams); } private function getMediaInfoFromFile($sMediaName) diff --git a/lib/Project.php b/lib/Project.php index 7a551ca..38213d7 100644 --- a/lib/Project.php +++ b/lib/Project.php @@ -137,8 +137,8 @@ class Project extends PhpObject { $asProjects = $this->oDb->selectRows($asInfo, 'codename'); foreach($asProjects as $sCodeName => &$asProject) { - //Update Geo JSON - if($sCodeName != '' && (!Converter::isGeoJsonValid($sCodeName) || !$asProject['latitude'] && !$asProject['longitude'])) { + //Update geoJSON + if(Converter::hasGpxFile($sCodeName) && (!Converter::isGeoJsonValid($sCodeName) || !$asProject['latitude'] && !$asProject['longitude'])) { $aiCenter = Converter::convertToGeoJson($sCodeName)['center']; $this->oDb->updateRow(self::PROJ_TABLE, $asProject['id'], ['latitude' => $aiCenter[1], 'longitude' => $aiCenter[0]]); $asProject['latitude'] = $aiCenter[1]; @@ -212,13 +212,15 @@ class Project extends PhpObject { $asResult['feed'][] = (new Feed($this->oDb, $iFeedId))->delete(); } + $bDeleted = $this->oDb->deleteRow(self::PROJ_TABLE, $this->getProjectId()); $asResult['project'][] = array( 'id' => $this->getProjectId(), - 'del' => $this->oDb->deleteRow(self::PROJ_TABLE, $this->getProjectId()), - 'desc' => $this->oDb->getLastError() + 'del' => $bDeleted, + 'desc_lang_id' => $bDeleted?'':'error.commit_db', + 'desc_lang_params' => array() ); } - else $asResult['project'][] = array('del'=>false, 'desc'=>'Error while setting project: no project ID'); + else $asResult['project'][] = array('del'=>false, 'desc_lang_id'=>'error.impossible_value', 'desc_lang_params'=>array($this->getProjectId(), 'project ID')); return $asResult; } diff --git a/lib/Uploader.php b/lib/Uploader.php index bc2f530..a604308 100644 --- a/lib/Uploader.php +++ b/lib/Uploader.php @@ -2,19 +2,16 @@ namespace Franzz\Livetrail; use Franzz\Objects\UploadHandler; -use Franzz\Objects\Translator; class Uploader extends UploadHandler { private Media $oMedia; - private Translator $oLang; public string $sBody; - function __construct(Media &$oMedia, Translator &$oLang) + function __construct(Media &$oMedia) { $this->oMedia = &$oMedia; - $this->oLang = &$oLang; $this->sBody = ''; parent::__construct(array( @@ -29,7 +26,9 @@ class Uploader extends UploadHandler //Check project mode if(!$this->oMedia->isProjectEditable()) { - $file->error = $this->get_error_message('upload.mode_archived', array($this->oMedia->getProjectCodeName())); + $file->error = true; + $file->desc_lang_id = 'upload.mode_archived'; + $file->desc_lang_params = array($this->oMedia->getProjectCodeName()); $bResult = false; } @@ -43,13 +42,22 @@ class Uploader extends UploadHandler if(empty($file->error)) { $asResult = $this->oMedia->addMedia($file->name); - if(!$asResult['result']) $file->error = $this->get_error_message($asResult['desc'], $asResult['data']); + if(!$asResult['result']) { + $file->error = true; + $file->desc_lang_id = $asResult['desc_lang_id']; + $file->desc_lang_params = $asResult['data']; + } else { $file->original_name = basename((string) $name); $file->id = $this->oMedia->getMediaId(); $file->thumbnail = $asResult['data']['thumb_path']; } } + if(!empty($file->error)) { + if(empty($file->desc_lang_id)) $file->desc_lang_id = is_string($file->error)?$file->error:'upload.error'; + if(empty($file->desc_lang_params)) $file->desc_lang_params = array(); + $file->error = true; + } return $file; } @@ -58,10 +66,7 @@ class Uploader extends UploadHandler $this->sBody .= $sBodyPart; } - protected function get_error_message($sError, $asParams=array()) { - $sTranslatedError = $this->oLang->getTranslation($sError, $asParams); - if($sTranslatedError) return $sTranslatedError; - elseif(array_key_exists($sError, $this->error_messages)) return $this->error_messages[$sError]; - else return $sError; + protected function get_error_message($sLangId, $asParams=array()) { + return array_key_exists($sLangId, $this->error_messages)?'upload.error':$sLangId; } } diff --git a/lib/User.php b/lib/User.php index b7b5e08..6a5f3ab 100644 --- a/lib/User.php +++ b/lib/User.php @@ -99,14 +99,14 @@ class User extends PhpObject { private function addUser($sEmail, $sLang, $sTimezone, $sNickName='') { $bSuccess = false; - $sDesc = ''; + $sLangId = ''; $iUserId = $this->oDb->insertRow( self::USER_TABLE, array('email'=>$sEmail, 'language'=>$sLang, 'timezone'=>$sTimezone) ); - if($iUserId == 0) $sDesc = 'lang:error.commit_db'; + if($iUserId == 0) $sLangId = 'error.commit_db'; else $bSuccess = true; //Extra optional values @@ -115,16 +115,16 @@ class User extends PhpObject { $this->updateGravatar($iUserId, $sEmail); } - return Livetrail::getResult($bSuccess, $sDesc, [Db::getId(self::USER_TABLE) => $iUserId]); + return Livetrail::getResult($bSuccess, $sLangId, [Db::getId(self::USER_TABLE) => $iUserId]); } public function setSubscription($bSubscribed) { if($this->getUserId() > 0) { $iSubscribed = $bSubscribed?1:0; $iUserId = $this->oDb->updateRow(self::USER_TABLE, $this->getUserId(), array('subscribed'=>$iSubscribed)); - if(!$iUserId) return Livetrail::getResult(false, 'lang:error.commit_db'); + if(!$iUserId) return Livetrail::getResult(false, 'error.commit_db'); $this->asUserInfo['subscribed'] = $iSubscribed; - return Livetrail::getResult(true, $iSubscribed?'lang:account.subscribed':'lang:account.unsubscribed'); + return Livetrail::getResult(true, $iSubscribed?'account.subscribed':'account.unsubscribed'); } } @@ -140,12 +140,13 @@ class User extends PhpObject { public function login($sEmail, $sPassword, $sLang, $sTimezone, $sNickName='') { $bSuccess = false; - $sDesc = ''; + $bSubscribe = false; + $sLangId = ''; $sEmail = strtolower(trim($sEmail)); //Check email value if(!filter_var($sEmail, FILTER_VALIDATE_EMAIL)) { - $sDesc = 'lang:account.invalid_email'; + $sLangId = 'account.invalid_email'; } else { //Check Email presence in DB @@ -161,13 +162,13 @@ class User extends PhpObject { //Is Admin if($asDBUser['clearance'] >= self::CLEARANCE_ADMIN) { //Request a password - if($sPassword === '') $sDesc = empty($asDBUser['password'])?'lang:account.set_password':'lang:account.password_required'; + if($sPassword === '') $sLangId = empty($asDBUser['password'])?'account.set_password':'account.password_required'; //Set password elseif(empty($asDBUser['password'])) { - if(!$this->oDb->updateRow(self::USER_TABLE, $iUserId, array('password' => password_hash($sPassword, PASSWORD_DEFAULT)))) $sDesc = 'lang:error.commit_db'; + if(!$this->oDb->updateRow(self::USER_TABLE, $iUserId, array('password' => password_hash($sPassword, PASSWORD_DEFAULT)))) $sLangId = 'error.commit_db'; else { - $sDesc = 'lang:account.password_set'; + $sLangId = 'account.password_set'; $bSuccess = true; } } @@ -175,20 +176,21 @@ class User extends PhpObject { //Check password elseif(password_verify($sPassword, $asDBUser['password'])) { $bSuccess = true; - $sDesc = 'lang:account.logged_in'; + $sLangId = 'account.logged_in'; } - else $sDesc = 'lang:account.invalid_credentials'; + else $sLangId = 'account.invalid_credentials'; } else { $bSuccess = true; - $sDesc = 'lang:account.logged_in'; + $sLangId = 'account.logged_in'; } } else { //Unknown user, create it $asAddResult = $this->addUser($sEmail, $sLang, $sTimezone, $sNickName); $bSuccess = $asAddResult['result']; - $sDesc = $bSuccess?'subscribe_user':$asAddResult['desc']; + $bSubscribe = $bSuccess; + $sLangId = $bSuccess?'':$asAddResult['desc_lang_id']; $iUserId = $asAddResult['data'][Db::getId(self::USER_TABLE)] ?? 0; } } @@ -199,7 +201,7 @@ class User extends PhpObject { $this->setTokenCookie(); } - return Livetrail::getResult($bSuccess, $sDesc); + return Livetrail::getResult($bSuccess, $sLangId, array('subscribe'=>$bSubscribe)); } public function logout() { @@ -207,7 +209,7 @@ class User extends PhpObject { $this->clearSession(); $this->clearCookie(); $this->setUserId(0); - return Livetrail::getResult(true, 'lang:account.logged_out'); + return Livetrail::getResult(true, 'account.logged_out'); } public function updateNickname($sNickname) { @@ -226,19 +228,23 @@ class User extends PhpObject { public function setUserClearance($iUserId, $iClearance) { $bSuccess = false; - $sDesc = ''; + $sLangId = ''; + $asLangParams = array(); - if(!$this->checkUserClearance(self::CLEARANCE_ADMIN)) $sDesc = 'unauthorized'; + if(!$this->checkUserClearance(self::CLEARANCE_ADMIN)) $sLangId = 'error.no_auth'; else { - if(!in_array($iClearance, self::CLEARANCES)) $sDesc = 'Setting wrong clearance "'.$iClearance.'" to user ID "'.$iUserId.'"'; + if(!in_array($iClearance, self::CLEARANCES)) { + $sLangId = 'error.impossible_value'; + $asLangParams = array($iClearance, 'clearance'); + } else { $iUserId = $this->oDb->updateRow(self::USER_TABLE, $iUserId, array('clearance'=>$iClearance)); - if(!$iUserId) $sDesc = 'lang:error.commit_db'; + if(!$iUserId) $sLangId = 'error.commit_db'; else $bSuccess = true; } } - return Livetrail::getResult($bSuccess, $sDesc); + return Livetrail::getResult($bSuccess, $sLangId, array(), $asLangParams); } /* Session */ diff --git a/readme.md b/readme.md index 3630cc9..889ef1d 100644 --- a/readme.md +++ b/readme.md @@ -30,8 +30,9 @@ Live Trail is a self-hosted web application for sharing journeys on an interacti 3. Copy timezone data: mariadb-tzinfo-to-sql /usr/share/zoneinfo | mariadb -u root mysql 4. Copy settings-sample.php to settings.php and populate 5. Follow CI/CD script in .gitea/workflows/deploy.yml -8. Go to #admin and create a new project, feed & maps -9. Add a GPX file named .gpx to /resources/geo/ +6. Add a GPX file named .gpx to /resources/geo/ +7. Go to #admin and create a new project (with code name = ) & feed + ## Web Root diff --git a/resources/lang/en.json b/resources/lang/en.json index c511fb8..a4a42ca 100644 --- a/resources/lang/en.json +++ b/resources/lang/en.json @@ -68,8 +68,10 @@ }, "error": { "commit_db": "Error committing to database", + "file_missing": "$0 file \"$1\" is missing", "impossible_value": "Value \"$0\" is not valid for field \"$1\"", "no_auth": "Not authorized", + "not_found": "Unknown action", "unknown_field": "Unknown field \"$0\"" }, "feed": { @@ -185,8 +187,16 @@ }, "mode_archived": "Project \"$0\" is archived. Uploads are not allowed.", "position": { + "determining": "Determining position…", + "error": "Unable to determine position", "new": "New position", - "title": "Add position" + "permission_denied": "Location permission was denied", + "sending": "Sending position…", + "success": "Position uploaded successfully", + "timeout": "Position request timed out", + "title": "Add position", + "unavailable": "Position is unavailable", + "unsupported": "This browser does not support geolocation" }, "success": "$0 uploaded successfully" }, diff --git a/resources/lang/es.json b/resources/lang/es.json index 072a48e..811811a 100644 --- a/resources/lang/es.json +++ b/resources/lang/es.json @@ -68,8 +68,10 @@ }, "error": { "commit_db": "Error SQL", + "file_missing": "Falta el archivo $0 \"$1\"", "impossible_value": "El valor \"$0\" no es posible para el campo \"$1\"", "no_auth": "Sin autorización", + "not_found": "Acción desconocida", "unknown_field": "Campo \"$0\" desconocido" }, "feed": { @@ -185,8 +187,16 @@ }, "mode_archived": "El proyecto \"$0\" está archivado. No se puede cargar.", "position": { + "determining": "Determinando la posición…", + "error": "No se puede determinar la posición", "new": "Nueva posición", - "title": "Subir posición" + "permission_denied": "Se ha denegado el permiso de ubicación", + "sending": "Enviando la posición…", + "success": "Posición subida correctamente", + "timeout": "La solicitud de posición ha agotado el tiempo de espera", + "title": "Subir posición", + "unavailable": "La posición no está disponible", + "unsupported": "Este navegador no admite la geolocalización" }, "success": "$0 se ha subido correctamente." }, diff --git a/resources/lang/fr.json b/resources/lang/fr.json index ebca399..8df5db6 100644 --- a/resources/lang/fr.json +++ b/resources/lang/fr.json @@ -68,8 +68,10 @@ }, "error": { "commit_db": "Erreur lors de la requête SQL", + "file_missing": "Le fichier $0 \"$1\" est manquant", "impossible_value": "La valeur \"$0\" n'est pas possible pour le champ \"$1\"", "no_auth": "Pas d'autorisation", + "not_found": "Action inconnue", "unknown_field": "Champ \"$0\" inconnu" }, "feed": { @@ -185,8 +187,16 @@ }, "mode_archived": "Le projet \"$0\" a été archivé. Aucun téléversement possible.", "position": { + "determining": "Détermination de la position…", + "error": "Impossible de déterminer la position", "new": "Nouvelle position", - "title": "Position supplémentaire" + "permission_denied": "L’autorisation d’accéder à la position a été refusée", + "sending": "Envoi de la position…", + "success": "Position téléversée avec succès", + "timeout": "La demande de position a expiré", + "title": "Position supplémentaire", + "unavailable": "La position n’est pas disponible", + "unsupported": "Ce navigateur ne prend pas en charge la géolocalisation" }, "success": "$0 a été téléversé" }, diff --git a/src/components/Admin.vue b/src/components/Admin.vue index e0d0117..a3b1ee1 100644 --- a/src/components/Admin.vue +++ b/src/components/Admin.vue @@ -24,8 +24,8 @@ export default { this.setProjects(); }, methods: { - l(id) { - return this.lang.get(id); + l(sLangId, asLangParams=[]) { + return this.lang.get(sLangId, asLangParams); }, addFeedback(sType, sMsg, asContext = {}) { delete asContext.a; @@ -60,22 +60,7 @@ export default { } } }) - .catch((sMsg) => {this.addFeedback('error', sMsg, {'create':sType});}); - }, - deleteElem(oElem) { - const asInputs = { - type: oElem.type, - id: oElem.id - }; - - this.api.post('admin_delete', asInputs) - .then(() => { - delete this.elems[asInputs.type][asInputs.id]; - this.addFeedback('success', this.l('admin.delete_success'), asInputs); - }) - .catch((sError) => { - this.addFeedback('error', sError, asInputs); - }); + .catch((oError) => {this.addFeedback('error', oError.desc_lang_text, {'create':sType});}); }, updateElem(oElem, oEvent) { if(this.saveTimer) clearTimeout(this.saveTimer); @@ -90,25 +75,43 @@ export default { value: sNewVal }; - this.api.post('admin_set', asInputs) - .then(() => { + this.api.request('admin_set', asInputs, 'POST') + .then((oResponse) => { this.elems[oElem.type][oElem.id][oEvent.target.name] = sNewVal; - this.addFeedback('success', this.l('admin.save_success'), asInputs); + const bSuccess = (oResponse.desc_lang_id == ''); + const sType = bSuccess?'success':'warning'; + const sText = this.l(bSuccess?'admin.save_success':oResponse.desc_lang_id, oResponse.desc_lang_params); + this.addFeedback(sType, sText, asInputs); }) - .catch((sError) => { + .catch((oError) => { oEvent.target.value = sOldVal; - this.addFeedback('error', sError, asInputs); + this.addFeedback('error', oError.desc_lang_text, asInputs); }); } }, + deleteElem(oElem) { + const asInputs = { + type: oElem.type, + id: oElem.id + }; + + this.api.post('admin_delete', asInputs) + .then(() => { + delete this.elems[asInputs.type][asInputs.id]; + this.addFeedback('success', this.l('admin.delete_success'), asInputs); + }) + .catch((oError) => { + this.addFeedback('error', oError.desc_lang_text, asInputs); + }); + }, queue(oElem, oEvent) { if(this.saveTimer) clearTimeout(this.saveTimer); this.saveTimer = setTimeout(() => {this.updateElem(oElem, oEvent);}, 2000); }, updateProject() { this.api.request('update_project', {}, 'POST') - .then((oResponse) => {this.addFeedback('success', oResponse.desc, {'update':'project'});}) - .catch((sMsg) => {this.addFeedback('error', sMsg, {'update':'project'});}); + .then((oResponse) => {this.addFeedback('success', oResponse.desc_lang_text, {'update':'project'});}) + .catch((oError) => {this.addFeedback('error', oError.desc_lang_text, {'update':'project'});}); } } } diff --git a/src/components/ProjectAccount.vue b/src/components/ProjectAccount.vue index f5aee0f..79ebb56 100644 --- a/src/components/ProjectAccount.vue +++ b/src/components/ProjectAccount.vue @@ -44,7 +44,7 @@ export default { this.api.request(this.user.subscribed?'subscribe':'unsubscribe', {}, 'POST') .then((asResponse) => { this.user.setInfo(asResponse.data); - this.feedbacks.push({type:asResponse.result, msg:asResponse.desc}); + this.feedbacks.push({type:asResponse.result, msg:asResponse.desc_lang_text}); }) .catch((sDesc) => { this.user.subscribed = !this.user.subscribed; @@ -64,7 +64,7 @@ export default { this.api.request(sAction, {'email': this.user.email, 'password': this.password, 'name': this.user.name}, 'POST') .then((asResponse) => { - this.feedbacks.push({type: asResponse.result, msg: asResponse.desc}); + this.feedbacks.push({type: asResponse.result, msg: asResponse.desc_lang_text}); this.user.setInfo(asResponse.data); this.passwordMode = false; this.settingPassword = false; @@ -72,7 +72,7 @@ export default { this.passwordConfirmation = ''; }) .catch((oError) => { - switch(oError.descKey) { + switch(oError.desc_lang_id) { case 'account.set_password': this.passwordMode = true; this.settingPassword = true; diff --git a/src/components/ProjectMediaLink.vue b/src/components/ProjectMediaLink.vue index a8950c1..a877057 100644 --- a/src/components/ProjectMediaLink.vue +++ b/src/components/ProjectMediaLink.vue @@ -72,7 +72,7 @@ export default { { const hasError = Object.prototype.hasOwnProperty.call(uploadedFile, 'error'); - this.logs.push(hasError ? uploadedFile.error : this.lang.get('upload.success', [uploadedFile.original_name || uploadedFile.name])); + if(hasError) this.addLog(uploadedFile.desc_lang_id, uploadedFile.desc_lang_params); + else this.addLog('upload.success', [uploadedFile.original_name || uploadedFile.name]); if(!hasError) this.files.push({...uploadedFile, content: ''}); }); }); this.uppy.on('upload-error', (file, error, response) => { - const message = response?.body?.error || error?.message || this.lang.get('upload.error'); - this.logs.push(message); + this.addLog(response?.body?.desc_lang_id || 'upload.error', response?.body?.desc_lang_params || []); }); this.uppy.on('complete', () => { @@ -90,29 +93,34 @@ export default { id: oFile.id, content: oFile.content }) - .then((asData) => {this.logs.push(this.lang.get('media.comment_update', asData.filename));}) - .catch((sMsgId) => {this.logs.push(this.lang.get(sMsgId));}); + .then((asData) => {this.addLog('media.comment_update', [asData.filename]);}) + .catch((oError) => {this.addLog(oError.desc_lang_id, oError.desc_lang_params);}); }, addPosition() { if(navigator.geolocation) { - this.logs.push('Determining position...'); + this.addLog('upload.position.determining'); navigator.geolocation.getCurrentPosition( (position) => { - this.logs.push('Sending position...'); + this.addLog('upload.position.sending'); this.api.post('add_position', { 'latitude': position.coords.latitude, 'longitude': position.coords.longitude, 'timestamp': Math.round(position.timestamp / 1000) }) - .then(() => {this.logs.push(this.lang.get('upload.success', [this.lang.get('upload.position.new')]));}) - .catch((sMsgId) => {this.logs.push(this.lang.get(sMsgId));}); + .then(() => {this.addLog('upload.position.success');}) + .catch((oError) => {this.addLog(oError.desc_lang_id, oError.desc_lang_params);}); }, (error) => { - this.logs.push(error.message); + const asErrorLangIds = { + 1: 'upload.position.permission_denied', + 2: 'upload.position.unavailable', + 3: 'upload.position.timeout' + }; + this.addLog(asErrorLangIds[error.code] || 'upload.position.error'); } ); } - else this.logs.push('This browser does not support geolocation'); + else this.addLog('upload.position.unsupported'); } } } @@ -144,7 +152,7 @@ export default {
-

{{ log }}.

+

{{ lang.get(log.lang_id, log.lang_params) }}

diff --git a/src/scripts/api.js b/src/scripts/api.js index 50aa86a..50b7aed 100644 --- a/src/scripts/api.js +++ b/src/scripts/api.js @@ -10,8 +10,8 @@ export default class Api { } async get(sAction, asParams = {}) { - const response = await this.request(sAction, asParams, 'GET'); - return response.data; + const oResponse = await this.request(sAction, asParams, 'GET'); + return oResponse.data; } async getAsset(sAssetPath) { @@ -25,8 +25,8 @@ export default class Api { } async post(sAction, asParams = {}) { - const response = await this.request(sAction, asParams, 'POST'); - return response.data; + const oResponse = await this.request(sAction, asParams, 'POST'); + return oResponse.data; } async request(sAction, asParams = {}, method = 'GET') { @@ -59,12 +59,13 @@ export default class Api { } const oResponse = await oRequest.json(); - oResponse.descKey = this.lang.getLangKey(oResponse.desc); - oResponse.desc = this.lang.parse(oResponse.desc); + oResponse.desc_lang_text = this.lang.get(oResponse.desc_lang_id, oResponse.desc_lang_params); if(oResponse.result == this.errorCode) { - const oError = new Error(oResponse.desc); - oError.descKey = oResponse.descKey; + const oError = new Error(oResponse.desc_lang_text); + oError.desc_lang_id = oResponse.desc_lang_id; + oError.desc_lang_params = oResponse.desc_lang_params; + oError.desc_lang_text = oResponse.desc_lang_text; throw oError; } diff --git a/src/scripts/lang.js b/src/scripts/lang.js index d08ea7c..03b2959 100644 --- a/src/scripts/lang.js +++ b/src/scripts/lang.js @@ -5,29 +5,20 @@ export default class Lang { this.prefix = prefix; } - get(key = '', params = []) { - if(key === '') return ''; + get(sLangId = '', params = []) { + if(sLangId === '') return ''; const normalizedParams = Array.isArray(params) ? params : [params]; - if(Object.prototype.hasOwnProperty.call(this.translations, key)) { - let text = this.translations[key]; + if(Object.prototype.hasOwnProperty.call(this.translations, sLangId)) { + let text = this.translations[sLangId]; normalizedParams.forEach((param, index) => { text = text.replace('$' + index, param); }); return text; } - console.warn('Missing translation:', key); - return key; - } - - parse(message = '') { - let sLangKey = this.getLangKey(message); - return (sLangKey != '')?this.get(sLangKey):message; - } - - getLangKey(message = '') { - return (this.prefix && typeof message === 'string' && message.startsWith(this.prefix))?message.slice(this.prefix.length):''; + console.warn('Missing translation:', sLangId); + return sLangId; } }