Files
spot/inc/spot.php

509 lines
16 KiB
PHP
Executable File

<?php
/* Timezones
* - Feeds (table `feeds`):
* - last_update: timestamp in site time (see Settings::TIMEZONE)
* - Spot Messages (table `messages`):
* - unix_time: UNIX (int) in UTC
* - site_time: timestamp in site time (see Settings::TIMEZONE)
* - iso_time: raw ISO 8601 in local timezone
* - Medias (table `medias`):
* - posted_on: timestamp in site time (see Settings::TIMEZONE)
* - taken_on: timestamp in site time (see Settings::TIMEZONE)
* - Posts (table `posts`):
* - site_time: timestamp in site time (see Settings::TIMEZONE)
*/
class Spot extends Main
{
//Database
const POST_TABLE = 'posts';
const FEED_CHUNK_SIZE = 15;
const DEFAULT_LANG = 'en';
/**
* Active Project
* @var Project
*/
private $oProject;
/**
* Media Class
* @var Media
*/
private $oMedia;
/**
* User
* @var User
*/
private $oUser;
public function __construct($oClassManagement, $sProcessPage, $sTimezone)
{
$asClasses = array(
array('name'=>'feed', 'project'=>true),
array('name'=>'project', 'project'=>true),
array('name'=>'media', 'project'=>true),
array('name'=>'converter', 'project'=>true),
array('name'=>'user', 'project'=>true),
array('name'=>'email', 'project'=>true)
);
parent::__construct($oClassManagement, $sProcessPage, $asClasses, true, __FILE__, $sTimezone);
$this->oUser = new User($this->oDb);
$this->oClassManagement->incClass('translator');
$this->oLang = new Translator($this->oUser->getLang(), self::DEFAULT_LANG);
$this->oProject = new Project($this->oDb);
$this->oMedia = new Media($this->oDb, $this->oProject);
}
protected function install()
{
//Install DB
$this->oDb->install();
}
protected function getSqlOptions()
{
return array
(
'tables' => array
(
Feed::MSG_TABLE => array('ref_msg_id', Db::getId(Feed::FEED_TABLE), 'type', 'latitude', 'longitude', 'iso_time', 'site_time', 'unix_time', 'content', 'battery_state'),
Feed::FEED_TABLE => array('ref_feed_id', Db::getId(Feed::SPOT_TABLE), Db::getId(Project::PROJ_TABLE), 'name', 'description', 'status', 'last_update'),
Feed::SPOT_TABLE => array('ref_spot_id', 'name', 'model'),
Project::PROJ_TABLE => array('name', 'codename', 'active_from', 'active_to', 'timezone'),
self::POST_TABLE => array(Db::getId(Project::PROJ_TABLE), Db::getId(User::USER_TABLE), 'name', 'content', 'site_time'),
Media::MEDIA_TABLE => array(Db::getId(Project::PROJ_TABLE), 'filename', 'type', 'taken_on', 'posted_on', 'rotate', 'comment'),
User::USER_TABLE => array('name', 'email', 'language', 'active')
),
'types' => array
(
'ref_msg_id' => "INT",
'type' => "VARCHAR(20)",
'latitude' => "DECIMAL(7,5)",
'longitude' => "DECIMAL(8,5)",
'iso_time' => "VARCHAR(24)",
'site_time' => "TIMESTAMP DEFAULT 0", //DEFAULT 0 removes auto-set to current time
'unix_time' => "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' => "TIMESTAMP DEFAULT 0",
'active_to' => "TIMESTAMP DEFAULT 0",
'timezone' => "VARCHAR(100)",
'last_update' => "TIMESTAMP DEFAULT 0",
'filename' => "VARCHAR(100) NOT NULL",
'taken_on' => "TIMESTAMP DEFAULT 0",
'posted_on' => "TIMESTAMP DEFAULT 0",
'rotate' => "SMALLINT",
'comment' => "LONGTEXT",
'email' => "VARCHAR(320)",
'language' => "VARCHAR(2)",
'active' => "BOOLEAN"
),
'constraints' => array
(
Feed::MSG_TABLE => array("UNIQUE KEY `uni_ref_msg_id` (`ref_msg_id`)", "INDEX(`ref_msg_id`)"),
Feed::FEED_TABLE => array("UNIQUE KEY `uni_ref_feed_id` (`ref_feed_id`)", "INDEX(`ref_feed_id`)"),
Feed::SPOT_TABLE => array("UNIQUE KEY `uni_ref_spot_id` (`ref_spot_id`)", "INDEX(`ref_spot_id`)"),
Project::PROJ_TABLE => "UNIQUE KEY `uni_proj_name` (`codename`)",
Media::MEDIA_TABLE => "UNIQUE KEY `uni_file_name` (`filename`)",
User::USER_TABLE => "UNIQUE KEY `uni_email` (`email`)"
),
'cascading_delete' => array
(
Feed::SPOT_TABLE => array(Feed::MSG_TABLE)
)
);
}
public function getMainPage($asGlobalVars = array(), $sMainPage = 'index', $asMainPageTags=array())
{
return parent::getMainPage(
array(
'vars' => array(
'chunk_size' => self::FEED_CHUNK_SIZE,
'default_project_codename' => $this->oProject->getProjectCodeName(),
'projects' => $this->oProject->getProjects(),
'user' => $this->oUser->getUserInfo()
),
'consts' => array(
'geo_server' => Settings::GEO_SERVER,
'modes' => Project::MODES,
'default_timezone' => Settings::TIMEZONE
)
),
'index',
array(
'host_url' => $this->asContext['serv_name'],
'filepath_css' => self::addTimestampToFilePath('style/spot.css'),
'filepath_js_d3' => self::addTimestampToFilePath('script/d3.min.js'),
'filepath_js_leaflet' => self::addTimestampToFilePath('script/leaflet.min.js'),
'filepath_js_jquery' => self::addTimestampToFilePath('script/jquery.min.js'),
'filepath_js_jquery_mods' => self::addTimestampToFilePath('script/jquery.mods.js'),
'filepath_js_spot' => self::addTimestampToFilePath('script/spot.js'),
'filepath_js_lightbox' => self::addTimestampToFilePath('script/lightbox.js')
)
);
}
/* Managing projects */
public function setProjectId($iProjectId=0) {
$this->oProject->setProjectId($iProjectId);
//Update all feeds belonging to the project
$asFeeds = $this->oProject->getFeedIds();
foreach($asFeeds as $iFeedId) {
$oFeed = new Feed($this->oDb, $iFeedId);
$oFeed->checkUpdateFeed($this->oProject->getMode());
}
}
public function genCronFile() {
//$bSuccess = (file_put_contents('spot_cron.sh', '#!/bin/bash'."\n".'cd '.dirname($_SERVER['SCRIPT_FILENAME'])."\n".'php -f index.php a=update_feed')!==false);
$sFileName = 'spot_cron.sh';
$sContent =
'#!/bin/bash'."\n".
'wget -qO- '.$this->asContext['serv_name'].'index.php?a=dummy > /dev/null'."\n".
'#Crontab job: 0 * * * * . '.dirname($_SERVER['SCRIPT_FILENAME']).'/'.$sFileName.' > /dev/null'."\n";
$bSuccess = (file_put_contents($sFileName, $sContent)!==false);
return self::getJsonResult($bSuccess, '');
}
public function getMarkers()
{
$asMessages = $this->getSpotMessages();
$bSuccess = !empty($this->getMedias('posted_on') + $asMessages + $this->getPosts());
$sDesc = $bSuccess?'':self::NO_DATA;
//Add medias
if($bSuccess) {
$asMedias = $this->getMedias('taken_on');
//Assign medias to closest message
$iIndex = 0;
$iMaxIndex = count($asMessages) - 1;
foreach($asMedias as $asMedia) {
while($iIndex <= $iMaxIndex && $asMedia['unix_time'] > $asMessages[$iIndex]['unix_time']) {
$iIndex++;
}
if($iIndex == 0) $iMsgIndex = $iIndex;
elseif($iIndex > $iMaxIndex) $iMsgIndex = $iMaxIndex;
else {
$iHalfWayPoint = ($asMessages[$iIndex - 1]['unix_time'] + $asMessages[$iIndex]['unix_time'])/2;
$iMsgIndex = ($asMedia['unix_time'] >= $iHalfWayPoint)?$iIndex:($iIndex - 1);
}
$asMessages[$iMsgIndex]['medias'][] = $asMedia;
}
}
return self::getJsonResult($bSuccess, $sDesc, $asMessages);
}
public function subscribe($sEmail) {
$asResult = $this->oUser->addUser($sEmail, $this->oLang->getLanguage());
$asUserInfo = $this->oUser->getUserInfo();
//Send Confirmation Email
if($asResult['result'] && $asResult['desc']=='lang:nl_subscribed') {
$oConfEmail = new Email($this->asContext['serv_name'], 'email_confirmation');
$oConfEmail->setDestInfo($asUserInfo);
$oConfEmail->send();
}
return self::getJsonResult($asResult['result'], $asResult['desc'], $asUserInfo);
}
public function unsubscribe() {
$asResult = $this->oUser->removeUser();
return self::getJsonResult($asResult['result'], $asResult['desc'], $asResult['data']);
}
public function unsubscribeFromEmail($iUserId) {
$this->oUser->setUserId($iUserId);
$this->oLang->setLanguage($this->oUser->getLang(), self::DEFAULT_LANG);
$asResult = $this->oUser->removeUser();
$sDesc = $asResult['desc'];
if($sDesc=='') $sDesc = $this->oLang->getTranslation('nl_unsubscribed');
return $sDesc;
}
private function getSpotMessages()
{
$asMessages = array();
//Get messages from all feeds belonging to the project
$asFeeds = $this->oProject->getFeedIds();
foreach($asFeeds as $iFeedId) {
$oFeed = new Feed($this->oDb, $iFeedId);
$asMessages = $oFeed->getMessages($this->oProject->getActivePeriod());
foreach($asMessages as &$asMessage)
{
$asMessage['latitude'] = floatval($asMessage['latitude']);
$asMessage['longitude'] = floatval($asMessage['longitude']);
$asMessage['lat_dms'] = self::decToDms($asMessage['latitude'], 'lat');
$asMessage['lon_dms'] = self::decToDms($asMessage['longitude'], 'lon');
$this->addTimeStamp($asMessage, $asMessage['unix_time']);
}
}
usort($asMessages, function($a, $b){return $a['unix_time'] > $b['unix_time'];});
$asSortedMessages = array_values($asMessages);
foreach($asSortedMessages as $iIndex=>&$asSortedMessage) $asSortedMessage['displayed_id'] = $iIndex + 1;
return $asSortedMessages;
}
/**
* Get valid medias based on $sTimeRefField:
* - taken_on: Date/time on which the media was taken
* - posted_on: Date/time on which the media was uploaded
* @param String $sTimeRefField Field to calculate relative times
* @return Array Medias info
*/
private function getMedias($sTimeRefField)
{
$asMedias = $this->oMedia->getMediasInfo();
$asValidMedias = array();
foreach($asMedias as $asMedia) {
$sTimeRef = $asMedia[$sTimeRefField];
if($sTimeRef >= $this->oProject->getActivePeriod('from') && $sTimeRef <= $this->oProject->getActivePeriod('to')) {
$asMedia['taken_on_formatted'] = $this->getTimeFormat(strtotime($asMedia['taken_on']));
$asMedia['posted_on_formatted'] = $this->getTimeFormat(strtotime($asMedia['posted_on']));
$this->addTimeStamp($asMedia, strtotime($sTimeRef));
$asValidMedias[] = $asMedia;
}
}
usort($asValidMedias, function($a, $b){return $a['unix_time'] > $b['unix_time'];});
$asSortedMedias = array_values($asValidMedias);
foreach($asSortedMedias as $iIndex=>&$asSortedMedia) $asSortedMedia['displayed_id'] = $iIndex + 1;
return $asSortedMedias;
}
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']['site_time'] = $this->oProject->getActivePeriod();
$asInfo['constOpe']['site_time'] = "BETWEEN";
}
$asPosts = $this->oDb->selectRows($asInfo);
foreach($asPosts as &$asPost) {
$iUnixTimeStamp = strtotime($asPost['site_time']); //assumes site timezone (Settings::TIMEZONE)
$asPost['formatted_name'] = Toolbox::mb_ucwords($asPost['name']);
unset($asPost[Db::getId(User::USER_TABLE)]);
$this->addTimeStamp($asPost, $iUnixTimeStamp);
}
usort($asPosts, function($a, $b){return $a['unix_time'] > $b['unix_time'];});
return $asPosts;
}
private function addTimeStamp(&$asData, $iTime) {
$asData['unix_time'] = (int) $iTime;
$asData['relative_time'] = Toolbox::getDateTimeDesc($iTime, $this->oLang->getLanguage());
$asData['formatted_time'] = $this->getTimeFormat($iTime);
}
public function getNewsFeed($iChunk=0)
{
$bHistoMode = ($this->oProject->getMode() == Project::MODE_HISTO);
$asFeeds = array();
$asFeedTypes = array(
'message' => array(
'table' => Feed::MSG_TABLE,
'feed' => $this->getSpotMessages(),
'priority' => 0
),
'media' => array(
'table' => Media::MEDIA_TABLE,
'feed' => $this->getMedias('posted_on'),
'priority' => 1
),
'post' => array(
'table' => self::POST_TABLE,
'feed' => $this->getPosts(),
'priority' => 2
)
);
foreach($asFeedTypes as $sFeedType=>$asFeedTypeInfo) {
foreach($asFeedTypeInfo['feed'] as $asFeed) {
$sPriority = $asFeedTypeInfo['priority'];
if($bHistoMode) $sPriority = count($asFeedTypes) - 1 - $asFeedTypeInfo['priority'];
$iId = ($asFeed['unix_time'] * -1).'.'.$sPriority.$asFeed[Db::getId($asFeedTypeInfo['table'])];
$asFeeds[$iId] = $asFeed;
$asFeeds[$iId]['type'] = $sFeedType;
}
}
//Sort by key
if($bHistoMode) krsort($asFeeds);
else ksort($asFeeds);
//Split chunks
$asFeeds = array_slice($asFeeds, $iChunk*self::FEED_CHUNK_SIZE, self::FEED_CHUNK_SIZE);
return self::getJsonResult(true, '', $asFeeds);
}
public function syncMedias() {
return $this->oMedia->syncFileFolder();
}
public function addPost($sName, $sPost)
{
$asData = array(
Db::getId(Project::PROJ_TABLE) => $this->oProject->getProjectId(),
Db::getId(User::USER_TABLE) => $this->oUser->getUserId(),
'name' => mb_strtolower(trim($sName)),
'content' => trim($sPost),
'site_time' => date(Db::TIMESTAMP_FORMAT) //site time (Settings::TIMEZONE)
);
$iPostId = $this->oDb->insertRow(self::POST_TABLE, $asData);
$this->oUser->updateNickname($sName);
return self::getJsonResult(($iPostId > 0), '');
}
public function upload()
{
$this->oClassManagement->incClass('uploader', true);
$oUploader = new Uploader($this->oMedia, $this->oLang);
return $oUploader->sBody;
}
public function addComment($iMediaId, $sComment) {
$oMedia = new Media($this->oDb, $this->oProject, $iMediaId);
$asResult = $oMedia->setComment($sComment);
return self::getJsonResult($asResult['result'], $asResult['desc'], $asResult['data']);
}
public function getAdminSettings() {
$oFeed = new Feed($this->oDb);
return self::getJsonResult(true, '', array(
'project' => $this->oProject->getProjects(),
'feed' => $oFeed->getFeeds(),
'spot' => $oFeed->getSpots()
));
}
public function setAdminSettings($sType, $iId, $sField, $sValue) {
$bSuccess = false;
$sDesc = '';
switch($sType) {
case 'project':
$oProject = new Project($this->oDb, $iId);
switch($sField) {
case 'name':
$bSuccess = $oProject->setProjectName($sValue);
break;
case 'codename':
$bSuccess = $oProject->setProjectCodeName($sValue);
break;
case 'active_from':
$bSuccess = $oProject->setActivePeriod($sValue.' 00:00:00', 'from');
break;
case 'active_to':
$bSuccess = $oProject->setActivePeriod($sValue.' 23:59:59', 'to');
break;
case 'timezone':
$bSuccess = $oProject->setTimeZone($sValue);
break;
}
$asResult = $oProject->getProject();
break;
case 'feed':
case 'spot':
$oFeed = new Feed($this->oDb, $iId);
switch($sField) {
case 'ref_feed_id':
$bSuccess = $oFeed->setRefFeedId($sValue);
break;
case 'spot_id':
$bSuccess = $oFeed->setSpotId($sValue);
break;
case 'project_id':
$bSuccess = $oFeed->setProjectId($sValue);
break;
}
$asResult = $oFeed->getFeed();
break;
}
if(!$bSuccess) $sDesc = Mask::LANG_PREFIX.'error_commit_db';
return self::getJsonResult($bSuccess, $sDesc, array($sType=>array($asResult)));
}
public function createProject() {
$oProject = new Project($this->oDb);
$iNewProjectId = $oProject->createProjectId();
$oFeed = new Feed($this->oDb);
$oFeed->createFeedId($iNewProjectId);
return self::getJsonResult($iNewProjectId>0, '', array(
'project' => array($oProject->getProject()),
'feed' => array($oFeed->getFeed())
));
}
public function convertGpxToGeojson($sGeoFileName) {
return Converter::convertToGeoJson($sGeoFileName);
}
public static function decToDms($dValue, $sType) {
if($sType=='lat') $sDirection = ($dValue >= 0)?'N':'S'; //Latitude
else $sDirection = ($dValue >= 0)?'E':'W'; //Longitude
$dLeft = abs($dValue);
//Degrees
$iDegree = floor($dLeft);
$dLeft -= $iDegree;
//Minutes
$iMinute = floor($dLeft * 60);
$dLeft -= $iMinute / 60;
//Seconds
$fSecond = round($dLeft * 3600, 1);
return $iDegree.'°'.$iMinute."'".$fSecond.'"'.$sDirection;
}
public function getTimeFormat($iTime) {
$sDate = date('d/m/Y', $iTime);
$sTime = date('H:i', $iTime);
return $this->oLang->getTranslation('date_time', array($sDate, $sTime));
}
}