'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('', 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', 'timezone', 'active', 'frequency') ), 'types' => array ( 'active' => "BOOLEAN", 'active_from' => "TIMESTAMP DEFAULT 0", 'active_to' => "TIMESTAMP DEFAULT 0", 'battery_state' => "VARCHAR(10)", 'codename' => "VARCHAR(100)", 'content' => "LONGTEXT", 'comment' => "LONGTEXT", 'description' => "VARCHAR(100)", 'email' => "VARCHAR(320) NOT NULL", 'filename' => "VARCHAR(100) NOT NULL", 'frequency' => "VARCHAR(1) NOT NULL", 'iso_time' => "VARCHAR(24)", 'language' => "VARCHAR(2)", 'last_update' => "TIMESTAMP DEFAULT 0", 'latitude' => "DECIMAL(7,5)", 'longitude' => "DECIMAL(8,5)", 'model' => "VARCHAR(20)", 'name' => "VARCHAR(100)", 'posted_on' => "TIMESTAMP DEFAULT 0", 'ref_feed_id' => "VARCHAR(40)", 'ref_msg_id' => "INT", 'ref_spot_id' => "VARCHAR(10)", 'rotate' => "SMALLINT", 'site_time' => "TIMESTAMP DEFAULT 0", //DEFAULT 0 removes auto-set to current time 'status' => "VARCHAR(10)", 'taken_on' => "TIMESTAMP DEFAULT 0", 'timezone' => "CHAR(64)", //see mysql.time_zone_name 'type' => "VARCHAR(20)", 'unix_time' => "INT" ), '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); } public function updateProject() { $bNewMsg = false; //Update all feeds belonging to the project $asFeeds = $this->oProject->getFeedIds(); foreach($asFeeds as $iFeedId) { $oFeed = new Feed($this->oDb, $iFeedId); $bNewMsg = $bNewMsg || $oFeed->checkUpdateFeed($this->oProject->getMode()); } //Send Update Email if($bNewMsg) { $oEmail = new Email($this->asContext['serv_name'], 'email_update'); $oEmail->setDestInfo($this->oUser->getActiveUsersInfo()); //Add Position $asMessages = $this->getSpotMessages(); $asLastMessage = end($asMessages); $asLastMessage['token'] = Settings::GEO_SERVER_TOKEN; $oEmail->oTemplate->setTags($asLastMessage); $oEmail->oTemplate->setTag('date_time', 'lang:date_time', array(date('d/m/Y', $asLastMessage['unix_time']), date('H:i', $asLastMessage['unix_time']))); //Add latest news feed $asNews = $this->getNewsFeed(0, true); $iPostCount = 0; foreach($asNews as $asPost) { if($asPost['type'] != 'message') { $oEmail->oTemplate->newInstance('news'); $oEmail->oTemplate->setInstanceTag('news', 'local_server', $this->asContext['serv_name']); $oEmail->oTemplate->addInstance($asPost['type'], $asPost); $oEmail->oTemplate->setInstanceTag($asPost['type'], 'local_server', $this->asContext['serv_name']); $iPostCount++; } if($iPostCount==5) break; } $oEmail->send(); } } 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=update_project > /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(), date_default_timezone_get()); $asUserInfo = $this->oUser->getUserInfo(); //Send Confirmation Email if($asResult['result'] && $asResult['desc']=='lang:nl_subscribed') { $oConfEmail = new Email($this->asContext['serv_name'], 'email_conf'); $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, $bInternal=false) { $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 $bInternal?$asFeeds: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); $asData = array( 'project' => $this->oProject->getProjects(), 'feed' => $oFeed->getFeeds(), 'spot' => $oFeed->getSpots() ); return self::getJsonResult(true, '', $asData); } 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 delAdminSettings($sType, $iId) { $bSuccess = false; $sDesc = ''; switch($sType) { case 'project': $oProject = new Project($this->oDb, $iId); $sDesc = $oProject->delete(); break; case 'feed': $oFeed = new Feed($this->oDb, $iId); $sDesc = $oFeed->delete(); break; } $bSuccess = ($sDesc==''); return self::getJsonResult($bSuccess, $sDesc, array($sType=>array(array('id'=>$iId, 'del'=>$bSuccess)))); } 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)); } }