incClass('cacher', true); } protected function install() { //Install DB $this->oDb->install(); //Add feed //$this->oDb->insertRow(self::FEED_TABLE, array('ref_feed_id'=>Settings::FEED_ID)); } 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), 'name', 'description', 'status'), self::SPOT_TABLE => array('ref_spot_id', 'name', 'model'), self::POST_TABLE => array('name', 'content') ), '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)", 'model' => "VARCHAR(20)", 'ref_feed_id' => "VARCHAR(40)", 'description' => "VARCHAR(100)", 'status' => "VARCHAR(10)", ), '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`)", ), '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( 'feed_id' => Settings::FEED_ID, 'chunk_size'=> self::FEED_CHUNK_SIZE, 'mode' => Settings::MODE, 'mapbox_key'=> Settings::MAPBOX_KEY ) ), 'index', array('css_timestamp'=>$this->addTimestampToFilePath('style/spot.css')) ); } /* Getting & Storing messages */ private function getFeed($sRefFeedId=Settings::FEED_ID) { $sUrl = self::FEED_HOOK.$sRefFeedId.self::FEED_TYPE_JSON; $sContent = file_get_contents($sUrl); return json_decode($sContent, true); } private function updateFeed($sRefFeedId=Settings::FEED_ID) { $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 $asFeedInfo = array( 'ref_feed_id' => $sRefFeedId, Db::getId(self::SPOT_TABLE) => $iSpotId, 'name' => $asFeed['name'], 'description' => $asFeed['description'], 'status' => $asFeed['status'], 'led' => date(Db::MYSQL_TIMESTAMP) //update with current time ); $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::MYSQL_TIMESTAMP, 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($sRefFeedId=Settings::FEED_ID) { /* 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')); */ //Check last message & update feed if necessary (max once a day) $sLastMsg = $this->oDb->selectValue(self::FEED_TABLE, 'led', array('ref_feed_id'=>$sRefFeedId)); if(Settings::MODE!=self::MODE_HISTO && mb_substr($sLastMsg, 0, 10) != date('Y-m-d')) $this->updateFeed($sRefFeedId); //Extract messages $asMessages = array_values($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() { $asInfo = array('from'=>self::MSG_TABLE, 'orderBy'=>array('timestamp'=>'ASC')); if(Settings::MODE==self::MODE_HISTO) { $asInfo['constraint'] = array('timestamp'=>Settings::HISTO_SPAN); $asInfo['constOpe'] = array('timestamp'=>"BETWEEN"); } $asMessages = $this->oDb->selectRows($asInfo); foreach($asMessages as $iKey=>$asMessage) { $asMessages[$iKey]['unix_timestamp'] = (int) $asMessages[$iKey]['unix_timestamp']; $asMessages[$iKey]['latitude'] = floatval($asMessages[$iKey]['latitude']); $asMessages[$iKey]['longitude'] = floatval($asMessages[$iKey]['longitude']); $asMessages[$iKey]['relative_time'] = Toolbox::getDateTimeDesc($asMessages[$iKey]['unix_timestamp']); $asMessages[$iKey]['formatted_time'] = date(self::FORMAT_TIME, $asMessages[$iKey]['unix_timestamp']); } return $asMessages; } 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 (Histo mode only) if( Settings::MODE != self::MODE_HISTO || $iPicTimeStamp >= strtotime(Settings::HISTO_SPAN['from']) && $iPicTimeStamp <= strtotime(Settings::HISTO_SPAN['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); if(Settings::MODE==self::MODE_HISTO) { $asInfo['constraint'] = array('led'=>Settings::HISTO_SPAN); $asInfo['constOpe'] = array('led'=>"BETWEEN"); } $asPosts = $this->oDb->selectRows($asInfo); foreach($asPosts as &$asPost) { $iUnixTimeStamp = strtotime($asPost['led']); $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('name'=>mb_strtolower(trim($sName)), 'content'=>trim($sPost)); $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(); 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; } $oCacher = new Cacher($sPattern, $sMapId); $oCacher->setToken($sToken); $oCacher->setDomains($asDomains); 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; } } ?>