'project', 'project'=>true), array('name'=>'cacher', 'project'=>true) ); parent::__construct($oClassManagement, $sProcessPage, $asClasses); $this->oProject = new Project($this->oDb); } protected function install() { //Install DB $this->oDb->install(); } protected function getSqlOptions() { return array ( 'tables' => array ( self::MSG_TABLE => array('ref_msg_id', Db::getId(self::FEED_TABLE), 'type', 'latitude', 'longitude', 'timestamp', 'unix_timestamp', 'content', 'battery_state'), self::FEED_TABLE => array('ref_feed_id', Db::getId(self::SPOT_TABLE), Db::getId(Project::PROJ_TABLE), 'name', 'description', 'status', 'last_update'), self::SPOT_TABLE => array('ref_spot_id', 'name', 'model'), Project::PROJ_TABLE => array('name', 'codename', 'active_from', 'active_to', 'geofile', 'timezone'), self::POST_TABLE => array(Db::getId(Project::PROJ_TABLE), 'name', 'content', 'timestamp') ), 'types' => array ( 'ref_msg_id' => "INT", 'type' => "VARCHAR(20)", 'latitude' => "DECIMAL(7,5)", 'longitude' => "DECIMAL(8,5)", 'timestamp' => "DATETIME", 'unix_timestamp'=> "INT", 'content' => "LONGTEXT", 'battery_state' => "VARCHAR(10)", 'ref_spot_id' => "VARCHAR(10)", 'name' => "VARCHAR(100)", 'codename' => "VARCHAR(100)", 'model' => "VARCHAR(20)", 'ref_feed_id' => "VARCHAR(40)", 'description' => "VARCHAR(100)", 'status' => "VARCHAR(10)", 'active_from' => "DATETIME", 'active_to' => "DATETIME", 'geofile' => "VARCHAR(50)", 'timezone' => "VARCHAR(100)", 'last_update' => "DATETIME" ), 'constraints' => array ( self::MSG_TABLE => "UNIQUE KEY `uni_ref_msg_id` (`ref_msg_id`)", self::FEED_TABLE => "UNIQUE KEY `uni_ref_feed_id` (`ref_feed_id`)", self::SPOT_TABLE => "UNIQUE KEY `uni_ref_spot_id` (`ref_spot_id`)", self::MSG_TABLE => "INDEX(`ref_msg_id`)", self::FEED_TABLE => "INDEX(`ref_feed_id`)", self::SPOT_TABLE => "INDEX(`ref_spot_id`)", Project::PROJ_TABLE => "UNIQUE KEY `uni_proj_name` (`codename`)", ), 'cascading_delete' => array ( self::SPOT_TABLE=>array(self::MSG_TABLE) ) ); } public function getMainPage($asGlobalVars = array(), $sMainPage = 'index', $asMainPageTags=array()) { return parent::getMainPage( array( 'vars' => array( 'chunk_size' => self::FEED_CHUNK_SIZE, 'mapbox_key' => Settings::MAPBOX_KEY, 'default_project_codename' => $this->oProject->getProjectCodeName(), 'projects' => $this->oProject->getProjects() ), 'consts' => array( 'modes' => Project::MODES, 'geo_folder'=> Project::GEO_FOLDER ) ), 'index', array('css_timestamp'=>$this->addTimestampToFilePath('style/spot.css')) ); } /* Managing projects */ public function setProjectId($iProjectId=0) { $this->oProject->setProjectId($iProjectId); } /* Getting & Storing messages */ private function getFeed($sRefFeedId) { $sUrl = self::FEED_HOOK.$sRefFeedId.self::FEED_TYPE_JSON; $sContent = file_get_contents($sUrl); return json_decode($sContent, true); } private function updateFeed($sRefFeedId) { $asData = $this->getFeed($sRefFeedId); $asMsgs = $asData['response']['feedMessageResponse']['messages']; $asFeed = $asData['response']['feedMessageResponse']['feed']; if(!empty($asMsgs)) { //Update Spot Info $asFirstMsg = array_values($asMsgs)[0]; $asSpotInfo = array('ref_spot_id'=>$asFirstMsg['messengerId'], 'name'=>$asFirstMsg['messengerName'], 'model'=>$asFirstMsg['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' => $sRefFeedId, Db::getId(self::SPOT_TABLE) => $iSpotId, Db::getId(Project::PROJ_TABLE) => $this->oProject->getProjectId(), 'name' => $asFeed['name'], 'description' => $asFeed['description'], 'status' => $asFeed['status'], 'last_update' => date(Db::TIMESTAMP_FORMAT) ); $iFeedId = $this->oDb->insertUpdateRow(self::FEED_TABLE, $asFeedInfo, array('ref_feed_id')); //Update Messages foreach($asMsgs as $asMsg) { $asMsg = array( 'ref_msg_id' => $asMsg['id'], Db::getId(self::FEED_TABLE) => $iFeedId, 'type' => $asMsg['messageType'], 'latitude' => $asMsg['latitude'], 'longitude' => $asMsg['longitude'], 'timestamp' => date(Db::TIMESTAMP_FORMAT, strtotime($asMsg['dateTime'])), //Stored in Local Time 'unix_timestamp' => $asMsg['unixTime'], //Stored in UNIX time 'content' => $asMsg['messageContent'], 'battery_state' => $asMsg['batteryState'] ); $this->oDb->insertUpdateRow(self::MSG_TABLE, $asMsg, array('ref_msg_id')); } } } public function getMessages() { /* Adding another point to test $test = $this->oDb->selectRow(self::MSG_TABLE, 1); unset($test['id_message']); $test['ref_msg_id'] = $test['ref_msg_id'] + 1; $test['latitude'] = '-41.4395'; $test['longitude'] = '172.1936'; $this->oDb->insertUpdateRow(self::MSG_TABLE, $test, array('ref_msg_id')); */ $asMessages = $this->getSpotMessages(); $bSuccess = !empty($asMessages); $sDesc = $bSuccess?'':self::NO_DATA; //Add pictures if($bSuccess) { $asPics = $this->getPictures(); //Sort messages and pictures chronologically uasort($asPics, function($a, $b){return $a['unix_timestamp'] > $b['unix_timestamp'];}); uasort($asMessages, function($a, $b){return $a['unix_timestamp'] > $b['unix_timestamp'];}); //Assign pictures to closest message $iIndex = 0; $iMaxIndex = count($asMessages) - 1; foreach($asPics as $asPic) { while($iIndex <= $iMaxIndex && $asPic['unix_timestamp'] > $asMessages[$iIndex]['unix_timestamp']) { $iIndex++; } if($iIndex == 0) $iMsgIndex = $iIndex; elseif($iIndex > $iMaxIndex) $iMsgIndex = $iMaxIndex; else { $iHalfWayPoint = ($asMessages[$iIndex]['unix_timestamp'] - $asMessages[$iIndex - 1]['unix_timestamp'])/2; $iMsgIndex = ($asPic['unix_timestamp'] >= $iHalfWayPoint)?$iIndex:($iIndex - 1); } $asMessages[$iMsgIndex]['pics'][] = $asPic; } } return self::getJsonResult($bSuccess, $sDesc, $asMessages); } private function getSpotMessages() { //Get Feed IDs $asFeeds = $this->oDb->selectRows(array( 'select' => array(Db::getId(self::FEED_TABLE), 'ref_feed_id', 'last_update'), 'from' => self::FEED_TABLE, 'constraint'=> array(Db::getId(Project::PROJ_TABLE) => $this->oProject->getProjectId())) ); //Update on Blog Mode and not updated already today $asAllFeedMessages = array(); foreach($asFeeds as $asFeed) { //Feed updated once a day in Blog Mode if($this->oProject->getMode() == Project::MODE_BLOG && mb_substr($asFeed['last_update'], 0, 10) != date('Y-m-d')) { $this->updateFeed($asFeed['ref_feed_id']); } $asInfo = array( 'select' => array('id_message', 'ref_msg_id', 'type', 'latitude', 'longitude', 'timestamp', 'unix_timestamp'), 'from' => self::MSG_TABLE, 'constraint'=> array( Db::getId(Spot::FEED_TABLE) => $asFeed[Db::getId(self::FEED_TABLE)], 'timestamp' => $this->oProject->getActivePeriod()), 'constOpe' => array( Db::getId(Spot::FEED_TABLE) => "=", 'timestamp' => "BETWEEN"), 'orderBy' => array('timestamp'=>'ASC')); $asMessages = $this->oDb->selectRows($asInfo); foreach($asMessages as $asMessage) { $asMessage['unix_timestamp'] = (int) $asMessage['unix_timestamp']; $asMessage['latitude'] = floatval($asMessage['latitude']); $asMessage['longitude'] = floatval($asMessage['longitude']); $asMessage['relative_time'] = Toolbox::getDateTimeDesc($asMessage['unix_timestamp']); $asMessage['formatted_time'] = date(self::FORMAT_TIME, $asMessage['unix_timestamp']); $asAllFeedMessages[] = $asMessage; } } return $asAllFeedMessages; } private function getPictures() { $asPicPaths = glob(self::PIC_FOLDER.'*.{jpg,JPG,jpeg,JPEG,png,PNG}', GLOB_BRACE); $asPics = array(); foreach($asPicPaths as $sPicPath) { //Finding picture timestamp $asPicInfo = self::getPicInfo($sPicPath); $iPicTimeStamp = $asPicInfo['timestamp']; //Get/Create thumbnail $sThumbnailPath = self::getPicThumbnail($sPicPath); //Filter on valid time interval if($iPicTimeStamp >= strtotime($this->oProject->getActivePeriod('from')) && $iPicTimeStamp <= strtotime($this->oProject->getActivePeriod('to'))) { $asPics[] = array( 'path' => $sPicPath, 'thumb_path' => $sThumbnailPath, 'rotate' => $asPicInfo['rotate'], 'unix_timestamp'=> $iPicTimeStamp, 'formatted_time'=> date(self::FORMAT_TIME, $iPicTimeStamp), 'relative_time' => Toolbox::getDateTimeDesc($iPicTimeStamp) ); } } return $asPics; } private function getPosts() { $asInfo = array( 'from' => self::POST_TABLE, 'constraint'=> array(Db::getId(Project::PROJ_TABLE) => $this->oProject->getProjectId()), 'constOpe' => array(Db::getId(Project::PROJ_TABLE) => "=") ); if($this->oProject->getMode()==Project::MODE_HISTO) { $asInfo['constraint']['timestamp'] = $this->oProject->getActivePeriod(); $asInfo['constOpe']['timestamp'] = "BETWEEN"; } $asPosts = $this->oDb->selectRows($asInfo); foreach($asPosts as &$asPost) { $iUnixTimeStamp = strtotime($asPost['timestamp']); $asPost['unix_timestamp'] = $iUnixTimeStamp; $asPost['relative_time'] = Toolbox::getDateTimeDesc($iUnixTimeStamp); $asPost['formatted_time'] = date(self::FORMAT_TIME, $iUnixTimeStamp); $asPost['formatted_name'] = Toolbox::mb_ucwords($asPost['name']); } return $asPosts; } private static function getJsonId($iTimeStamp, $sPriority='0', $iDbId=0) { return ($iTimeStamp * -1).'.'.$sPriority.$iDbId; } public function getNewsFeed($iChunk=0) { $asFeed = array(); //Messages $asMessages = $this->getSpotMessages(); foreach($asMessages as $asMessage) { $iMsgId = $asMessage[Db::getId(self::MSG_TABLE)]; $iId = self::getJsonId($asMessage['unix_timestamp'], '0', $iMsgId); $asFeed[$iId] = $asMessage; $asFeed[$iId]['type'] = 'message'; $asFeed[$iId]['displayed_id'] = $iMsgId; } //Pictures $asPics = $this->getPictures(); foreach($asPics as $iKey=>$asPic) { $iId = self::getJsonId($asPic['unix_timestamp'], '1', $iKey); $asFeed[$iId] = $asPic; $asFeed[$iId]['type'] = 'picture'; } //Post $asPosts = $this->getPosts(); foreach($asPosts as $asPost) { $iId = self::getJsonId($asPost['unix_timestamp'], '2', $asPost[Db::getId(self::POST_TABLE)]); $asFeed[$iId] = $asPost; $asFeed[$iId]['type'] = 'post'; } //Sort by key ksort($asFeed); //Split chunks $asFeed = array_slice($asFeed, $iChunk*self::FEED_CHUNK_SIZE, self::FEED_CHUNK_SIZE); return self::getJsonResult(true, '', $asFeed); } public function addPost($sName, $sPost) { $asData = array( Db::getId(Project::PROJ_TABLE) => $this->oProject->getProjectId(), 'name' => mb_strtolower(trim($sName)), 'content' => trim($sPost), 'timestamp' => date(Db::TIMESTAMP_FORMAT) ); $iPostId = $this->oDb->insertRow(self::POST_TABLE, $asData); return self::getJsonResult(($iPostId > 0), ''); } public function upload() { $this->oClassManagement->incClass('uploader', true); $oUploader = new Uploader(array('image_versions'=>array(), 'accept_file_types'=>'/\.(gif|jpe?g|png)$/i')); $oUploader->init(); return $oUploader->getBody(); } public function getTile($sMapId, $iX, $iY, $iZ) { if(isset($_SERVER['HTTP_REFERER']) && $_SERVER['HTTP_REFERER'] == $this->asContext['serv_name']) { $asDomains = array(); $sReferer = 'http://spot.lutran.fr'; switch($sMapId) { case 'mapbox.satellite': case 'mapbox.streets': $sPattern = 'https://api.tiles.mapbox.com/v4/{id}/{z}/{x}/{y}@2x.png?access_token={token}'; $sToken = Settings::MAPBOX_KEY; break; case 'linz': $sPattern = 'http://tiles-{s}.data-cdn.linz.govt.nz/services;key={token}/tiles/v4/layer=50767/EPSG:3857/{z}/{x}/{y}.png'; $sToken = Settings::LINZ_KEY; $asDomains = array('a', 'b', 'c', 'd'); break; case 'ign.es': $sPattern = 'http://www.ign.es/wmts/mapa-raster?request=getTile&format=image/png&layer=MTN&TileMatrixSet=GoogleMapsCompatible&TileMatrix={z}&TileCol={x}&TileRow={y}'; break; case 'ign.fr': $sPattern = 'https://wxs.ign.fr/{token}/geoportail/wmts?LAYER=GEOGRAPHICALGRIDSYSTEMS.MAPS&EXCEPTIONS=text/xml&FORMAT=image/jpeg&SERVICE=WMTS&VERSION=1.0.0&REQUEST=GetTile&STYLE=normal&TILEMATRIXSET=PM&TILEMATRIX={z}&TILEROW={y}&TILECOL={x}'; $sToken = Settings::IGN_FR_KEY; $sReferer = 'https://www.visugpx.com/yfJDwfuTlf'; break; } $oCacher = new Cacher($sPattern, $sMapId); $oCacher->setToken($sToken); $oCacher->setDomains($asDomains); $oCacher->setReferer($sReferer); return $oCacher->pushTile($iX, $iY, $iZ); } else { header('HTTP/1.1 403 Forbidden'); exit; } } public static function getPicInfo($sPicPath) { $iPicTimeStamp = $iPicTakenTimeStamp = $iPicFileTimeStamp = 0; $asExif = @exif_read_data($sPicPath, 0, true); if(!$asExif) $asExif['FILE']['FileDateTime'] = filemtime($sPicPath); //Timestamps if(array_key_exists('EXIF', $asExif) && array_key_exists('DateTimeOriginal', $asExif['EXIF'])) $iPicTakenTimeStamp = strtotime($asExif['EXIF']['DateTimeOriginal']); if(array_key_exists('FILE', $asExif) && array_key_exists('FileDateTime', $asExif['FILE'])) $iPicFileTimeStamp = $asExif['FILE']['FileDateTime']; //Merge timestamps $iPicTimeStamp = ($iPicTakenTimeStamp > 0)?$iPicTakenTimeStamp:$iPicFileTimeStamp; //Time Zone //if($iPicTimeStamp >= self::HONEYMOON_DATE_BEG && $iPicTimeStamp <= self::HONEYMOON_DATE_END) $iPicTimeStamp -= 60*60*(12+1); //timezone + daylight saving //Orientation if(array_key_exists('IFD0', $asExif) && array_key_exists('Orientation', $asExif['IFD0'])) { switch($asExif['IFD0']['Orientation']) { case 1: $sRotate = '0'; break; //None case 3: $sRotate = '180'; break; //Flip over case 6: $sRotate = '90'; break; //Clockwise case 8: $sRotate = '-90'; break; //Trigo default: $sRotate = $asExif['IFD0']['Orientation']; } } else $sRotate = '0'; return array( 'timestamp' => $iPicTimeStamp, 'taken_ts' => $iPicTakenTimeStamp, 'file_ts' => $iPicFileTimeStamp, 'rotate' => $sRotate ); } public static function getPicThumbnail($sPicPath) { $sPicName = pathinfo($sPicPath, PATHINFO_BASENAME); $sThumbPath = self::THUMB_FOLDER.$sPicName; if(!file_exists($sThumbPath)) $asThumbInfo = ToolBox::createThumbnail($sPicPath, 400, 0, $sThumbPath/*, false, array('jpg', 'jpeg', 'gif', 'png'), true*/); else $asThumbInfo = array('error'=>'', 'out'=>$sThumbPath); return ($asThumbInfo['error']=='')?$asThumbInfo['out']:$sPicPath; } } ?>