310 lines
9.7 KiB
PHP
310 lines
9.7 KiB
PHP
<?php
|
|
|
|
namespace Franzz\Spot;
|
|
use Franzz\Objects\PhpObject;
|
|
use Franzz\Objects\Db;
|
|
use Franzz\Objects\Translator;
|
|
use \Settings;
|
|
|
|
/**
|
|
* Feed Class
|
|
* 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
|
|
|
|
//Weather
|
|
const WEATHER_HOOK = 'https://weather.visualcrossing.com/VisualCrossingWebServices/rest/services/timeline';
|
|
const WEATHER_PARAM = array(
|
|
'key' => Settings::WEATHER_TOKEN,
|
|
'unitGroup' => 'metric',
|
|
'lang' => 'en',
|
|
'include' => 'current',
|
|
'iconSet' => 'icons2'
|
|
);
|
|
|
|
//Timezone
|
|
const TIMEZONE_HOOK = 'http://api.geonames.org/timezoneJSON';
|
|
const TIMEZONE_PARAM = array(
|
|
'username' => Settings::TIMEZONE_USER
|
|
);
|
|
|
|
//DB Tables
|
|
const SPOT_TABLE = 'spots';
|
|
const FEED_TABLE = 'feeds';
|
|
const MSG_TABLE = 'messages';
|
|
|
|
//Hide/Display values
|
|
const MSG_HIDDEN = 0;
|
|
const MSG_DISPLAYED = 1;
|
|
|
|
/**
|
|
* 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()) {
|
|
$sFeedIdCol = Db::getId(self::FEED_TABLE, true);
|
|
$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($sFeedIdCol => $this->getFeedId(), 'display' => self::MSG_DISPLAYED),
|
|
'constOpe' => array($sFeedIdCol => "=", 'display' => "="),
|
|
'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)] ?? 0;
|
|
}
|
|
|
|
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();
|
|
$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' => $this->getTimeZone(array($asMsg['latitude'], $asMsg['longitude']), $asMsg['unixTime']),
|
|
'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);
|
|
|
|
if(array_key_exists('currentConditions', $asWeather)) { //Current conditions
|
|
$sWeatherIcon = $asWeather['currentConditions']['icon'];
|
|
$sWeatherCond = $asWeather['currentConditions']['conditions'];
|
|
$sWeatherTemp = $asWeather['currentConditions']['temp'];
|
|
}
|
|
elseif($asWeather['days'][0]['icon'] != '') { //Daily Conditions
|
|
$sWeatherIcon = $asWeather['days'][0]['icon'];
|
|
$sWeatherCond = $asWeather['days'][0]['conditions'];
|
|
$sWeatherTemp = $asWeather['days'][0]['temp'];
|
|
}
|
|
else {
|
|
$sWeatherIcon = 'unknown';
|
|
}
|
|
|
|
//Get Condition ID
|
|
$sCondKey = (new Translator(self::WEATHER_PARAM['lang']))->getTranslationKey($sWeatherCond);
|
|
|
|
return array(
|
|
'weather_icon' => $sWeatherIcon,
|
|
'weather_cond' => $sCondKey,
|
|
'weather_temp' => floatval($sWeatherTemp)
|
|
);
|
|
}
|
|
|
|
private function getTimeZone($asLatLng, $iTimeStamp) {
|
|
$asParams = self::TIMEZONE_PARAM;
|
|
$asParams['lat'] = $asLatLng[0];
|
|
$asParams['lng'] = $asLatLng[1];
|
|
$sApiUrl = self::TIMEZONE_HOOK.'?'.http_build_query($asParams);
|
|
|
|
$asTimeZone = json_decode(file_get_contents($sApiUrl), true);
|
|
|
|
return $asTimeZone['timezoneId'] ?? Settings::TIMEZONE;
|
|
}
|
|
|
|
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;
|
|
}
|
|
}
|