Settings::WEATHER_TOKEN, 'unitGroup' => 'metric', 'lang' => 'en', 'include' => 'current', 'iconSet' => 'icons2' ); //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 = $asFeed['last_update']=='0000-00-00 00:00:00'?0:strtotime($asFeed['last_update']); } public function createFeedId($oProjectId) { $this->setFeedId($this->oDb->insertRow(self::FEED_TABLE, array( Db::getId(Project::PROJ_TABLE) => $oProjectId, 'status' => 'INACTIVE' ))); 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($asConstraints=array()) { $asInfo = array( 'select' => array( Db::getId(self::MSG_TABLE), 'ref_msg_id', 'type', //ID 'latitude', 'longitude', //Position 'site_time', 'timezone', 'unix_time', //Time 'weather_icon', 'weather_cond', 'weather_temp' //Weather ), 'from' => self::MSG_TABLE, 'join' => array(self::FEED_TABLE => Db::getId(self::FEED_TABLE)), 'constraint'=> array(Db::getId(self::FEED_TABLE, true) => $this->getFeedId()), 'constOpe' => array(Db::getId(self::FEED_TABLE, true) => "="), 'orderBy' => array('site_time'=>'ASC') ); if(!empty($asConstraints)) $asInfo = array_merge($asInfo, $asConstraints); $asResult = $this->oDb->selectRows($asInfo); /* Temporary lookup - Start */ $iCount = 0; foreach($asResult as &$asMsg) { if($asMsg['weather_icon'] == '' && $iCount < 3) { $asWeather = $this->getWeather(array($asMsg['latitude'], $asMsg['longitude']), $asMsg['unix_time']); $asMsg = array_merge($asMsg, $asWeather); $this->oDb->updateRow(self::MSG_TABLE, $asMsg[Db::getId(self::MSG_TABLE)], $asWeather, false); $iCount++; } } /* Temporary lookup - End */ return $asResult; } public function getLastMessageId($asConstraints=array()) { $asMessages = $this->getMessages($asConstraints); return end($asMessages)[Db::getId(self::MSG_TABLE)]; } 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 && !Settings::DEBUG) $bNewMsg = $this->updateFeed(); } return $bNewMsg; } private function updateFeed() { $bNewMsg = false; $asData = $this->retrieveFeed(); $sNow = date(Db::TIMESTAMP_FORMAT); 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'], 'name' => $asMsgs[0]['messengerName'], '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'], Db::getId(self::SPOT_TABLE) => $iSpotId, 'name' => $asFeed['name'], 'description' => $asFeed['description'], 'status' => $asFeed['status'], 'last_update' => $sNow ); $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'], 'iso_time' => $asMsg['dateTime'], //ISO 8601 time (backup) 'site_time' => date(Db::TIMESTAMP_FORMAT, $asMsg['unixTime']), //Conversion to Site Time 'timezone' => Spot::getTimeZoneFromDate($asMsg['dateTime']), //Local Time Zone 'unix_time' => $asMsg['unixTime'], //UNIX Time (backup) 'content' => $asMsg['messageContent'], 'battery_state' => $asMsg['batteryState'] ); $iMsgId = $this->oDb->selectId(self::MSG_TABLE, array('ref_msg_id'=>$asMsg['ref_msg_id'])); if(!$iMsgId) { //First Catch $asMsg['posted_on'] = $sNow; //Weather Data $asMsg = array_merge($asMsg, $this->getWeather(array($asMsg['latitude'], $asMsg['longitude']), $asMsg['unix_time'])); $this->oDb->insertRow(self::MSG_TABLE, $asMsg); $bNewMsg = true; } else $this->oDb->updateRow(self::MSG_TABLE, $iMsgId, $asMsg); } } } else $this->oDb->updateRow(self::FEED_TABLE, $this->getFeedId(), array('last_update'=>$sNow)); return $bNewMsg; } private function getWeather($asLatLng, $iTimeStamp) { $sApiUrl = self::WEATHER_HOOK.'/'.$asLatLng[0].','.$asLatLng[1].'/'.$iTimeStamp.'?'.http_build_query(self::WEATHER_PARAM); $asWeather = json_decode(file_get_contents($sApiUrl), true); //Get Condition ID $sCondKey = (new Translator(self::WEATHER_PARAM['lang']))->getTranslationKey($asWeather['currentConditions']['conditions']); return array( 'weather_icon' => $asWeather['currentConditions']['icon'], 'weather_cond' => $sCondKey, 'weather_temp' => floatval($asWeather['currentConditions']['temp']) ); } private function retrieveFeed() { $sContent = '[]'; if($this->sRefFeedId !='') { $sUrl = self::FEED_HOOK.$this->sRefFeedId.self::FEED_TYPE_JSON; $sContent = file_get_contents($sUrl); } 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() { $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'); return $asResult; } }