diff --git a/images/spot-logo-only.svg b/images/spot-logo-only.svg new file mode 100644 index 0000000..154056b --- /dev/null +++ b/images/spot-logo-only.svg @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/images/spot-logo.svg b/images/spot-logo.svg new file mode 100644 index 0000000..0bac0cf --- /dev/null +++ b/images/spot-logo.svg @@ -0,0 +1,48 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/inc/feed.php b/inc/feed.php index 4e328bf..9552dcc 100644 --- a/inc/feed.php +++ b/inc/feed.php @@ -5,45 +5,49 @@ * Also manages spots (devices) & messages */ class Feed extends PhpObject { - + //Spot feed const FEED_HOOK = 'https://api.findmespot.com/spot-main-web/consumer/rest-api/2.0/public/feed/'; const FEED_TYPE_XML = '/message.xml'; const FEED_TYPE_JSON = '/message.json'; const FEED_MAX_REFRESH = 5 * 60; //Seconds - + //DB Tables const SPOT_TABLE = 'spots'; const FEED_TABLE = 'feeds'; const MSG_TABLE = 'messages'; - + /** * Database Handle * @var Db */ private $oDb; - + private $iFeedId; private $sRefFeedId; private $iLastUpdate; - + public function __construct(Db &$oDb, $iFeedId=0) { parent::__construct(__CLASS__, Settings::DEBUG); $this->oDb = &$oDb; if($iFeedId > 0) $this->setFeedId($iFeedId); } - + public function getFeedId() { return $this->iFeedId; } - + + public function getLastUpdate(): int { + return $this->iLastUpdate; + } + public function setFeedId($iFeedId) { $this->iFeedId = $iFeedId; $asFeed = $this->getFeed(); $this->sRefFeedId = $asFeed['ref_feed_id']; $this->iLastUpdate = strtotime($asFeed['last_update']); } - + public function createFeedId($oProjectId) { $this->setFeedId($this->oDb->insertRow(self::FEED_TABLE, array( Db::getId(Project::PROJ_TABLE) => $oProjectId, @@ -51,39 +55,39 @@ class Feed extends PhpObject { ))); return $this->getFeedId(); } - + public function setRefFeedId($sRefFeedId) { return $this->updateField('ref_feed_id', $sRefFeedId); } - + public function setSpotId($iSpotId) { return $this->updateField(Db::getId(self::SPOT_TABLE), $iSpotId); } - + public function setProjectId($iProjectId) { return $this->updateField(Db::getId(Project::PROJ_TABLE), $iProjectId); } - + public function getSpots() { $asSpots = $this->oDb->selectRows(array('from'=>self::SPOT_TABLE)); foreach($asSpots as &$asSpot) $asSpot['id'] = $asSpot[Db::getId(self::SPOT_TABLE)]; return $asSpots; } - + public function getFeeds($iFeedId=0) { $asInfo = array('from'=>self::FEED_TABLE); if($iFeedId > 0) $asInfo['constraint'] = array(Db::getId(self::FEED_TABLE)=>$iFeedId); $asFeeds = $this->oDb->selectRows($asInfo); - + foreach($asFeeds as &$asFeed) $asFeed['id'] = $asFeed[Db::getId(self::FEED_TABLE)]; return $asFeeds; } - + public function getFeed() { $asFeeds = $this->getFeeds($this->getFeedId()); return array_shift($asFeeds); } - + public function getMessages($asActivePeriod = array()) { $asInfo = array( 'select' => array('id_message', 'ref_msg_id', 'type', 'latitude', 'longitude', 'site_time', 'unix_time'), @@ -92,7 +96,7 @@ class Feed extends PhpObject { 'constOpe' => array(Db::getId(self::FEED_TABLE) => "="), 'orderBy' => array('site_time'=>'ASC') ); - + if(!empty($asActivePeriod)) { $asInfo['constraint']['site_time'] = $asActivePeriod; $asInfo['constOpe']['site_time'] = "BETWEEN"; @@ -100,22 +104,22 @@ class Feed extends PhpObject { return $this->oDb->selectRows($asInfo); } - + public function checkUpdateFeed($sProjectMode) { $bNewMsg = false; - + //Spam Check: no more than 1 API request per 5 minutes if($sProjectMode == Project::MODE_BLOG) { $oLastUpdate = new DateTime('@'.$this->iLastUpdate); $oNow = new DateTime('now'); $iSecDiff = $oNow->getTimestamp() - $oLastUpdate->getTimestamp(); - + if($iSecDiff > self::FEED_MAX_REFRESH) $bNewMsg = $this->updateFeed(); } - + return $bNewMsg; } - + private function updateFeed() { $bNewMsg = false; $asData = $this->retrieveFeed(); @@ -123,14 +127,14 @@ class Feed extends PhpObject { if(!isset($asData['response']['errors']) || empty($asData['response']['errors'])) { $asMsgs = $asData['response']['feedMessageResponse']['messages']; $asFeed = $asData['response']['feedMessageResponse']['feed']; - + //Fix unstable Spot API Structure if(array_key_exists('message', $asMsgs)) $asMsgs = $asMsgs['message']; //Sometimes adds an extra "message" level if(!array_key_exists(0, $asMsgs)) $asMsgs = array($asMsgs); //Jumps a level when there is only 1 message - + //Update Spot, Feed & Messages if(!empty($asMsgs) && array_key_exists('messengerId', $asMsgs[0])) { - + //Update Spot Info from the first message $asSpotInfo = array( 'ref_spot_id' => $asMsgs[0]['messengerId'], @@ -138,7 +142,7 @@ class Feed extends PhpObject { 'model' => $asMsgs[0]['modelId'] ); $iSpotId = $this->oDb->insertUpdateRow(self::SPOT_TABLE, $asSpotInfo, array('ref_spot_id')); - + //Update Feed Info and last update date $asFeedInfo = array( 'ref_feed_id' => $asFeed['id'], @@ -149,7 +153,7 @@ class Feed extends PhpObject { 'last_update' => $sLastUpdate ); $iFeedId = $this->oDb->insertUpdateRow(self::FEED_TABLE, $asFeedInfo, array('ref_feed_id')); - + //Update Messages foreach($asMsgs as $asMsg) { $asMsg = array( @@ -164,7 +168,7 @@ class Feed extends PhpObject { 'content' => $asMsg['messageContent'], 'battery_state' => $asMsg['batteryState'] ); - + $iMsgId = $this->oDb->selectId(self::MSG_TABLE, array('ref_msg_id'=>$asMsg['ref_msg_id'])); if(!$iMsgId) { $this->oDb->insertRow(self::MSG_TABLE, $asMsg); @@ -175,10 +179,10 @@ class Feed extends PhpObject { } } else $this->oDb->updateRow(self::FEED_TABLE, $this->getFeedId(), array('last_update'=>$sLastUpdate)); - + return $bNewMsg; } - + private function retrieveFeed() { $sContent = '[]'; if($this->sRefFeedId !='') { @@ -187,14 +191,14 @@ class Feed extends PhpObject { } return json_decode($sContent, true); } - + private function updateField($sField, $oValue) { $bResult = ($this->oDb->updateRow(self::FEED_TABLE, $this->getFeedId(), array($sField=>$oValue)) > 0); $this->setFeedId($this->getFeedId()); - + return $bResult; } - + public function delete() { $sDesc = ''; if($this->getFeedId() > 0) { @@ -202,7 +206,7 @@ class Feed extends PhpObject { if(!$bSuccess) $sDesc = $this->oDb->getLastError(); } else $sDesc = 'Error while setting project: no Feed ID'; - + return $sDesc; } -} \ No newline at end of file +} diff --git a/inc/project.php b/inc/project.php index 60771bb..c37e7e7 100644 --- a/inc/project.php +++ b/inc/project.php @@ -1,39 +1,39 @@ self::MODE_PREVIZ, 'blog'=>self::MODE_BLOG, 'histo'=>self::MODE_HISTO); - + //DB Tables const PROJ_TABLE = 'projects'; - + /** * Database Handle * @var Db */ private $oDb; - + private $iProjectId; private $sMode; private $sName; private $sCodeName; private $asActive; private $asGeo; - + public function __construct(Db &$oDb, $iProjectId=0) { parent::__construct(__CLASS__, Settings::DEBUG); $this->oDb = &$oDb; if($iProjectId > 0) $this->setProjectId($iProjectId); } - + public function getProjectId() { return $this->iProjectId; } - + public function setProjectId($iProjectId=0) { if($iProjectId > 0) { $this->iProjectId = $iProjectId; @@ -46,7 +46,7 @@ class Project extends PhpObject { * Selected Project [-------Project 1-------][------------Project 2-------------][---------------Project 3------------------ * Mode --P--][--------B--------][--P--][-----------B---------------][---P---][-----B-----][---------H---------- */ - $sQuery = + $sQuery = "SELECT MAX(id_project) ". "FROM projects ". "WHERE active_to = (". @@ -61,39 +61,39 @@ class Project extends PhpObject { $asResult = $this->oDb->getArrayQuery($sQuery, true); $this->iProjectId = array_shift($asResult); } - + $this->setProjectInfo(); } - + public function createProjectId() { $this->setProjectId($this->oDb->insertRow(self::PROJ_TABLE, array('timezone'=>Settings::TIMEZONE))); return $this->getProjectId(); } - + public function getMode() { return $this->sMode; } - + public function getProjectName() { return $this->sName; } - + public function setProjectName($sName) { return $this->updateField('name', $sName); } - + public function getProjectCodeName() { return $this->sCodeName; } - + public function setProjectCodeName($sCodeName) { return $this->updateField('codename', $sCodeName); } - + public function getActivePeriod($sFromTo='') { return ($sFromTo=='')?$this->asActive:$this->asActive[$sFromTo]; } - + public function setActivePeriod($oValue, $sFromTo='') { if($sFromTo=='') { $this->updateField('active_from', $oValue['from']); @@ -103,15 +103,15 @@ class Project extends PhpObject { return $this->updateField('active_'.$sFromTo, $oValue); } } - + public function getTimeZone() { return $this->asGeo['timezone']; } - + public function setTimeZone($sTimeZone) { return $this->updateField('timezone', $sTimeZone); } - + public function getFeedIds() { return $this->oDb->selectColumn( Feed::FEED_TABLE, @@ -119,7 +119,7 @@ class Project extends PhpObject { array(Db::getId(self::PROJ_TABLE) => $this->getProjectId()) ); } - + public function getProjects($iProjectId=0) { $bSpecificProj = ($iProjectId > 0); $asInfo = array( @@ -135,7 +135,7 @@ class Project extends PhpObject { 'from' => self::PROJ_TABLE ); if($bSpecificProj) $asInfo['constraint'] = array(Db::getId(self::PROJ_TABLE)=>$iProjectId); - + $asProjects = $this->oDb->selectRows($asInfo, 'codename'); foreach($asProjects as $sCodeName=>&$asProject) { switch($asProject['mode']) { @@ -143,20 +143,31 @@ class Project extends PhpObject { case 1: $asProject['mode'] = self::MODE_BLOG; break; case 2: $asProject['mode'] = self::MODE_HISTO; break; } - + if($sCodeName!= '' && !Converter::isGeoJsonValid($sCodeName)) Converter::convertToGeoJson($sCodeName); - + $asProject['geofilepath'] = Spot::addTimestampToFilePath(Geo::getFilePath($sCodeName, GeoJson::EXT)); $asProject['gpxfilepath'] = Spot::addTimestampToFilePath(Geo::getFilePath($sCodeName, Gpx::EXT)); $asProject['codename'] = $sCodeName; } return $bSpecificProj?$asProject:$asProjects; } - + public function getProject() { return $this->getProjects($this->getProjectId()); } - + + public function getLastUpdate(): int { + $iLastUpdate = INF; + + $asFeedIds = $this->getFeedIds(); + foreach($asFeedIds as $iFeedId) { + $iLastUpdate = min($iLastUpdate, (new Feed($this->oDb, $iFeedId))->getLastUpdate()); + } + + return $iLastUpdate; + } + private function setProjectInfo() { if($this->getProjectId() > 0) { $asProject = $this->getProject(); @@ -169,14 +180,14 @@ class Project extends PhpObject { } else $this->addError('Error while setting project: no project ID'); } - + private function updateField($sField, $oValue) { $bResult = ($this->oDb->updateRow(self::PROJ_TABLE, $this->getProjectId(), array($sField=>$oValue)) > 0); $this->setProjectInfo(); - + return $bResult; } - + public function delete() { $sDesc = ''; if($this->getProjectId() > 0) { @@ -184,7 +195,7 @@ class Project extends PhpObject { if(!$bSuccess) $sDesc = $this->oDb->getLastError(); } else $sDesc = 'Error while setting project: no project ID'; - + return $sDesc; } -} \ No newline at end of file +} diff --git a/inc/spot.php b/inc/spot.php index b2ab2db..c098c4d 100755 --- a/inc/spot.php +++ b/inc/spot.php @@ -3,8 +3,8 @@ /* Timezones * - Feeds (table `feeds`): * - last_update: timestamp in site time (see Settings::TIMEZONE) - * - Spot Messages (table `messages`): - * - unix_time: UNIX (int) in UTC + * - Spot Messages (table `messages`): + * - unix_time: UNIX (int) in UTC * - site_time: timestamp in site time (see Settings::TIMEZONE) * - iso_time: raw ISO 8601 in local timezone * - Medias (table `medias`): @@ -18,30 +18,30 @@ class Spot extends Main { //Database const POST_TABLE = 'posts'; - + const FEED_CHUNK_SIZE = 15; const MAIL_CHUNK_SIZE = 5; - + const DEFAULT_LANG = 'en'; - + /** * Active Project * @var Project */ private $oProject; - + /** * Media Class * @var Media */ private $oMedia; - + /** * User * @var User */ private $oUser; - + public function __construct($oClassManagement, $sProcessPage, $sTimezone) { $asClasses = array( @@ -53,22 +53,22 @@ class Spot extends Main array('name'=>'email', 'project'=>true) ); parent::__construct($oClassManagement, $sProcessPage, $asClasses, true, __FILE__, $sTimezone); - + $this->oUser = new User($this->oDb); - + $this->oClassManagement->incClass('translator'); $this->oLang = new Translator('', self::DEFAULT_LANG); - + $this->oProject = new Project($this->oDb); $this->oMedia = new Media($this->oDb, $this->oProject); } - + protected function install() { //Install DB $this->oDb->install(); } - + protected function getSqlOptions() { return array @@ -129,7 +129,7 @@ class Spot extends Main ) ); } - + public function getMainPage($asGlobalVars = array(), $sMainPage = 'index', $asMainPageTags=array()) { return parent::getMainPage( @@ -146,7 +146,7 @@ class Spot extends Main 'default_timezone' => Settings::TIMEZONE ) ), - 'index', + $sMainPage, array( 'host_url' => $this->asContext['serv_name'], 'filepath_css' => self::addTimestampToFilePath('style/spot.css'), @@ -159,35 +159,35 @@ class Spot extends Main ) ); } - + /* Managing projects */ - + public function setProjectId($iProjectId=0) { $this->oProject->setProjectId($iProjectId); } - + public function updateProject() { $bNewMsg = false; - + //Update all feeds belonging to the project $asFeeds = $this->oProject->getFeedIds(); foreach($asFeeds as $iFeedId) { $oFeed = new Feed($this->oDb, $iFeedId); $bNewMsg = $bNewMsg || $oFeed->checkUpdateFeed($this->oProject->getMode()); } - + //Send Update Email if($bNewMsg) { $oEmail = new Email($this->asContext['serv_name'], 'email_update'); $oEmail->setDestInfo($this->oUser->getActiveUsersInfo()); - + //Add Position $asMessages = $this->getSpotMessages(); $asLastMessage = end($asMessages); $asLastMessage['token'] = Settings::GEO_SERVER_TOKEN; $oEmail->oTemplate->setTags($asLastMessage); $oEmail->oTemplate->setTag('date_time', 'time:'.$asLastMessage['unix_time'], 'd/m/Y, H:i'); - + //Add latest news feed $asNews = $this->getNewsFeed(0, true); $iPostCount = 0; @@ -206,11 +206,11 @@ class Spot extends Main } if($iPostCount == self::MAIL_CHUNK_SIZE) break; } - + $oEmail->send(); } } - + public function genCronFile() { //$bSuccess = (file_put_contents('spot_cron.sh', '#!/bin/bash'."\n".'cd '.dirname($_SERVER['SCRIPT_FILENAME'])."\n".'php -f index.php a=update_feed')!==false); $sFileName = 'spot_cron.sh'; @@ -221,17 +221,17 @@ class Spot extends Main $bSuccess = (file_put_contents($sFileName, $sContent)!==false); return self::getJsonResult($bSuccess, ''); } - + public function getMarkers() { $asMessages = $this->getSpotMessages(); $bSuccess = !empty($this->getMedias('posted_on') + $asMessages + $this->getPosts()); $sDesc = $bSuccess?'':self::NO_DATA; - + //Add medias if($bSuccess) { $asMedias = $this->getMedias('taken_on'); - + //Assign medias to closest message $iIndex = 0; $iMaxIndex = count($asMessages) - 1; @@ -245,47 +245,51 @@ class Spot extends Main $iHalfWayPoint = ($asMessages[$iIndex - 1]['unix_time'] + $asMessages[$iIndex]['unix_time'])/2; $iMsgIndex = ($asMedia['unix_time'] >= $iHalfWayPoint)?$iIndex:($iIndex - 1); } - + $asMessages[$iMsgIndex]['medias'][] = $asMedia; } } - - return self::getJsonResult($bSuccess, $sDesc, $asMessages); + + //Add Project Last Update + $asLastUpdate = array(); + $this->addTimeStamp($asLastUpdate, $this->oProject->getLastUpdate()); + + return self::getJsonResult($bSuccess, $sDesc, array('messages' => $asMessages, 'last_update'=>$asLastUpdate)); } - + public function subscribe($sEmail) { $asResult = $this->oUser->addUser($sEmail, $this->oLang->getLanguage(), date_default_timezone_get()); $asUserInfo = $this->oUser->getUserInfo(); - + //Send Confirmation Email if($asResult['result'] && $asResult['desc']=='lang:nl_subscribed') { $oConfEmail = new Email($this->asContext['serv_name'], 'email_conf'); $oConfEmail->setDestInfo($asUserInfo); $oConfEmail->send(); } - + return self::getJsonResult($asResult['result'], $asResult['desc'], $asUserInfo); } - + public function unsubscribe() { $asResult = $this->oUser->removeUser(); return self::getJsonResult($asResult['result'], $asResult['desc'], $asResult['data']); } - + public function unsubscribeFromEmail($iUserId) { $this->oUser->setUserId($iUserId); $this->oLang->setLanguage($this->oUser->getLang(), self::DEFAULT_LANG); $asResult = $this->oUser->removeUser(); - + $sDesc = $asResult['desc']; if($sDesc=='') $sDesc = $this->oLang->getTranslation('nl_unsubscribed'); return $sDesc; } - + private function getSpotMessages() { $asMessages = array(); - + //Get messages from all feeds belonging to the project $asFeeds = $this->oProject->getFeedIds(); foreach($asFeeds as $iFeedId) { @@ -297,18 +301,21 @@ class Spot extends Main $asMessage['longitude'] = floatval($asMessage['longitude']); $asMessage['lat_dms'] = self::decToDms($asMessage['latitude'], 'lat'); $asMessage['lon_dms'] = self::decToDms($asMessage['longitude'], 'lon'); - + $this->addTimeStamp($asMessage, $asMessage['unix_time']); } } + + //Sort chronologically usort($asMessages, function($a, $b){return $a['unix_time'] > $b['unix_time'];}); - + + //Add Display ID $asSortedMessages = array_values($asMessages); foreach($asSortedMessages as $iIndex=>&$asSortedMessage) $asSortedMessage['displayed_id'] = $iIndex + 1; - + return $asSortedMessages; } - + /** * Get valid medias based on $sTimeRefField: * - taken_on: Date/time on which the media was taken @@ -325,20 +332,20 @@ class Spot extends Main if($sTimeRef >= $this->oProject->getActivePeriod('from') && $sTimeRef <= $this->oProject->getActivePeriod('to')) { $asMedia['taken_on_formatted'] = $this->getTimeFormat(strtotime($asMedia['taken_on'])); $asMedia['posted_on_formatted'] = $this->getTimeFormat(strtotime($asMedia['posted_on'])); - + $this->addTimeStamp($asMedia, strtotime($sTimeRef)); $asValidMedias[] = $asMedia; } } - + usort($asValidMedias, function($a, $b){return $a['unix_time'] > $b['unix_time'];}); - + $asSortedMedias = array_values($asValidMedias); foreach($asSortedMedias as $iIndex=>&$asSortedMedia) $asSortedMedia['displayed_id'] = $iIndex + 1; - + return $asSortedMedias; } - + private function getPosts() { $asInfo = array( @@ -353,23 +360,23 @@ class Spot extends Main $asPosts = $this->oDb->selectRows($asInfo); foreach($asPosts as &$asPost) { - $iUnixTimeStamp = strtotime($asPost['site_time']); //assumes site timezone (Settings::TIMEZONE) + $iUnixTimeStamp = strtotime($asPost['site_time']); //assumes site timezone (Settings::TIMEZONE) $asPost['formatted_name'] = Toolbox::mb_ucwords($asPost['name']); unset($asPost[Db::getId(User::USER_TABLE)]); - + $this->addTimeStamp($asPost, $iUnixTimeStamp); } usort($asPosts, function($a, $b){return $a['unix_time'] > $b['unix_time'];}); - + return $asPosts; } - + private function addTimeStamp(&$asData, $iTime) { $asData['unix_time'] = (int) $iTime; $asData['relative_time'] = Toolbox::getDateTimeDesc($iTime, $this->oLang->getLanguage()); $asData['formatted_time'] = $this->getTimeFormat($iTime); } - + public function getNewsFeed($iChunk=0, $bInternal=false) { $bHistoMode = ($this->oProject->getMode() == Project::MODE_HISTO); @@ -395,7 +402,7 @@ class Spot extends Main foreach($asFeedTypeInfo['feed'] as $asFeed) { $sPriority = $asFeedTypeInfo['priority']; if($bHistoMode) $sPriority = count($asFeedTypes) - 1 - $asFeedTypeInfo['priority']; - + $iTableId = $asFeed[Db::getId($asFeedTypeInfo['table'])]; $iId = ($asFeed['unix_time'] * -1).'.'.$sPriority.$iTableId; $asFeeds[$iId] = $asFeed; @@ -403,52 +410,52 @@ class Spot extends Main $asFeeds[$iId]['id'] = $iTableId; } } - + //Sort by key if($bHistoMode) krsort($asFeeds); else ksort($asFeeds); - + //Split chunks - $asFeeds = array_slice($asFeeds, $iChunk * self::FEED_CHUNK_SIZE, self::FEED_CHUNK_SIZE); - + $asFeeds = array_slice($asFeeds, $iChunk * self::FEED_CHUNK_SIZE, self::FEED_CHUNK_SIZE); + return $bInternal?$asFeeds:self::getJsonResult(true, '', $asFeeds); } - + public function syncMedias() { return $this->oMedia->syncFileFolder(); } - + public function addPost($sName, $sPost) { $asData = array( Db::getId(Project::PROJ_TABLE) => $this->oProject->getProjectId(), - 'name' => mb_strtolower(trim($sName)), + 'name' => mb_strtolower(trim($sName)), 'content' => trim($sPost), 'site_time' => date(Db::TIMESTAMP_FORMAT) //site time (Settings::TIMEZONE) ); if($this->oUser->getUserId() > 0) $asData[Db::getId(User::USER_TABLE)] = $this->oUser->getUserId(); - + $iPostId = $this->oDb->insertRow(self::POST_TABLE, $asData); - + $this->oUser->updateNickname($sName); - + return self::getJsonResult(($iPostId > 0), ''); } - + public function upload() { $this->oClassManagement->incClass('uploader', true); $oUploader = new Uploader($this->oMedia, $this->oLang); - + return $oUploader->sBody; } - + 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']); } - + public function getAdminSettings() { $oFeed = new Feed($this->oDb); $asData = array( @@ -458,12 +465,12 @@ class Spot extends Main ); return self::getJsonResult(true, '', $asData); } - + public function setAdminSettings($sType, $iId, $sField, $sValue) { $bSuccess = false; $sDesc = ''; $asResult = array(); - + switch($sType) { case 'project': $oProject = new Project($this->oDb, $iId); @@ -504,14 +511,14 @@ class Spot extends Main break; } if(!$bSuccess) $sDesc = Mask::LANG_PREFIX.'error_commit_db'; - + return self::getJsonResult($bSuccess, $sDesc, array($sType=>array($asResult))); } - + public function delAdminSettings($sType, $iId) { $bSuccess = false; $sDesc = ''; - + switch($sType) { case 'project': $oProject = new Project($this->oDb, $iId); @@ -523,17 +530,17 @@ class Spot extends Main break; } $bSuccess = ($sDesc==''); - + return self::getJsonResult($bSuccess, $sDesc, array($sType=>array(array('id'=>$iId, 'del'=>$bSuccess)))); } - + public function createProject() { $oProject = new Project($this->oDb); $iNewProjectId = $oProject->createProjectId(); - + $oFeed = new Feed($this->oDb); $oFeed->createFeedId($iNewProjectId); - + return self::getJsonResult($iNewProjectId>0, '', array( 'project' => array($oProject->getProject()), 'feed' => array($oFeed->getFeed()) @@ -543,30 +550,30 @@ class Spot extends Main public function convertGpxToGeojson($sGeoFileName) { return Converter::convertToGeoJson($sGeoFileName); } - + public static function decToDms($dValue, $sType) { if($sType=='lat') $sDirection = ($dValue >= 0)?'N':'S'; //Latitude else $sDirection = ($dValue >= 0)?'E':'W'; //Longitude - + $dLeft = abs($dValue); - + //Degrees $iDegree = floor($dLeft); $dLeft -= $iDegree; - + //Minutes $iMinute = floor($dLeft * 60); $dLeft -= $iMinute / 60; - + //Seconds $fSecond = round($dLeft * 3600, 1); - + return $iDegree.'°'.$iMinute."'".$fSecond.'"'.$sDirection; } - + public function getTimeFormat($iTime) { $sDate = date('d/m/Y', $iTime); $sTime = date('H:i', $iTime); return $this->oLang->getTranslation('date_time', array($sDate, $sTime)); } -} \ No newline at end of file +} diff --git a/index.php b/index.php index fc8a5de..5bbab2a 100755 --- a/index.php +++ b/index.php @@ -94,5 +94,3 @@ $sDebug = ob_get_clean(); if(Settings::DEBUG && $sDebug!='') $oSpot->addUncaughtError($sDebug); echo $sResult; - -?> \ No newline at end of file diff --git a/languages/en.lang b/languages/en.lang index 98156b1..75d5607 100644 --- a/languages/en.lang +++ b/languages/en.lang @@ -28,6 +28,7 @@ map_ign_france = IGN (France) map_ign_spain = IGN (Spain) map_linz = LINZ (New Zealand) map_usgs = USGS (USA) +map_natgeo = National Geographic (USA) pic = Picture pics = Pictures @@ -56,7 +57,7 @@ ref_feed_id = Ref. Feed ID spot_id = Spot ID name = Name status = Status -last_update = Last Update +last_update = Last Spot update ref_spot_id = Ref. Spot ID model = Model delete = Delete @@ -105,4 +106,4 @@ legend = Legend credits_project = Spotty Project credits_git = Git Repository -credits_license = under GPLv3 license \ No newline at end of file +credits_license = under GPLv3 license diff --git a/languages/fr.lang b/languages/fr.lang index 3644b7e..87bf6cc 100644 --- a/languages/fr.lang +++ b/languages/fr.lang @@ -28,6 +28,7 @@ map_ign_france = IGN (France) map_ign_spain = IGN (Espagne) map_linz = LINZ (Nouvelle-Zélande) map_usgs = USGS (États-Unis) +map_natgeo = National Geographic (États-Unis) pic = Photo pics = Photos @@ -56,7 +57,7 @@ ref_feed_id = ID Feed ref. spot_id = ID Spot name = Description status = Statut -last_update = Dernière maj +last_update = Dernière maj depuis Spot ref_spot_id = ID Spot ref. model = Modèle delete = Supprimer @@ -105,4 +106,4 @@ legend = Légende credits_project = Projet Spotty credits_git = Dépôt Git -credits_license = sous licence GPLv3 \ No newline at end of file +credits_license = sous licence GPLv3 diff --git a/masks/project.html b/masks/project.html index e66510c..6167d9e 100644 --- a/masks/project.html +++ b/masks/project.html @@ -5,7 +5,10 @@
-
+
+ +

+
@@ -66,7 +69,7 @@ oSpot.pageInit = function(asHash) { oSpot.onResize = function() { self.tmp('map_offset', -1 * (isFeedPanelOpen()?self.tmp('$Feed').outerWidth(true):0) / $('body').outerWidth(true)); - + if(typeof self.tmp('elev') != 'undefined') { var bElevOpened = self.tmp('elev')._showState; if(bElevOpened) self.tmp('elev')._expand(); @@ -95,7 +98,7 @@ function toggleFeedPanel(bShow, sMapAction) { self.tmp('$Projects').toggleClass('with-feed', (typeof bShow === 'undefined')?null:bShow); oSpot.onResize(); if(isMobile()) $('#settings-button').toggle(!isFeedPanelOpen()); - + sMapAction = sMapAction || 'panTo'; switch(sMapAction) { case 'none': break; @@ -113,7 +116,7 @@ function toggleSettingsPanel(bShow) { self.tmp('$Projects').toggleClass('with-settings', (typeof bShow === 'undefined')?null:bShow); oSpot.onResize(); if(isMobile()) $('#post-button').toggle(!isSettingsPanelOpen()); - + oSpot.tmp('map').panBy([(isSettingsPanelOpen()?-1:1)*self.tmp('$Settings').outerWidth(true)/2, 0], {duration: 0.5}); } @@ -121,6 +124,13 @@ function isSettingsPanelOpen() { return self.tmp('$Projects').hasClass('with-settings'); } +function updateSettingsPanel(asLastUpdate) { + var $LastUpdate = self.tmp('$Settings').find('#last_update').toggle(self.vars(['project', 'mode']) == self.consts.modes.blog); + $LastUpdate.find('abbr') + .attr('title', asLastUpdate.formatted_time) + .text(oSpot.lang('last_update')+' '+asLastUpdate.relative_time); +} + function isMobile() { return $('#mobile').is(':visible'); } @@ -139,9 +149,9 @@ function initPage(asHash) { self.tmp('markers', 'object'); self.tmp('trail-markers', 'object'); self.tmp('marker_size', {width: 32, height: 32}); - + if(!isMobile()) toggleFeedPanel(true, 'none'); - + //Lightbox options lightbox.option({ alwaysShowNavOnTouchDevices: true, @@ -152,7 +162,7 @@ function initPage(asHash) { resizeDuration: 600, hasVideo: true }); - + //Assign Track Type Colors self.tmp('track-type-styles', 'object'); $('#legend').find('.line').each(function(iKey, oLegend){ @@ -160,7 +170,7 @@ function initPage(asHash) { var sTrackType = $Legend.attr('class').replace('line', '').trim(); self.tmp(['track-type-styles', sTrackType], {weight: parseInt($Legend.css('height')), color: $Legend.css('background-color'), opacity: 1}); }); - + //Post Panel one-off init (see initPosts for project related init) //Scrollbar self.tmp('simple-bar', new SimpleBar($('#posts')[0])); @@ -173,10 +183,10 @@ function initPage(asHash) { $("#feed").onSwipe(function(aiDelta){ if(aiDelta.x > self.tmp('$Feed').outerWidth(true)/3 && aiDelta.x > Math.abs(aiDelta.y)) toggleFeedPanel(false); }); - + //Feed Panel initPosts(); - + //Settings Panel initSettings(); @@ -189,19 +199,23 @@ function initPage(asHash) { function initProject(sProjectCodeName, oFocusPost){ self.tmp('first_exec', false); self.vars('project', self.vars(['projects', sProjectCodeName])); - + //Page Title self.setPageTitle(oSpot.vars(['project', 'name'])); - + //Timezone difference notice - var bSameTime = ((new Date()).toLocaleString([], {timeZone: oSpot.consts.timezone}) == (new Date()).toLocaleString([], {timeZone: oSpot.vars(['project', 'timezone'])})); + var bSameTime = ( + (new Date()).toLocaleString([], {timeZone: oSpot.consts.timezone}) == + (new Date()).toLocaleString([], {timeZone: oSpot.vars(['project', 'timezone'])}) + ); self.tmp('site_tz_notice', bSameTime?'':getTimeZoneDesc(oSpot.consts.timezone)); self.tmp('proj_tz_notice', bSameTime?'':getTimeZoneDesc(self.vars(['project', 'timezone']))); - + + //Load Track & Markers $.when( //Markers: Spot Messages & Medias self.get( - 'markers', + 'markers', function(){}, {project_id: self.vars(['project', 'id'])}, function(e){console.log(e);} @@ -213,13 +227,14 @@ function initProject(sProjectCodeName, oFocusPost){ mimeType: 'application/json' }) ).done(function(aoMessages, aoTracks) { - initSpotMessages(aoMessages[0]['data'] || [], aoTracks[0], aoMessages[0]['desc']=='No Data'); + initSpotMessages(aoMessages[0]['data']['messages'] || [], aoTracks[0], aoMessages[0]['desc']=='No Data'); + updateSettingsPanel(aoMessages[0]['data']['last_update']); }); - + //Show/Hide Poster Panel var bHistoMode = (self.vars(['project', 'mode']) == self.consts.modes.histo); $('#poster').toggle(!bHistoMode); - + //Feed auto-update updateFeed(true, false, function(){focusOnPost(oFocusPost);}); self.tmp('simple-bar').getScrollElement().scrollTop = 0; @@ -237,7 +252,7 @@ function initPosts() { //Auto-adjust text area height autosize($('#post')); - + //Add post Event handling $('#submit').click(function(){ if($('#poster').checkForm()) @@ -262,7 +277,7 @@ function initPosts() { function initSettings(){ //Scrollbar new SimpleBar($('#settings-sections-scrollbox')[0]); - + //Feedback display function var settingsFeedback = function(sType, sMsg){ $('

', {'class': sType}) @@ -273,7 +288,7 @@ function initSettings(){ .delay(5000) .slideUp('fast', function(){$(this).remove();}); }; - + //Newsletter Subscription $('#nl_btn').click(function(){ var sAction = $(this).prop('name'); @@ -282,7 +297,7 @@ function initSettings(){ if(!regexEmail.test(sEmail)) settingsFeedback('error', oSpot.lang('nl_invalid_email')); else { oSpot.get( - sAction, + sAction, function(asData, sDesc) { settingsFeedback('success', sDesc); oSpot.vars('user', asData); @@ -299,7 +314,7 @@ function initSettings(){ ); } }); - + //Twink interface with user data setUserInterface(); } @@ -310,7 +325,7 @@ function setUserInterface() { $('#email').val(asUserInfo.email).prop('disabled', true); $('#nl_btn').attr({name: 'unsubscribe', title: oSpot.lang('nl_unsubscribe'), 'class':'unsubscribe'}); $('#nl_desc').text(oSpot.lang('nl_subscribed_desc')); - + //Populate nickname if(asUserInfo.name) $('#name').val(asUserInfo.name); } @@ -333,12 +348,12 @@ function getElevWidth() { iSettingsPanelWidth = isSettingsPanelOpen()?self.tmp('$Settings').outerWidth(true):0, iLegendWidth = $('.leaflet-bottom.leaflet-left > .leaflet-control-layers').outerWidth(true), oElevRightMarging = parseInt($('.leaflet-bottom.leaflet-right > .leaflet-control-scale').css('margin-right').slice(0, -2)); - + return iPageWidth - iFeedPanelWidth - iSettingsPanelWidth - iLegendWidth - oElevRightMarging; } function initSpotMessages(aoMessages, aoTracks, bNoFeed) { - + //Tile layers aoLayers = {}; aoLayers[oSpot.lang('map_satellite')] = L.tileLayer(self.tmp('tile_api'), {id: 'mapbox.satellite-streets', minZoom: 0, maxZoom: 19}); @@ -347,7 +362,8 @@ function initSpotMessages(aoMessages, aoTracks, bNoFeed) { aoLayers[oSpot.lang('map_ign_spain')] = L.tileLayer(self.tmp('tile_api'), {id: 'ign.es', minZoom: 1, maxZoom: 20}); aoLayers[oSpot.lang('map_linz')] = L.tileLayer(self.tmp('tile_api'), {id: 'linz', maxZoom: 17, continuousWorld: true, attribution: 'Sourced from LINZ. CC BY 4.0'}); aoLayers[oSpot.lang('map_usgs')] = L.tileLayer(self.tmp('tile_api'), {id: 'usgs', minZoom: 1, maxZoom: 16}); - + //aoLayers[oSpot.lang('map_natgeo')] = L.tileLayer(self.tmp('tile_api'), {id: 'natgeo.pct', minZoom: 5, maxZoom: 14}); + //Map var oMap = L.map(self.tmp('$Map')[0], { layers: [aoLayers[oSpot.lang('map_satellite')]], @@ -355,26 +371,26 @@ function initSpotMessages(aoMessages, aoTracks, bNoFeed) { zoomControl: false }); self.tmp('map', oMap); - + //Controls: Settings Panel var oSettingsPanel = L.control({position: 'topleft'}); var $SettingsButton = $('#settings-button').clone(); $SettingsButton.click(toggleSettingsPanel); oSettingsPanel.onAdd = function(oMap) {return $SettingsButton[0];}; oSettingsPanel.addTo(oMap); - + //Controls: Feed Panel var oFeedPanel = L.control({position: 'topright'}); var $PostButton = $('#post-button').clone(); $PostButton.click(toggleFeedPanel); oFeedPanel.onAdd = function(oMap) {return $PostButton[0];}; oFeedPanel.addTo(oMap); - + //Controls: Legend var oLegend = L.control({position: 'bottomleft'}); oLegend.onAdd = function(oMap) {return $('#legend').clone()[0];}; oLegend.addTo(oMap); - + //Controls: Projects var $Labels = $('

', {'class': 'leaflet-control-layers-base'}); $.each(self.vars('projects'), function(sCodeName, asProject){ @@ -384,7 +400,7 @@ function initSpotMessages(aoMessages, aoTracks, bNoFeed) { toggleSettingsPanel(false); self.setHash(self.vars('page'), [$(this).val()]); }); - + var $Label = $('