473 lines
15 KiB
PHP
Executable File
473 lines
15 KiB
PHP
Executable File
<?php
|
|
|
|
class Spot extends Main
|
|
{
|
|
//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';
|
|
|
|
//Database
|
|
const MSG_TABLE = 'messages';
|
|
const FEED_TABLE = 'feeds';
|
|
const SPOT_TABLE = 'spots';
|
|
const POST_TABLE = 'posts';
|
|
|
|
//Picture folders
|
|
const PIC_FOLDER = 'files/';
|
|
const THUMB_FOLDER = self::PIC_FOLDER.'thumbs/';
|
|
|
|
const FORMAT_TIME = 'd/m/Y à H:i';
|
|
|
|
const FEED_CHUNK_SIZE = 15;
|
|
|
|
/**
|
|
* Active Project
|
|
* @var Project
|
|
*/
|
|
private $oProject;
|
|
|
|
public function __construct($oClassManagement, $sProcessPage)
|
|
{
|
|
$asClasses = array(
|
|
array('name'=>'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;
|
|
}
|
|
}
|
|
|
|
?>
|