diff --git a/files/db/update_v3_to_v4.sql b/files/db/update_v3_to_v4.sql new file mode 100644 index 0000000..093da8b --- /dev/null +++ b/files/db/update_v3_to_v4.sql @@ -0,0 +1,15 @@ +ALTER TABLE messages ADD iso_time VARCHAR(24) AFTER longitude; +ALTER TABLE messages CHANGE COLUMN timestamp site_time TIMESTAMP DEFAULT 0; +ALTER TABLE messages CHANGE COLUMN unix_timestamp unix_time INT; +UPDATE messages SET iso_time = CONCAT(REPLACE(CONVERT_TZ(FROM_UNIXTIME(unix_timestamp), @@session.time_zone, 'Pacific/Auckland'), ' ', 'T'), '+1200') WHERE id_feed = 1; + +ALTER TABLE feeds MODIFY last_update TIMESTAMP DEFAULT 0; + +ALTER TABLE projects MODIFY active_from TIMESTAMP DEFAULT 0; +ALTER TABLE projects MODIFY active_to TIMESTAMP DEFAULT 0; + +ALTER TABLE posts CHANGE COLUMN timestamp site_time TIMESTAMP DEFAULT 0; + +ALTER TABLE pictures CHANGE COLUMN timestamp posted_on TIMESTAMP DEFAULT 0; +UPDATE pictures INNER JOIN projects USING(id_project) SET taken_on = CONVERT_TZ(taken_on, projects.timezone, @@session.time_zone) where id_project = 1; +ALTER TABLE pictures MODIFY taken_on TIMESTAMP DEFAULT 0; \ No newline at end of file diff --git a/inc/picture.php b/inc/picture.php index 55d4953..0a157b1 100644 --- a/inc/picture.php +++ b/inc/picture.php @@ -32,7 +32,7 @@ class Picture extends PhpObject { if(empty($this->asPics)) { if($this->oProject->getProjectId()) { $asPics = $this->oDb->selectRows(array( - 'select' => array(Db::getId(self::PIC_TABLE), 'filename', 'taken_on', 'timestamp AS added_on', 'rotate'), + 'select' => array(Db::getId(self::PIC_TABLE), 'filename', 'taken_on', 'posted_on', 'rotate'), 'from' => self::PIC_TABLE, 'constraint'=> array(Db::getId(Project::PROJ_TABLE) => $this->oProject->getProjectId()) )); @@ -54,17 +54,20 @@ class Picture extends PhpObject { public function addPic($sPicName, $sMethod='upload') { $sError = ''; if(!$this->isProjectModeValid() && $sMethod!='sync') $sError = 'Le projet (id='.$this->oProject->getProjectId().') n\'est pas en mode "blog"'; - elseif($this->oDb->pingValue(self::PIC_TABLE, array('filename'=>$sPicName))) $sError = 'l\'image existe déjà'; + elseif($this->oDb->pingValue(self::PIC_TABLE, array('filename'=>$sPicName)) && $sMethod!='sync') $sError = 'l\'image existe déjà'; else { //Add picture to DB - $asPicInfo = self::getPicInfo($sPicName); - $iPicId = $this->oDb->insertRow(self::PIC_TABLE, array( + $asPicInfo = $this->getPicInfoFromFile($sPicName); + $asDbInfo = array( Db::getId(Project::PROJ_TABLE) => $this->oProject->getProjectId(), 'filename' => $sPicName, - 'taken_on' => date(Db::TIMESTAMP_FORMAT, $asPicInfo['taken_ts']), - 'timestamp' => date(Db::TIMESTAMP_FORMAT, $asPicInfo['file_ts']), + 'taken_on' => ($asPicInfo['taken_ts'] > 0)?date(Db::TIMESTAMP_FORMAT, $asPicInfo['taken_ts']):0, //Site Time (Settings::TIMEZONE) + 'posted_on' => date(Db::TIMESTAMP_FORMAT, $asPicInfo['file_ts']), //Site Time 'rotate' => $asPicInfo['rotate'] - )); + ); + + if($sMethod=='sync') $iPicId = $this->oDb->insertUpdateRow(self::PIC_TABLE, $asDbInfo, array(Db::getId(Project::PROJ_TABLE), 'filename')); + else $iPicId = $this->oDb->insertRow(self::PIC_TABLE, $asDbInfo); if(!$iPicId) $sError = 'l\'image n\'a pas pu être entrée en base'; else { @@ -95,19 +98,26 @@ class Picture extends PhpObject { return $this->getCleanMessageStack(); } - private static function getPicInfo($sPicName) + private function getPicInfoFromFile($sPicName) { $sPicPath = self::getPicPath($sPicName); - $iPicTimeStamp = $iPicTakenTimeStamp = $iPicFileTimeStamp = 0; + $iTimeStamp = $iTakenOn = $iPostedOn = 0; + $sTakenOn = ''; $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']; + if(array_key_exists('EXIF', $asExif) && array_key_exists('DateTimeOriginal', $asExif['EXIF'])) $sTakenOn = $asExif['EXIF']['DateTimeOriginal']; + if(array_key_exists('FILE', $asExif) && array_key_exists('FileDateTime', $asExif['FILE'])) $iPostedOn = $asExif['FILE']['FileDateTime']; + + //Picture info do not have any TZ: Interpreting date time using project timezone (assuming all pictures have been taken in this time zone) + if($sTakenOn != '') { + $oTakenOn = new DateTime($sTakenOn, new DateTimeZone($this->oProject->getTimeZone())); + $iTakenOn = $oTakenOn->format('U'); + } //Merge timestamps - $iPicTimeStamp = ($iPicTakenTimeStamp > 0)?$iPicTakenTimeStamp:$iPicFileTimeStamp; + $iTimeStamp = ($iTakenOn > 0)?$iTakenOn:$iPostedOn; //Orientation if(array_key_exists('IFD0', $asExif) && array_key_exists('Orientation', $asExif['IFD0'])) { @@ -123,9 +133,9 @@ class Picture extends PhpObject { else $sRotate = '0'; return array( - 'timestamp' => $iPicTimeStamp, - 'taken_ts' => $iPicTakenTimeStamp, - 'file_ts' => $iPicFileTimeStamp, + 'timestamp' => $iTimeStamp, + 'taken_ts' => $iTakenOn, + 'file_ts' => $iPostedOn, 'rotate' => $sRotate ); } diff --git a/inc/project.php b/inc/project.php index cdb671d..32be6b6 100644 --- a/inc/project.php +++ b/inc/project.php @@ -25,7 +25,7 @@ class Project extends PhpObject { private $sName; private $sCodeName; private $asActive; - private $sGeo; + private $asGeo; public function __construct(Db &$oDb) { parent::__construct(__CLASS__, Settings::DEBUG); @@ -68,7 +68,12 @@ class Project extends PhpObject { return ($sFromTo=='')?$this->asActive:$this->asActive[$sFromTo]; } + public function getTimeZone() { + return $this->asGeo['timezone']; + } + public function getProjects($iProjectId=0) { + $bSpecificProj = ($iProjectId > 0); $asInfo = array( 'select'=> array( Db::getId(self::PROJ_TABLE)." AS id", @@ -82,7 +87,7 @@ class Project extends PhpObject { ), 'from' => self::PROJ_TABLE ); - if($iProjectId > 0) $asInfo['constraint'] = array(Db::getId(self::PROJ_TABLE)=>$iProjectId); + if($bSpecificProj) $asInfo['constraint'] = array(Db::getId(self::PROJ_TABLE)=>$iProjectId); $asProjects = $this->oDb->selectRows($asInfo, 'codename'); foreach($asProjects as $sCodeName=>&$asProject) { @@ -94,17 +99,19 @@ class Project extends PhpObject { $asProject['geofile'] = Spot::addTimestampToFilePath(self::GEO_FOLDER.$asProject['geofile']); $asProject['codename'] = $sCodeName; } - return $asProjects; + return $bSpecificProj?$asProject:$asProjects; } private function setProjectInfo() { - $asResult = $this->getProjects($this->iProjectId); - $asProject = reset($asResult); - - $this->sMode = $asProject['mode']; - $this->asActive = array('from'=>$asProject['active_from'], 'to'=>$asProject['active_to']); - $this->sCodeName = key($asResult); - $this->sName = $asProject['name']; - $this->sGeo = array('file'=>$asProject['geofile'], 'timezone'=>$asProject['timezone']); + if($this->iProjectId > 0) { + $asProject = $this->getProjects($this->iProjectId); + + $this->sMode = $asProject['mode']; + $this->asActive = array('from'=>$asProject['active_from'], 'to'=>$asProject['active_to']); + $this->sCodeName = $asProject['codename']; + $this->sName = $asProject['name']; + $this->asGeo = array('file'=>$asProject['geofile'], 'timezone'=>$asProject['timezone']); + } + else $this->addError('Error while setting project: no project ID'); } } \ No newline at end of file diff --git a/inc/spot.php b/inc/spot.php index 4dba194..add2fd7 100755 --- a/inc/spot.php +++ b/inc/spot.php @@ -1,5 +1,19 @@ array ( - self::MSG_TABLE => array('ref_msg_id', Db::getId(self::FEED_TABLE), 'type', 'latitude', 'longitude', 'timestamp', 'unix_timestamp', 'content', 'battery_state'), + self::MSG_TABLE => array('ref_msg_id', Db::getId(self::FEED_TABLE), 'type', 'latitude', 'longitude', 'iso_time', 'site_time', 'unix_time', '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'), - Picture::PIC_TABLE => array(Db::getId(Project::PROJ_TABLE), 'filename', 'taken_on', 'timestamp', 'rotate') + self::POST_TABLE => array(Db::getId(Project::PROJ_TABLE), 'name', 'content', 'site_time'), + Picture::PIC_TABLE => array(Db::getId(Project::PROJ_TABLE), 'filename', 'taken_on', 'posted_on', 'rotate') ), 'types' => array ( @@ -67,8 +81,9 @@ class Spot extends Main 'type' => "VARCHAR(20)", 'latitude' => "DECIMAL(7,5)", 'longitude' => "DECIMAL(8,5)", - 'timestamp' => "DATETIME", - 'unix_timestamp'=> "INT", + '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)", @@ -78,13 +93,14 @@ class Spot extends Main 'ref_feed_id' => "VARCHAR(40)", 'description' => "VARCHAR(100)", 'status' => "VARCHAR(10)", - 'active_from' => "DATETIME", - 'active_to' => "DATETIME", + 'active_from' => "TIMESTAMP DEFAULT 0", + 'active_to' => "TIMESTAMP DEFAULT 0", 'geofile' => "VARCHAR(50)", 'timezone' => "VARCHAR(100)", - 'last_update' => "DATETIME", + 'last_update' => "TIMESTAMP DEFAULT 0", 'filename' => "VARCHAR(100) NOT NULL", - 'taken_on' => "DATETIME", + 'taken_on' => "TIMESTAMP DEFAULT 0", + 'posted_on' => "TIMESTAMP DEFAULT 0", 'rotate' => "SMALLINT" ), 'constraints' => array @@ -155,7 +171,11 @@ class Spot extends Main { //Update Spot Info $asFirstMsg = array_values($asMsgs)[0]; - $asSpotInfo = array('ref_spot_id'=>$asFirstMsg['messengerId'], 'name'=>$asFirstMsg['messengerName'], 'model'=>$asFirstMsg['modelId']); + $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 @@ -179,8 +199,9 @@ class Spot extends Main '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 + 'iso_time' => $asMsg['dateTime'], //ISO 8601 time (backup) + 'site_time' => date(Db::TIMESTAMP_FORMAT, $asMsg['unixTime']), //Conversion to Site Time (see Settings::TIMEZONE) + 'unix_time' => $asMsg['unixTime'], //UNIX Time (backup) 'content' => $asMsg['messageContent'], 'battery_state' => $asMsg['batteryState'] ); @@ -203,14 +224,14 @@ class Spot extends Main $iIndex = 0; $iMaxIndex = count($asMessages) - 1; foreach($asPics as $asPic) { - while($iIndex <= $iMaxIndex && $asPic['unix_timestamp'] > $asMessages[$iIndex]['unix_timestamp']) { + while($iIndex <= $iMaxIndex && $asPic['unix_time'] > $asMessages[$iIndex]['unix_time']) { $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); + $iHalfWayPoint = ($asMessages[$iIndex]['unix_time'] - $asMessages[$iIndex - 1]['unix_time'])/2; + $iMsgIndex = ($asPic['unix_time'] >= $iHalfWayPoint)?$iIndex:($iIndex - 1); } $asMessages[$iMsgIndex]['pics'][] = $asPic; @@ -239,15 +260,15 @@ class Spot extends Main } $asInfo = array( - 'select' => array('id_message', 'ref_msg_id', 'type', 'latitude', 'longitude', 'timestamp', 'unix_timestamp'), + 'select' => array('id_message', 'ref_msg_id', 'type', 'latitude', 'longitude', 'site_time', 'unix_time'), 'from' => self::MSG_TABLE, 'constraint'=> array( Db::getId(Spot::FEED_TABLE) => $asFeed[Db::getId(self::FEED_TABLE)], - 'timestamp' => $this->oProject->getActivePeriod()), + 'site_time' => $this->oProject->getActivePeriod()), 'constOpe' => array( Db::getId(Spot::FEED_TABLE) => "=", - 'timestamp' => "BETWEEN"), - 'orderBy' => array('timestamp'=>'ASC')); + 'site_time' => "BETWEEN"), + 'orderBy' => array('site_time'=>'ASC')); $asMessages = $this->oDb->selectRows($asInfo); foreach($asMessages as $asMessage) @@ -257,20 +278,20 @@ class Spot extends Main $asMessage['lat_dms'] = self::DecToDMS($asMessage['latitude'], 'lat'); $asMessage['lon_dms'] = self::DecToDMS($asMessage['longitude'], 'lon'); - $this->addTimeStamp($asMessage, $asMessage['unix_timestamp']); + $this->addTimeStamp($asMessage, $asMessage['unix_time']); $asAllFeedMessages[] = $asMessage; } } - usort($asAllFeedMessages, function($a, $b){return $a['unix_timestamp'] > $b['unix_timestamp'];}); + usort($asAllFeedMessages, function($a, $b){return $a['unix_time'] > $b['unix_time'];}); return $asAllFeedMessages; } /** - * Get valid pictures based on $sTimeRefField: - * - taken_on: Date/time on which the picture was taken (local time) - * - added_on: Date/time on which the picture was uploaded (site's time: Settings::TIMEZONE) + * Get valid pictures based on $sTimeRefField (both are on site time): + * - taken_on: Date/time on which the picture was taken + * - posted_on: Date/time on which the picture was uploaded * @param String $sTimeRefField Field to calculate relative times * @return Array Pictures info */ @@ -286,7 +307,7 @@ class Spot extends Main $asValidPics[] = $asPic; } } - usort($asValidPics, function($a, $b){return $a['unix_timestamp'] > $b['unix_timestamp'];}); + usort($asValidPics, function($a, $b){return $a['unix_time'] > $b['unix_time'];}); return $asValidPics; } @@ -299,24 +320,24 @@ class Spot extends Main 'constOpe' => array(Db::getId(Project::PROJ_TABLE) => "=") ); if($this->oProject->getMode()==Project::MODE_HISTO) { - $asInfo['constraint']['timestamp'] = $this->oProject->getActivePeriod(); - $asInfo['constOpe']['timestamp'] = "BETWEEN"; + $asInfo['constraint']['site_time'] = $this->oProject->getActivePeriod(); + $asInfo['constOpe']['site_time'] = "BETWEEN"; } $asPosts = $this->oDb->selectRows($asInfo); foreach($asPosts as &$asPost) { - $iUnixTimeStamp = strtotime($asPost['timestamp']); + $iUnixTimeStamp = strtotime($asPost['site_time']); //assumes site timezone (Settings::TIMEZONE) $asPost['formatted_name'] = Toolbox::mb_ucwords($asPost['name']); $this->addTimeStamp($asPost, $iUnixTimeStamp); } - usort($asPosts, function($a, $b){return $a['unix_timestamp'] > $b['unix_timestamp'];}); + usort($asPosts, function($a, $b){return $a['unix_time'] > $b['unix_time'];}); return $asPosts; } private function addTimeStamp(&$asData, $iTime) { - $asData['unix_timestamp'] = (int) $iTime; + $asData['unix_time'] = (int) $iTime; $asData['relative_time'] = Toolbox::getDateTimeDesc($iTime, 'fr'); $asData['formatted_time'] = date(self::FORMAT_TIME, $iTime); } @@ -333,7 +354,7 @@ class Spot extends Main ), 'picture' => array( 'table' => Picture::PIC_TABLE, - 'feed' => $this->getPictures('added_on'), + 'feed' => $this->getPictures('posted_on'), 'priority' => 1 ), 'post' => array( @@ -347,7 +368,7 @@ class Spot extends Main $sPriority = $asFeedTypeInfo['priority']; if($bHistoMode) $sPriority = count($asFeedTypes) - 1 - $asFeedTypeInfo['priority']; - $iId = ($asFeed['unix_timestamp'] * -1).'.'.$sPriority.$asFeed[Db::getId($asFeedTypeInfo['table'])]; + $iId = ($asFeed['unix_time'] * -1).'.'.$sPriority.$asFeed[Db::getId($asFeedTypeInfo['table'])]; $asFeeds[$iId] = $asFeed; $asFeeds[$iId]['type'] = $sFeedType; @@ -375,7 +396,7 @@ class Spot extends Main Db::getId(Project::PROJ_TABLE) => $this->oProject->getProjectId(), 'name' => mb_strtolower(trim($sName)), 'content' => trim($sPost), - 'timestamp' => date(Db::TIMESTAMP_FORMAT) + 'site_time' => date(Db::TIMESTAMP_FORMAT) //site time (Settings::TIMEZONE) ); $iPostId = $this->oDb->insertRow(self::POST_TABLE, $asData); return self::getJsonResult(($iPostId > 0), ''); diff --git a/masks/project.html b/masks/project.html index e9b363a..5b605f4 100755 --- a/masks/project.html +++ b/masks/project.html @@ -235,7 +235,7 @@ function initSpotMessages(aoMessages, aoTracks) { if(oMsg.pics) { var $Pics = $('
', {'class':'pics'}); $.each(oMsg.pics, function(iKey, asPic){ - $Pics.append($('', {href: asPic.pic_path, 'data-lightbox': self.consts.title, 'data-title': asPic.formatted_time}) + $Pics.append($('', {href: asPic.pic_path, 'data-lightbox': self.consts.title, 'data-title': asPic.formatted_time+' (heure française)'}) .append($('', {'src': asPic.thumb_path}))); }); $Tooltip @@ -387,7 +387,7 @@ function getPost(asPost) { break; case 'picture': var $Image = $('', {'src': asPost.thumb_path/*, 'style':'transform:rotate('+asPost.rotate+'deg);'*/}); - $Body = $('', {href: asPost.pic_path, 'data-lightbox': self.consts.title, 'data-title': 'Photo ajoutée le '+sAbsTime+' (prise le '+asPost.taken_on_formatted+' heure locale)'}).append($Image); + $Body = $('', {href: asPost.pic_path, 'data-lightbox': self.consts.title, 'data-title': 'Photo ajoutée le '+sAbsTime+' et prise le '+asPost.taken_on_formatted+' (heure française)'}).append($Image); break; case 'post': $Body = $('
') diff --git a/todo b/todo index 6a12f38..92946f7 100644 --- a/todo +++ b/todo @@ -2,10 +2,9 @@ To Do List ---------- - Elevation chart - Device/Spot Class -- Manage projects.timezone - Remove files2/ on server - Replace all images with FA icons -- add timezone to feed - on hover sur message : open popup on map (check zoom) - on hover on relative time: display absolute time -- bug on picture assignment to markers (routeburn) - related to TZ issue \ No newline at end of file +- bug on picture assignment to markers (routeburn) - related to TZ issue +- Replace 'heure française' with actual project timezone \ No newline at end of file