Implement lint

This commit is contained in:
2026-08-27 12:45:30 +02:00
parent 171db88400
commit 17009b641f
41 changed files with 70402 additions and 580 deletions
+3
View File
@@ -14,3 +14,6 @@
/vendor/ /vendor/
/node_modules/ /node_modules/
/composer.dev.lock /composer.dev.lock
# Lint caches
/.php-cs-fixer.cache
+53
View File
@@ -0,0 +1,53 @@
<?php
/*
* PHP-CS-Fixer config codifying the style already in use under lib/ and
* public/. Deliberately NOT based on @PSR2/@PSR12 - this codebase diverges
* from those on purpose (tabs, no space before control-structure parens),
* so pulling in a preset would rewrite most of the codebase to match a
* style nobody uses here. Rules below were derived by sampling lib/*.php,
* not from a style guide.
*/
$finder = (new PhpCsFixer\Finder())
->in([__DIR__.'/lib', __DIR__.'/public'])
->append([__DIR__.'/config/settings-sample.php'])
->name('*.php');
return (new PhpCsFixer\Config())
->setIndent("\t")
->setLineEnding("\n")
->setRules([
//Strings: single quotes except where interpolation needs double
'single_quote' => true,
//Arrays: short [] syntax, not long-form array()
'array_syntax' => ['syntax' => 'short'],
//Braces: same line as class/function signature (dominant in lib/,
//not the PSR-2 next-line-for-class convention)
'braces_position' => [
'classes_opening_brace' => 'same_line',
'functions_opening_brace' => 'same_line',
'anonymous_functions_opening_brace' => 'same_line'
],
//Concatenation: no padding around `.`
'concat_space' => ['spacing' => 'none'],
//true/false/null: lowercase (100% consistent already)
'constant_case' => ['case' => 'lower'],
'lowercase_keywords' => true,
'visibility_required' => ['elements' => ['method', 'property', 'const']],
//Housekeeping - safe regardless of style
'no_unused_imports' => true,
'no_trailing_whitespace' => true,
'no_trailing_whitespace_in_comment' => true,
'single_blank_line_at_eof' => true,
'no_empty_statement' => true,
'trim_array_spaces' => true,
'new_with_parentheses' => true
])
->setFinder($finder);
+8
View File
@@ -13,9 +13,13 @@
} }
], ],
"require": { "require": {
"php": ">=8.5",
"franzz/objects": "dev-vue", "franzz/objects": "dev-vue",
"phpmailer/phpmailer": "^7.1" "phpmailer/phpmailer": "^7.1"
}, },
"require-dev": {
"friendsofphp/php-cs-fixer": "^3.75"
},
"autoload": { "autoload": {
"psr-4": { "psr-4": {
"Franzz\\Livetrail\\": "lib/", "Franzz\\Livetrail\\": "lib/",
@@ -24,5 +28,9 @@
"files": [ "files": [
"config/settings.php" "config/settings.php"
] ]
},
"scripts": {
"lint": "php-cs-fixer fix --dry-run --diff",
"lint-fix": "php-cs-fixer fix"
} }
} }
+8
View File
@@ -10,9 +10,13 @@
} }
], ],
"require": { "require": {
"php": ">=8.5",
"franzz/objects": "dev-vue", "franzz/objects": "dev-vue",
"phpmailer/phpmailer": "^7.1" "phpmailer/phpmailer": "^7.1"
}, },
"require-dev": {
"friendsofphp/php-cs-fixer": "^3.75"
},
"autoload": { "autoload": {
"psr-4": { "psr-4": {
"Franzz\\Livetrail\\": "lib/" "Franzz\\Livetrail\\": "lib/"
@@ -20,5 +24,9 @@
"files": [ "files": [
"config/settings.php" "config/settings.php"
] ]
},
"scripts": {
"lint": "php-cs-fixer fix --dry-run --diff",
"lint-fix": "php-cs-fixer fix"
} }
} }
+16 -17
View File
@@ -1,20 +1,19 @@
<?php <?php
class Settings class Settings {
{ public const DB_SERVER = 'localhost';
const DB_SERVER = 'localhost'; public const DB_LOGIN = '';
const DB_LOGIN = ''; public const DB_PASS = '';
const DB_PASS = ''; public const DB_NAME = 'livetrail';
const DB_NAME = 'livetrail'; public const DB_ENC = 'utf8mb4';
const DB_ENC = 'utf8mb4'; public const TEXT_ENC = 'UTF-8';
const TEXT_ENC = 'UTF-8'; public const TIMEZONE = 'Europe/Zurich';
const TIMEZONE = 'Europe/Zurich'; public const MAIL_SERVER = '';
const MAIL_SERVER = ''; public const MAIL_FROM = '';
const MAIL_FROM = ''; public const MAIL_USER = '';
const MAIL_USER = ''; public const MAIL_PASS = '';
const MAIL_PASS = ''; public const WEATHER_TOKEN = ''; //visualcrossing.com
const WEATHER_TOKEN = ''; //visualcrossing.com public const TIMEZONE_USER = ''; //geonames.org
const TIMEZONE_USER = ''; //geonames.org public const DEBUG = true;
const DEBUG = true; public const LOG_FOLDER = __DIR__;
const LOG_FOLDER = __DIR__;
} }
+94
View File
@@ -0,0 +1,94 @@
// ESLint flat config codifying the style already in use across src/.
// Goal: catch real mistakes and enforce the existing conventions, not impose
// an external style guide. Rules were derived by sampling src/scripts/*.js
// and src/components/*.vue, not from a template.
import js from '@eslint/js';
import vue from 'eslint-plugin-vue';
import globals from 'globals';
export default [
js.configs.recommended,
...vue.configs['flat/essential'],
{
languageOptions: {
ecmaVersion: 'latest',
sourceType: 'module',
globals: {
...globals.browser
}
},
rules: {
//Indentation: tabs everywhere, one indent per level
'indent': 'off', //too many false positives on Vue template attr wrapping; keep manual
'no-tabs': 'off',
//Strings: single quotes (project uses single-quoted strings exclusively)
'quotes': ['warn', 'single', {avoidEscape: true, allowTemplateLiterals: true}],
//Semicolons: required almost everywhere. The one consistent
//exception is the top-level `export default {...}` in Vue SFCs,
//which never gets a trailing semicolon - ESLint's `semi` rule
//can't carve that one spot out, so this is 'warn' rather than
//'error' to avoid flagging every component's SFC boilerplate
//as a hard failure.
'semi': ['warn', 'always'],
//Equality: codebase mixes == and === deliberately (e.g. loose checks
//against numeric strings from the API) - don't force either
'eqeqeq': 'off',
//Function parens: no space before the parameter list - `function(x)`, not `function (x)`
'space-before-function-paren': ['warn', 'never'],
//Control structures: no space before the parenthesis -
//`if(x)`, `for(...)`, `switch(x)`, not `if (x)` etc.
//(100% consistent across src/ - checked via grep before writing this)
'keyword-spacing': ['warn', {
after: true,
overrides: {
if: {after: false},
for: {after: false},
while: {after: false},
switch: {after: false},
catch: {after: false}
}
}],
'space-before-blocks': ['warn', 'always'],
//Object-curly-spacing is deliberately NOT configured: the codebase
//consistently uses `{a, b}` (no padding) for object literals and
//destructuring, but `import { x } from 'y'` (padded) for imports -
//and this rule can't apply different spacing to those two node
//kinds, so enforcing either would misfire on the other.
'object-curly-spacing': 'off',
'array-bracket-spacing': ['warn', 'never'],
//Prefix convention (Hungarian-ish: sString, iInt, aArray, oObject, bBool)
//is a project-wide naming discipline, not something ESLint can check;
//left undocumented here on purpose.
//Real-bug catchers - keep these strict regardless of style
//ignoreRestSiblings covers the `const {prev, ...rest} = obj` idiom
//(destructuring a key out just to exclude it from the rest), used
//in App.vue - that `prev` binding is intentionally unused.
'no-unused-vars': ['warn', {argsIgnorePattern: '^_', ignoreRestSiblings: true}],
'no-undef': 'error',
'no-var': 'warn', //codebase is ES module/class based; var only appears in one legacy fallback block
'prefer-const': 'off', //not consistently followed, don't force churn
//Vue-specific: components are registered and used with camelCase
//tags in templates (<appIcon>, <projectMapLink>). ESLint's
//component-name-in-template-casing rule only supports PascalCase
//or kebab-case, neither of which matches, so it's left off here
//rather than forced into a casing the codebase doesn't use.
'vue/component-name-in-template-casing': 'off',
'vue/multi-word-component-names': 'off', //AppIcon, Admin, Project etc. mix single/multi-word by design
'vue/attribute-hyphenation': 'off', //existing templates mix camelCase and kebab-case attrs; not consistent enough to enforce yet
'vue/require-default-prop': 'off',
'vue/no-v-html': 'error' //codebase currently has zero v-html usage - keep it that way
}
},
{
ignores: ['public/**', 'vendor/**', 'node_modules/**']
}
];
+20 -33
View File
@@ -6,9 +6,8 @@ use Franzz\Objects\PhpObject;
use Franzz\Objects\ToolBox; use Franzz\Objects\ToolBox;
//TODO Keep only local specificities and move bulk to Franzz\Objects\Controller //TODO Keep only local specificities and move bulk to Franzz\Objects\Controller
class Controller extends PhpObject class Controller extends PhpObject {
{ private const MUTATING_ACTIONS = [
const MUTATING_ACTIONS = array(
'add_post', 'add_post',
'subscribe', 'subscribe',
'unsubscribe', 'unsubscribe',
@@ -21,34 +20,31 @@ class Controller extends PhpObject
'admin_set', 'admin_set',
'admin_create', 'admin_create',
'admin_delete' 'admin_delete'
); ];
const SESSION_WRITING_ACTIONS = array( private const SESSION_WRITING_ACTIONS = [
'login', 'login',
'logout' 'logout'
); ];
private Livetrail $oLivetrail; private Livetrail $oLivetrail;
private array $asReq; private array $asReq;
private string $sCsrfToken = ''; private string $sCsrfToken = '';
public function __construct() public function __construct() {
{
parent::__construct(__CLASS__); parent::__construct(__CLASS__);
} }
private function setReqVal(string $sKey, $oValue, string $sValidation=''): void private function setReqVal(string $sKey, $oValue, string $sValidation=''): void {
{
$this->asReq[$sKey] = $this->validateValue($sValidation, $oValue); $this->asReq[$sKey] = $this->validateValue($sValidation, $oValue);
} }
public function handle($sProcessPage, array $argv = array()): string public function handle($sProcessPage, array $argv = []): string {
{
//Start buffering so warnings/notices can be collected //Start buffering so warnings/notices can be collected
ob_start(); ob_start();
//Parse variables //Parse variables
$asReq = ToolBox::getRequest($argv); $asReq = ToolBox::getRequest($argv);
$this->asReq = array(); $this->asReq = [];
$sAction = $asReq['a'] ?? ''; $sAction = $asReq['a'] ?? '';
$this->setReqVal('t', $asReq['t'] ?? ''); $this->setReqVal('t', $asReq['t'] ?? '');
$this->setReqVal('name', $asReq['name'] ?? ''); $this->setReqVal('name', $asReq['name'] ?? '');
@@ -90,8 +86,7 @@ class Controller extends PhpObject
return $sResult; return $sResult;
} }
private function validateMutationRequest(string $sAction): bool private function validateMutationRequest(string $sAction): bool {
{
return return
PHP_SAPI === 'cli' PHP_SAPI === 'cli'
|| ||
@@ -101,44 +96,38 @@ class Controller extends PhpObject
; ;
} }
private function getCsrfToken(): string private function getCsrfToken(): string {
{
if($this->sCsrfToken === '') $this->initCsrfToken(); if($this->sCsrfToken === '') $this->initCsrfToken();
return $this->sCsrfToken; return $this->sCsrfToken;
} }
private function setCsrfToken(): void private function setCsrfToken(): void {
{
if(empty($_SESSION['csrf_token'])) $_SESSION['csrf_token'] = bin2hex(random_bytes(32)); if(empty($_SESSION['csrf_token'])) $_SESSION['csrf_token'] = bin2hex(random_bytes(32));
$this->sCsrfToken = $_SESSION['csrf_token']; $this->sCsrfToken = $_SESSION['csrf_token'];
} }
private function initCsrfToken(): void private function initCsrfToken(): void {
{
if(PHP_SAPI === 'cli') return; if(PHP_SAPI === 'cli') return;
if(session_status() !== PHP_SESSION_ACTIVE) { if(session_status() !== PHP_SESSION_ACTIVE) {
$bSecure = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off') || (($_SERVER['HTTP_X_FORWARDED_PROTO'] ?? '') === 'https'); $bSecure = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off') || (($_SERVER['HTTP_X_FORWARDED_PROTO'] ?? '') === 'https');
session_set_cookie_params(array('httponly' => true, 'secure' => $bSecure, 'samesite' => 'Lax')); session_set_cookie_params(['httponly' => true, 'secure' => $bSecure, 'samesite' => 'Lax']);
session_start(); session_start();
} }
$this->setCsrfToken(); $this->setCsrfToken();
} }
private function checkCsrfToken(string $sClientToken): bool private function checkCsrfToken(string $sClientToken): bool {
{
$sServerToken = $this->getCsrfToken(); $sServerToken = $this->getCsrfToken();
return PHP_SAPI === 'cli' || ($sServerToken !== '' && is_string($sClientToken) && hash_equals($sServerToken, $sClientToken)); return PHP_SAPI === 'cli' || ($sServerToken !== '' && is_string($sClientToken) && hash_equals($sServerToken, $sClientToken));
} }
private function closeSession(): void private function closeSession(): void {
{
if(session_status() === PHP_SESSION_ACTIVE) session_write_close(); if(session_status() === PHP_SESSION_ACTIVE) session_write_close();
} }
private function dispatch(string $sAction): string private function dispatch(string $sAction): string {
{
return match($sAction) { return match($sAction) {
'markers' => $this->oLivetrail->getMarkers(), 'markers' => $this->oLivetrail->getMarkers(),
'last_update' => $this->oLivetrail->getLastUpdate(), 'last_update' => $this->oLivetrail->getLastUpdate(),
@@ -154,8 +143,7 @@ class Controller extends PhpObject
}; };
} }
private function dispatchAdmin(string $sAction): string private function dispatchAdmin(string $sAction): string {
{
if(!$this->oLivetrail->checkUserClearance(User::CLEARANCE_ADMIN)) { if(!$this->oLivetrail->checkUserClearance(User::CLEARANCE_ADMIN)) {
return Livetrail::getJsonResult(false, Livetrail::NOT_FOUND); return Livetrail::getJsonResult(false, Livetrail::NOT_FOUND);
} }
@@ -173,11 +161,10 @@ class Controller extends PhpObject
}; };
} }
private static function validateValue(string $sValidation, $oValue=0) private static function validateValue(string $sValidation, $oValue=0) {
{
return match($sValidation) { return match($sValidation) {
'' => $oValue, '' => $oValue,
'positiveInt' => filter_var($oValue, FILTER_VALIDATE_INT, array('options' => array('default' => 0, 'min_range' => 0))) 'positiveInt' => filter_var($oValue, FILTER_VALIDATE_INT, ['options' => ['default' => 0, 'min_range' => 0]])
}; };
} }
} }
+2 -2
View File
@@ -25,7 +25,7 @@ class Email extends PhpObject {
parent::__construct(__CLASS__); parent::__construct(__CLASS__);
$this->sServName = $sServName; $this->sServName = $sServName;
$this->setTemplate($sTemplateName); $this->setTemplate($sTemplateName);
$this->asDests = array(); $this->asDests = [];
} }
public function setTemplate($sTemplateName) { public function setTemplate($sTemplateName) {
@@ -39,7 +39,7 @@ class Email extends PhpObject {
* @param array $asDests Contains: id_user, name, email, language, timezone, active * @param array $asDests Contains: id_user, name, email, language, timezone, active
*/ */
public function setDestInfo($asDests) { public function setDestInfo($asDests) {
if(array_key_exists('email', $asDests)) $asDests = array($asDests); if(array_key_exists('email', $asDests)) $asDests = [$asDests];
$this->asDests = $asDests; $this->asDests = $asDests;
} }
+50 -50
View File
@@ -13,32 +13,32 @@ use \Settings;
class Feed extends PhpObject { class Feed extends PhpObject {
//Spot feed //Spot feed
const FEED_HOOK = 'https://api.findmespot.com/spot-main-web/consumer/rest-api/2.0/public/feed/'; private const FEED_HOOK = 'https://api.findmespot.com/spot-main-web/consumer/rest-api/2.0/public/feed/';
const FEED_TYPE_XML = '/message.xml'; private const FEED_TYPE_XML = '/message.xml';
const FEED_TYPE_JSON = '/message.json'; private const FEED_TYPE_JSON = '/message.json';
const FEED_MAX_REFRESH = 5 * 60; //Seconds private const FEED_MAX_REFRESH = 5 * 60; //Seconds
//Weather //Weather
const WEATHER_HOOK = 'https://weather.visualcrossing.com/VisualCrossingWebServices/rest/services/timeline'; private const WEATHER_HOOK = 'https://weather.visualcrossing.com/VisualCrossingWebServices/rest/services/timeline';
const WEATHER_PARAM = array( private const WEATHER_PARAM = [
'key' => Settings::WEATHER_TOKEN, 'key' => Settings::WEATHER_TOKEN,
'unitGroup' => 'metric', 'unitGroup' => 'metric',
'lang' => 'en', 'lang' => 'en',
'include' => 'current', 'include' => 'current',
'iconSet' => 'icons2' 'iconSet' => 'icons2'
); ];
//Timezone //Timezone
const TIMEZONE_HOOK = 'http://api.geonames.org/timezoneJSON'; private const TIMEZONE_HOOK = 'http://api.geonames.org/timezoneJSON';
//DB Tables //DB Tables
const SPOT_TABLE = 'spots'; public const SPOT_TABLE = 'spots';
const FEED_TABLE = 'feeds'; public const FEED_TABLE = 'feeds';
const MSG_TABLE = 'messages'; public const MSG_TABLE = 'messages';
//Hide/Display values //Hide/Display values
const MSG_HIDDEN = 0; public const MSG_HIDDEN = 0;
const MSG_DISPLAYED = 1; public const MSG_DISPLAYED = 1;
/** /**
* Database Handle * Database Handle
@@ -72,10 +72,10 @@ class Feed extends PhpObject {
} }
public function createFeedId($oProjectId) { public function createFeedId($oProjectId) {
$this->setFeedId($this->oDb->insertRow(self::FEED_TABLE, array( $this->setFeedId($this->oDb->insertRow(self::FEED_TABLE, [
Db::getId(Project::PROJ_TABLE) => $oProjectId, Db::getId(Project::PROJ_TABLE) => $oProjectId,
'status' => 'INACTIVE' 'status' => 'INACTIVE'
))); ]));
return $this->getFeedId(); return $this->getFeedId();
} }
@@ -92,14 +92,14 @@ class Feed extends PhpObject {
} }
public function getSpots() { public function getSpots() {
$asSpots = $this->oDb->selectRows(array('from'=>self::SPOT_TABLE)); $asSpots = $this->oDb->selectRows(['from'=>self::SPOT_TABLE]);
foreach($asSpots as &$asSpot) $asSpot['id'] = $asSpot[Db::getId(self::SPOT_TABLE)]; foreach($asSpots as &$asSpot) $asSpot['id'] = $asSpot[Db::getId(self::SPOT_TABLE)];
return $asSpots; return $asSpots;
} }
public function getFeeds($iFeedId=0) { public function getFeeds($iFeedId=0) {
$asInfo = array('from'=>self::FEED_TABLE); $asInfo = ['from'=>self::FEED_TABLE];
if($iFeedId > 0) $asInfo['constraint'] = array(Db::getId(self::FEED_TABLE)=>$iFeedId); if($iFeedId > 0) $asInfo['constraint'] = [Db::getId(self::FEED_TABLE)=>$iFeedId];
$asFeeds = $this->oDb->selectRows($asInfo); $asFeeds = $this->oDb->selectRows($asInfo);
foreach($asFeeds as &$asFeed) $asFeed['id'] = $asFeed[Db::getId(self::FEED_TABLE)]; foreach($asFeeds as &$asFeed) $asFeed['id'] = $asFeed[Db::getId(self::FEED_TABLE)];
@@ -111,21 +111,21 @@ class Feed extends PhpObject {
return array_shift($asFeeds); return array_shift($asFeeds);
} }
public function getMessages($asConstraints=array()) { public function getMessages($asConstraints=[]) {
$sFeedIdCol = Db::getId(self::FEED_TABLE, true); $sFeedIdCol = Db::getId(self::FEED_TABLE, true);
$asInfo = array( $asInfo = [
'select' => array( 'select' => [
Db::getId(self::MSG_TABLE), 'ref_msg_id', 'type', //ID Db::getId(self::MSG_TABLE), 'ref_msg_id', 'type', //ID
'latitude', 'longitude', //Position 'latitude', 'longitude', //Position
'site_time', 'timezone', 'unix_time', //Time 'site_time', 'timezone', 'unix_time', //Time
'weather_icon', 'weather_cond', 'weather_temp' //Weather 'weather_icon', 'weather_cond', 'weather_temp' //Weather
), ],
'from' => self::MSG_TABLE, 'from' => self::MSG_TABLE,
'join' => array(self::FEED_TABLE => Db::getId(self::FEED_TABLE)), 'join' => [self::FEED_TABLE => Db::getId(self::FEED_TABLE)],
'constraint'=> array($sFeedIdCol => $this->getFeedId(), 'display' => self::MSG_DISPLAYED), 'constraint'=> [$sFeedIdCol => $this->getFeedId(), 'display' => self::MSG_DISPLAYED],
'constOpe' => array($sFeedIdCol => "=", 'display' => "="), 'constOpe' => [$sFeedIdCol => '=', 'display' => '='],
'orderBy' => array('site_time'=>'ASC') 'orderBy' => ['site_time'=>'ASC']
); ];
if(!empty($asConstraints)) $asInfo = array_merge($asInfo, $asConstraints); if(!empty($asConstraints)) $asInfo = array_merge($asInfo, $asConstraints);
$asResult = $this->oDb->selectRows($asInfo); $asResult = $this->oDb->selectRows($asInfo);
@@ -134,7 +134,7 @@ class Feed extends PhpObject {
$iCount = 0; $iCount = 0;
foreach($asResult as &$asMsg) { foreach($asResult as &$asMsg) {
if($asMsg['weather_icon'] == '' && $iCount < 3) { if($asMsg['weather_icon'] == '' && $iCount < 3) {
$asWeather = $this->getWeather(array($asMsg['latitude'], $asMsg['longitude']), $asMsg['unix_time']); $asWeather = $this->getWeather([$asMsg['latitude'], $asMsg['longitude']], $asMsg['unix_time']);
$asMsg = array_merge($asMsg, $asWeather); $asMsg = array_merge($asMsg, $asWeather);
$this->oDb->updateRow(self::MSG_TABLE, $asMsg[Db::getId(self::MSG_TABLE)], $asWeather, false); $this->oDb->updateRow(self::MSG_TABLE, $asMsg[Db::getId(self::MSG_TABLE)], $asWeather, false);
$iCount++; $iCount++;
@@ -145,7 +145,7 @@ class Feed extends PhpObject {
return $asResult; return $asResult;
} }
public function getLastMessageId($asConstraints=array()) { public function getLastMessageId($asConstraints=[]) {
$asMessages = $this->getMessages($asConstraints); $asMessages = $this->getMessages($asConstraints);
return end($asMessages)[Db::getId(self::MSG_TABLE)] ?? 0; return end($asMessages)[Db::getId(self::MSG_TABLE)] ?? 0;
} }
@@ -169,7 +169,7 @@ class Feed extends PhpObject {
$sTimeZone = date_default_timezone_get(); $sTimeZone = date_default_timezone_get();
$oDateTime = new \DateTime('@'.$iTimestamp); $oDateTime = new \DateTime('@'.$iTimestamp);
$oDateTime->setTimezone(new \DateTimeZone($sTimeZone)); $oDateTime->setTimezone(new \DateTimeZone($sTimeZone));
$asWeather = $this->getWeather(array($sLat, $sLng), $iTimestamp); $asWeather = $this->getWeather([$sLat, $sLng], $iTimestamp);
$asMsg = [ $asMsg = [
'ref_msg_id' => $iTimestamp.'/man', 'ref_msg_id' => $iTimestamp.'/man',
@@ -200,33 +200,33 @@ class Feed extends PhpObject {
//Fix unstable Spot API Structure //Fix unstable Spot API Structure
if(array_key_exists('message', $asMsgs)) $asMsgs = $asMsgs['message']; //Sometimes adds an extra "message" level 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 if(!array_key_exists(0, $asMsgs)) $asMsgs = [$asMsgs]; //Jumps a level when there is only 1 message
//Update Spot, Feed & Messages //Update Spot, Feed & Messages
if(!empty($asMsgs) && array_key_exists('messengerId', $asMsgs[0])) { if(!empty($asMsgs) && array_key_exists('messengerId', $asMsgs[0])) {
//Update Spot Info from the first message //Update Spot Info from the first message
$asSpotInfo = array( $asSpotInfo = [
'ref_spot_id' => $asMsgs[0]['messengerId'], 'ref_spot_id' => $asMsgs[0]['messengerId'],
'name' => $asMsgs[0]['messengerName'], 'name' => $asMsgs[0]['messengerName'],
'model' => $asMsgs[0]['modelId'] 'model' => $asMsgs[0]['modelId']
); ];
$iSpotId = $this->oDb->insertUpdateRow(self::SPOT_TABLE, $asSpotInfo, array('ref_spot_id')); $iSpotId = $this->oDb->insertUpdateRow(self::SPOT_TABLE, $asSpotInfo, ['ref_spot_id']);
//Update Feed Info and last update date //Update Feed Info and last update date
$asFeedInfo = array( $asFeedInfo = [
'ref_feed_id' => $asFeed['id'], 'ref_feed_id' => $asFeed['id'],
Db::getId(self::SPOT_TABLE) => $iSpotId, Db::getId(self::SPOT_TABLE) => $iSpotId,
'name' => $asFeed['name'], 'name' => $asFeed['name'],
'description' => $asFeed['description'], 'description' => $asFeed['description'],
'status' => $asFeed['status'], 'status' => $asFeed['status'],
'last_update' => $sNow 'last_update' => $sNow
); ];
$iFeedId = $this->oDb->insertUpdateRow(self::FEED_TABLE, $asFeedInfo, array('ref_feed_id')); $iFeedId = $this->oDb->insertUpdateRow(self::FEED_TABLE, $asFeedInfo, ['ref_feed_id']);
//Update Messages //Update Messages
foreach($asMsgs as $asMsg) { foreach($asMsgs as $asMsg) {
$asMsg = array( $asMsg = [
'ref_msg_id' => $asMsg['id'], 'ref_msg_id' => $asMsg['id'],
Db::getId(self::FEED_TABLE) => $iFeedId, Db::getId(self::FEED_TABLE) => $iFeedId,
'type' => $asMsg['messageType'], 'type' => $asMsg['messageType'],
@@ -238,15 +238,15 @@ class Feed extends PhpObject {
'unix_time' => $asMsg['unixTime'], //UNIX Time (backup) 'unix_time' => $asMsg['unixTime'], //UNIX Time (backup)
'content' => $asMsg['messageContent'], 'content' => $asMsg['messageContent'],
'battery_state' => $asMsg['batteryState'] 'battery_state' => $asMsg['batteryState']
); ];
$iMsgId = $this->oDb->selectId(self::MSG_TABLE, array('ref_msg_id'=>$asMsg['ref_msg_id'])); $iMsgId = $this->oDb->selectId(self::MSG_TABLE, ['ref_msg_id'=>$asMsg['ref_msg_id']]);
if(!$iMsgId) { if(!$iMsgId) {
//First Catch //First Catch
$asMsg['posted_on'] = $sNow; $asMsg['posted_on'] = $sNow;
//Weather Data //Weather Data
$asMsg = array_merge($asMsg, $this->getWeather(array($asMsg['latitude'], $asMsg['longitude']), $asMsg['unix_time'])); $asMsg = array_merge($asMsg, $this->getWeather([$asMsg['latitude'], $asMsg['longitude']], $asMsg['unix_time']));
$this->oDb->insertRow(self::MSG_TABLE, $asMsg); $this->oDb->insertRow(self::MSG_TABLE, $asMsg);
$bNewMsg = true; $bNewMsg = true;
@@ -255,7 +255,7 @@ class Feed extends PhpObject {
} }
} }
} }
else $this->oDb->updateRow(self::FEED_TABLE, $this->getFeedId(), array('last_update'=>$sNow)); else $this->oDb->updateRow(self::FEED_TABLE, $this->getFeedId(), ['last_update'=>$sNow]);
return $bNewMsg; return $bNewMsg;
} }
@@ -281,19 +281,19 @@ class Feed extends PhpObject {
//Get Condition Language ID //Get Condition Language ID
$sCondLangId = (new Translator(self::WEATHER_PARAM['lang']))->getTranslationKey($sWeatherCond); $sCondLangId = (new Translator(self::WEATHER_PARAM['lang']))->getTranslationKey($sWeatherCond);
return array( return [
'weather_icon' => $sWeatherIcon, 'weather_icon' => $sWeatherIcon,
'weather_cond' => $sCondLangId, 'weather_cond' => $sCondLangId,
'weather_temp' => floatval($sWeatherTemp) 'weather_temp' => floatval($sWeatherTemp)
); ];
} }
private function getTimeZone($iLat, $iLng) { private function getTimeZone($iLat, $iLng) {
$asParams = array( $asParams = [
'username' => Settings::TIMEZONE_USER, 'username' => Settings::TIMEZONE_USER,
'lat' => $iLat, 'lat' => $iLat,
'lng' => $iLng 'lng' => $iLng
); ];
$sApiUrl = self::TIMEZONE_HOOK.'?'.http_build_query($asParams); $sApiUrl = self::TIMEZONE_HOOK.'?'.http_build_query($asParams);
$asTimeZone = json_decode(file_get_contents($sApiUrl), true); $asTimeZone = json_decode(file_get_contents($sApiUrl), true);
@@ -314,7 +314,7 @@ class Feed extends PhpObject {
} }
private function updateField($sField, $oValue) { private function updateField($sField, $oValue) {
$bResult = ($this->oDb->updateRow(self::FEED_TABLE, $this->getFeedId(), array($sField=>$oValue)) > 0); $bResult = ($this->oDb->updateRow(self::FEED_TABLE, $this->getFeedId(), [$sField=>$oValue]) > 0);
$this->setFeedId($this->getFeedId()); $this->setFeedId($this->getFeedId());
return $bResult; return $bResult;
@@ -323,8 +323,8 @@ class Feed extends PhpObject {
public function delete() { public function delete() {
$bSuccess = false; $bSuccess = false;
$sLangId = ''; $sLangId = '';
$asLangParams = array(); $asLangParams = [];
$asData = array(); $asData = [];
if($this->getFeedId() > 0) { if($this->getFeedId() > 0) {
$asData['id'] = $this->getFeedId(); $asData['id'] = $this->getFeedId();
@@ -333,7 +333,7 @@ class Feed extends PhpObject {
} }
else { else {
$sLangId = 'error.impossible_value'; $sLangId = 'error.impossible_value';
$asLangParams = array($this->getFeedId(), 'feed ID'); $asLangParams = [$this->getFeedId(), 'feed ID'];
} }
return Livetrail::getResult($bSuccess, $sLangId, $asData, $asLangParams); return Livetrail::getResult($bSuccess, $sLangId, $asData, $asLangParams);
+3 -3
View File
@@ -7,8 +7,8 @@ use \Settings;
abstract class Geo extends PhpObject { abstract class Geo extends PhpObject {
protected const EXT = ''; protected const EXT = '';
const GEO_FOLDER = 'geo'; private const GEO_FOLDER = 'geo';
const OPT_SIMPLE = 'simplification'; private const OPT_SIMPLE = 'simplification';
protected array $asTracks; protected array $asTracks;
protected string $sFilePath; protected string $sFilePath;
@@ -16,7 +16,7 @@ abstract class Geo extends PhpObject {
public function __construct(string $sCodeName) { public function __construct(string $sCodeName) {
parent::__construct(get_class($this), Settings::DEBUG, PhpObject::MODE_HTML); parent::__construct(get_class($this), Settings::DEBUG, PhpObject::MODE_HTML);
$this->sFilePath = self::getBackEndFilePath($sCodeName); $this->sFilePath = self::getBackEndFilePath($sCodeName);
$this->asTracks = array(); $this->asTracks = [];
} }
//Access from backend //Access from backend
+24 -24
View File
@@ -3,11 +3,11 @@
namespace Franzz\Livetrail; namespace Franzz\Livetrail;
class GeoJson extends Geo { class GeoJson extends Geo {
protected const EXT = '.geojson';
const EXT = '.geojson'; private const MAX_FILESIZE = 2; //MB
const MAX_FILESIZE = 2; //MB private const MAX_DEVIATION_FLAT = 0.1; //10%
const MAX_DEVIATION_FLAT = 0.1; //10% private const MAX_DEVIATION_ELEV = 0.1; //10%
const MAX_DEVIATION_ELEV = 0.1; //10%
public function __construct($sCodeName) { public function __construct($sCodeName) {
parent::__construct($sCodeName); parent::__construct($sCodeName);
@@ -38,7 +38,7 @@ class GeoJson extends Geo {
$iGlobalInvalidPointCount = 0; $iGlobalInvalidPointCount = 0;
$iGlobalPointCount = 0; $iGlobalPointCount = 0;
$this->asTracks = array(); $this->asTracks = [];
foreach($asTracks as $asTrackProps) { foreach($asTracks as $asTrackProps) {
$asOptions = $this->parseOptions($asTrackProps['cmt']); $asOptions = $this->parseOptions($asTrackProps['cmt']);
@@ -64,18 +64,18 @@ class GeoJson extends Geo {
continue 2; //discard tracks continue 2; //discard tracks
} }
$asTrack = array( $asTrack = [
'type' => 'Feature', 'type' => 'Feature',
'properties' => array( 'properties' => [
'name' => $asTrackProps['name'], 'name' => $asTrackProps['name'],
'type' => $sType, 'type' => $sType,
'description' => $asTrackProps['desc'] 'description' => $asTrackProps['desc']
), ],
'geometry' => array( 'geometry' => [
'type' => 'LineString', 'type' => 'LineString',
'coordinates' => array() 'coordinates' => []
) ]
); ];
if($sType != 'hitchhiking' && str_contains($asTrackProps['desc'], ' ➜ ')) { if($sType != 'hitchhiking' && str_contains($asTrackProps['desc'], ' ➜ ')) {
list($sFrom, $sTo) = explode(' ➜ ', $asTrackProps['desc']); list($sFrom, $sTo) = explode(' ➜ ', $asTrackProps['desc']);
@@ -86,9 +86,9 @@ class GeoJson extends Geo {
$asTrackPoints = $asTrackProps['points']; $asTrackPoints = $asTrackProps['points'];
$iPointCount = count($asTrackPoints); $iPointCount = count($asTrackPoints);
$iInvalidPointCount = 0; $iInvalidPointCount = 0;
$asPrevPoint = array(); $asPrevPoint = [];
foreach($asTrackPoints as $iIndex=>$asPoint) { foreach($asTrackPoints as $iIndex=>$asPoint) {
$asNextPoint = ($iIndex < ($iPointCount - 1))?$asTrackPoints[$iIndex + 1]:array(); $asNextPoint = ($iIndex < ($iPointCount - 1))?$asTrackPoints[$iIndex + 1]:[];
if($bSimplify && !empty($asPrevPoint) && !empty($asNextPoint)) { if($bSimplify && !empty($asPrevPoint) && !empty($asNextPoint)) {
if(!$this->isPointValid($asPrevPoint, $asPoint, $asNextPoint)) { if(!$this->isPointValid($asPrevPoint, $asPoint, $asNextPoint)) {
$iInvalidPointCount++; $iInvalidPointCount++;
@@ -112,11 +112,11 @@ class GeoJson extends Geo {
$this->addNotice('Sorting off-tracks'); $this->addNotice('Sorting off-tracks');
//Find first & last track points //Find first & last track points
$asTracksEnds = array(); $asTracksEnds = [];
$asTracks = array(); $asTracks = [];
foreach($this->asTracks as $iTrackId=>$asTrack) { foreach($this->asTracks as $iTrackId=>$asTrack) {
$sTrackId = 't'.$iTrackId; $sTrackId = 't'.$iTrackId;
$asTracksEnds[$sTrackId] = array('first'=>reset($asTrack['geometry']['coordinates']), 'last'=>end($asTrack['geometry']['coordinates'])); $asTracksEnds[$sTrackId] = ['first'=>reset($asTrack['geometry']['coordinates']), 'last'=>end($asTrack['geometry']['coordinates'])];
$asTracks[$sTrackId] = $asTrack; $asTracks[$sTrackId] = $asTrack;
} }
@@ -153,14 +153,14 @@ class GeoJson extends Geo {
//Move track //Move track
unset($asTracks[$sTrackId]); unset($asTracks[$sTrackId]);
$iOffset = array_search($sConnectedTrackId, array_keys($asTracks)) + $iPosition; $iOffset = array_search($sConnectedTrackId, array_keys($asTracks)) + $iPosition;
$asTracks = array_slice($asTracks, 0, $iOffset) + array($sTrackId => $asTrack) + array_slice($asTracks, $iOffset); $asTracks = array_slice($asTracks, 0, $iOffset) + [$sTrackId => $asTrack] + array_slice($asTracks, $iOffset);
} }
$this->asTracks = array_values($asTracks); $this->asTracks = array_values($asTracks);
} }
public function getCenter() { public function getCenter() {
$asCoords = array(); $asCoords = [];
$asMainTracks = array_filter($this->asTracks, function ($astrack) {return $astrack['properties']['type'] == 'main';}); $asMainTracks = array_filter($this->asTracks, function ($astrack) {return $astrack['properties']['type'] == 'main';});
foreach($asMainTracks as $asMainTrack) { foreach($asMainTracks as $asMainTrack) {
foreach($asMainTrack['geometry']['coordinates'] as $aiCoords) { foreach($asMainTrack['geometry']['coordinates'] as $aiCoords) {
@@ -173,7 +173,7 @@ class GeoJson extends Geo {
private function parseOptions($sComment) { private function parseOptions($sComment) {
$sComment = strip_tags(html_entity_decode($sComment)); $sComment = strip_tags(html_entity_decode($sComment));
$asOptions = array(self::OPT_SIMPLE=>''); $asOptions = [self::OPT_SIMPLE=>''];
foreach(explode("\n", $sComment) as $sLine) { foreach(explode("\n", $sComment) as $sLine) {
$asOptions[mb_strtolower(trim(mb_strstr($sLine, ':', true)))] = mb_strtolower(trim(mb_substr(mb_strstr($sLine, ':'), 1))); $asOptions[mb_strtolower(trim(mb_strstr($sLine, ':', true)))] = mb_strtolower(trim(mb_substr(mb_strstr($sLine, ':'), 1)));
} }
@@ -189,8 +189,8 @@ class GeoJson extends Geo {
//Path Turn Check -> -> -> -> //Path Turn Check -> -> -> ->
//Law of Cosines (vector): angle = arccos(OA.OB / ||OA||.||OB||) //Law of Cosines (vector): angle = arccos(OA.OB / ||OA||.||OB||)
$fVectorOA = array('lon'=>($asPointA['lon'] - $asPointO['lon']), 'lat'=> ($asPointA['lat'] - $asPointO['lat'])); $fVectorOA = ['lon'=>($asPointA['lon'] - $asPointO['lon']), 'lat'=> ($asPointA['lat'] - $asPointO['lat'])];
$fVectorOB = array('lon'=>($asPointB['lon'] - $asPointO['lon']), 'lat'=> ($asPointB['lat'] - $asPointO['lat'])); $fVectorOB = ['lon'=>($asPointB['lon'] - $asPointO['lon']), 'lat'=> ($asPointB['lat'] - $asPointO['lat'])];
$fLengthOA = sqrt(pow($asPointA['lon'] - $asPointO['lon'], 2) + pow($asPointA['lat'] - $asPointO['lat'], 2)); $fLengthOA = sqrt(pow($asPointA['lon'] - $asPointO['lon'], 2) + pow($asPointA['lat'] - $asPointO['lat'], 2));
$fLengthOB = sqrt(pow($asPointO['lon'] - $asPointB['lon'], 2) + pow($asPointO['lat'] - $asPointB['lat'], 2)); $fLengthOB = sqrt(pow($asPointO['lon'] - $asPointB['lon'], 2) + pow($asPointO['lat'] - $asPointB['lat'], 2));
@@ -210,10 +210,10 @@ class GeoJson extends Geo {
} }
private function buildGeoJson() { private function buildGeoJson() {
return json_encode(array('type'=>'FeatureCollection', 'features'=>$this->asTracks)); return json_encode(['type'=>'FeatureCollection', 'features'=>$this->asTracks]);
} }
private static function getDistance($asPointA, $asPointB) { private static function getDistance($asPointA, $asPointB) {
$fLatFrom = $asPointA[1]; $fLatFrom = $asPointA[1];
$fLonFrom = $asPointA[0]; $fLonFrom = $asPointA[0];
$fLatTo = $asPointB[1]; $fLatTo = $asPointB[1];
+6 -6
View File
@@ -5,7 +5,7 @@ use Franzz\Objects\ToolBox;
class Gpx extends Geo { class Gpx extends Geo {
const EXT = '.gpx'; public const EXT = '.gpx';
public function __construct($sCodeName) { public function __construct($sCodeName) {
parent::__construct($sCodeName); parent::__construct($sCodeName);
@@ -25,21 +25,21 @@ class Gpx extends Geo {
//Tracks //Tracks
$this->addNotice('Converting '.count($oXml->trk).' tracks'); $this->addNotice('Converting '.count($oXml->trk).' tracks');
foreach($oXml->trk as $aoTrack) { foreach($oXml->trk as $aoTrack) {
$asTrack = array( $asTrack = [
'name' => (string) $aoTrack->name, 'name' => (string) $aoTrack->name,
'desc' => str_replace("\n", '', ToolBox::fixEOL((strip_tags($aoTrack->desc)))), 'desc' => str_replace("\n", '', ToolBox::fixEOL((strip_tags($aoTrack->desc)))),
'cmt' => ToolBox::fixEOL((strip_tags($aoTrack->cmt))), 'cmt' => ToolBox::fixEOL((strip_tags($aoTrack->cmt))),
'color' => (string) $aoTrack->extensions->children('gpxx', true)->TrackExtension->DisplayColor, 'color' => (string) $aoTrack->extensions->children('gpxx', true)->TrackExtension->DisplayColor,
'points'=> array() 'points'=> []
); ];
foreach($aoTrack->trkseg as $asSegment) { foreach($aoTrack->trkseg as $asSegment) {
foreach($asSegment as $asPoint) { foreach($asSegment as $asPoint) {
$asTrack['points'][] = array( $asTrack['points'][] = [
'lon' => (float) $asPoint['lon'], 'lon' => (float) $asPoint['lon'],
'lat' => (float) $asPoint['lat'], 'lat' => (float) $asPoint['lat'],
'ele' => (int) $asPoint->ele 'ele' => (int) $asPoint->ele
); ];
} }
} }
$this->asTracks[] = $asTrack; $this->asTracks[] = $asTrack;
+203 -215
View File
@@ -5,7 +5,6 @@ use Franzz\Objects\Db;
use Franzz\Objects\Main; use Franzz\Objects\Main;
use Franzz\Objects\Translator; use Franzz\Objects\Translator;
use Franzz\Objects\ToolBox; use Franzz\Objects\ToolBox;
use Franzz\Objects\Mask;
use \Settings; use \Settings;
/* Timezones /* Timezones
@@ -32,27 +31,25 @@ use \Settings;
* - timezone: Site Timezone (stored user's timezone for emails) * - timezone: Site Timezone (stored user's timezone for emails)
*/ */
class Livetrail extends Main class Livetrail extends Main {
{
//Database //Database
const POST_TABLE = 'posts'; public const POST_TABLE = 'posts';
const FEED_CHUNK_SIZE = 15; private const FEED_CHUNK_SIZE = 15;
const MAIL_CHUNK_SIZE = 5; private const MAIL_CHUNK_SIZE = 5;
const DEFAULT_LANG = 'en'; public const DEFAULT_LANG = 'en';
const PROJECT_NAME = 'LiveTrail'; public const PROJECT_NAME = 'LiveTrail';
const MAIN_PAGE = 'index'; private const MAIN_PAGE = 'index';
const VITE_APP = 'src/app.js'; private const VITE_APP = 'src/app.js';
private Project $oProject; private Project $oProject;
private Media $oMedia; private Media $oMedia;
private User $oUser; private User $oUser;
private Map $oMap; private Map $oMap;
public function __construct($sProcessPage, $sTimezone) public function __construct($sProcessPage, $sTimezone) {
{
parent::__construct($sProcessPage, true, $sTimezone); parent::__construct($sProcessPage, true, $sTimezone);
$this->oUser = new User($this->oDb); $this->oUser = new User($this->oDb);
@@ -65,116 +62,114 @@ class Livetrail extends Main
$this->oMap = new Map($this->oDb); $this->oMap = new Map($this->oDb);
} }
protected function install() protected function install() {
{
//Install DB //Install DB
$this->oDb->install(); $this->oDb->install();
//Add first user //Add first user
$iUserId = $this->oDb->insertRow(User::USER_TABLE, array( $iUserId = $this->oDb->insertRow(User::USER_TABLE, [
'name' => 'Admin', 'name' => 'Admin',
'email' => 'admin@admin.com', 'email' => 'admin@admin.com',
'language' => self::DEFAULT_LANG, 'language' => self::DEFAULT_LANG,
'timezone' => date_default_timezone_get(), 'timezone' => date_default_timezone_get(),
'subscribed'=> User::USER_SUBSCRIBED, 'subscribed'=> User::USER_SUBSCRIBED,
'clearance' => User::CLEARANCE_ADMIN 'clearance' => User::CLEARANCE_ADMIN
)); ]);
$this->oUser->setUserId($iUserId); $this->oUser->setUserId($iUserId);
} }
protected function getSqlOptions() protected function getSqlOptions() {
{ return
return array [
( 'tables' =>
'tables' => array [
( Feed::MSG_TABLE => ['ref_msg_id', Db::getId(Feed::FEED_TABLE), 'type', 'latitude', 'longitude', 'iso_time', 'site_time', 'timezone', 'unix_time', 'content', 'battery_state', 'posted_on', 'weather_icon', 'weather_cond', 'weather_temp', 'display'],
Feed::MSG_TABLE => array('ref_msg_id', Db::getId(Feed::FEED_TABLE), 'type', 'latitude', 'longitude', 'iso_time', 'site_time', 'timezone', 'unix_time', 'content', 'battery_state', 'posted_on', 'weather_icon', 'weather_cond', 'weather_temp', 'display'), Feed::FEED_TABLE => ['ref_feed_id', Db::getId(Feed::SPOT_TABLE), Db::getId(Project::PROJ_TABLE), 'name', 'description', 'status', 'last_update'],
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 => ['ref_spot_id', 'name', 'model'],
Feed::SPOT_TABLE => array('ref_spot_id', 'name', 'model'), Project::PROJ_TABLE => ['name', 'codename', 'active_from', 'active_to'],
Project::PROJ_TABLE => array('name', 'codename', 'active_from', 'active_to'), self::POST_TABLE => [Db::getId(Project::PROJ_TABLE), Db::getId(User::USER_TABLE), 'name', 'content', 'site_time', 'timezone'],
self::POST_TABLE => array(Db::getId(Project::PROJ_TABLE), Db::getId(User::USER_TABLE), 'name', 'content', 'site_time', 'timezone'), Media::MEDIA_TABLE => [Db::getId(Project::PROJ_TABLE), 'filename', 'type', 'taken_on', 'posted_on', 'timezone', 'latitude', 'longitude', 'altitude', 'width', 'height', 'rotate', 'comment'],
Media::MEDIA_TABLE => array(Db::getId(Project::PROJ_TABLE), 'filename', 'type', 'taken_on', 'posted_on', 'timezone', 'latitude', 'longitude', 'altitude', 'width', 'height', 'rotate', 'comment'), User::USER_TABLE => ['name', 'email', 'password', 'token', 'token_exp', 'gravatar', 'language', 'timezone', 'subscribed', 'clearance'],
User::USER_TABLE => array('name', 'email', 'password', 'token', 'token_exp', 'gravatar', 'language', 'timezone', 'subscribed', 'clearance'), Map::MAP_TABLE => ['codename', 'pattern', 'token', 'tile_size', 'min_zoom', 'max_zoom', 'attribution'],
Map::MAP_TABLE => array('codename', 'pattern', 'token', 'tile_size', 'min_zoom', 'max_zoom', 'attribution'), Map::MAPPING_TABLE => [Db::getId(Map::MAP_TABLE) , Db::getId(Project::PROJ_TABLE)]
Map::MAPPING_TABLE => array(Db::getId(Map::MAP_TABLE) , Db::getId(Project::PROJ_TABLE)) ],
), 'types' =>
'types' => array [
( 'clearance' => 'TINYINT(1) DEFAULT '.User::CLEARANCE_USER,
'clearance' => "TINYINT(1) DEFAULT ".User::CLEARANCE_USER, 'active_from' => 'TIMESTAMP DEFAULT 0',
'active_from' => "TIMESTAMP DEFAULT 0", 'active_to' => 'TIMESTAMP DEFAULT 0',
'active_to' => "TIMESTAMP DEFAULT 0", 'battery_state' => 'VARCHAR(10)',
'battery_state' => "VARCHAR(10)", 'codename' => 'VARCHAR(100)',
'codename' => "VARCHAR(100)", 'content' => 'LONGTEXT',
'content' => "LONGTEXT", 'comment' => 'LONGTEXT',
'comment' => "LONGTEXT", 'description' => 'VARCHAR(100)',
'description' => "VARCHAR(100)", 'email' => 'VARCHAR(320) NOT NULL',
'email' => "VARCHAR(320) NOT NULL", 'filename' => 'VARCHAR(100) NOT NULL',
'filename' => "VARCHAR(100) NOT NULL", 'iso_time' => 'VARCHAR(24)',
'iso_time' => "VARCHAR(24)", 'language' => 'VARCHAR(2)',
'language' => "VARCHAR(2)", 'last_update' => 'TIMESTAMP DEFAULT 0',
'last_update' => "TIMESTAMP DEFAULT 0", 'latitude' => 'DECIMAL(8,6)',
'latitude' => "DECIMAL(8,6)", 'longitude' => 'DECIMAL(9,6)',
'longitude' => "DECIMAL(9,6)", 'altitude' => 'SMALLINT',
'altitude' => "SMALLINT", 'model' => 'VARCHAR(20)',
'model' => "VARCHAR(20)", 'name' => 'VARCHAR(100)',
'name' => "VARCHAR(100)", 'pattern' => 'VARCHAR(200) NOT NULL',
'pattern' => "VARCHAR(200) NOT NULL",
'password' => "VARCHAR(255) NOT NULL DEFAULT ''", 'password' => "VARCHAR(255) NOT NULL DEFAULT ''",
'posted_on' => "TIMESTAMP DEFAULT 0", 'posted_on' => 'TIMESTAMP DEFAULT 0',
'ref_feed_id' => "VARCHAR(40)", 'ref_feed_id' => 'VARCHAR(40)',
'ref_msg_id' => "VARCHAR(15)", 'ref_msg_id' => 'VARCHAR(15)',
'ref_spot_id' => "VARCHAR(10)", 'ref_spot_id' => 'VARCHAR(10)',
'rotate' => "SMALLINT", 'rotate' => 'SMALLINT',
'site_time' => "TIMESTAMP DEFAULT 0", //DEFAULT 0 removes auto-set to current time 'site_time' => 'TIMESTAMP DEFAULT 0', //DEFAULT 0 removes auto-set to current time
'status' => "VARCHAR(10)", 'status' => 'VARCHAR(10)',
'subscribed' => "BOOLEAN DEFAULT ".User::USER_UNSUBSCRIBED, 'subscribed' => 'BOOLEAN DEFAULT '.User::USER_UNSUBSCRIBED,
'taken_on' => "TIMESTAMP DEFAULT 0", 'taken_on' => 'TIMESTAMP DEFAULT 0',
'timezone' => "CHAR(64) NOT NULL", //see mysql.time_zone_name 'timezone' => 'CHAR(64) NOT NULL', //see mysql.time_zone_name
'token' => "VARCHAR(4096)", 'token' => 'VARCHAR(4096)',
'token_exp' => "TIMESTAMP DEFAULT 0", 'token_exp' => 'TIMESTAMP DEFAULT 0',
'type' => "VARCHAR(20)", 'type' => 'VARCHAR(20)',
'unix_time' => "INT", 'unix_time' => 'INT',
'min_zoom' => "TINYINT UNSIGNED", 'min_zoom' => 'TINYINT UNSIGNED',
'max_zoom' => "TINYINT UNSIGNED", 'max_zoom' => 'TINYINT UNSIGNED',
'attribution' => "VARCHAR(100)", 'attribution' => 'VARCHAR(100)',
'gravatar' => "LONGTEXT", 'gravatar' => 'LONGTEXT',
'weather_icon' => "VARCHAR(30)", 'weather_icon' => 'VARCHAR(30)',
'weather_cond' => "VARCHAR(30)", 'weather_cond' => 'VARCHAR(30)',
'weather_temp' => "DECIMAL(3,1)", 'weather_temp' => 'DECIMAL(3,1)',
'tile_size' => "SMALLINT UNSIGNED DEFAULT 256", 'tile_size' => 'SMALLINT UNSIGNED DEFAULT 256',
'width' => "INT", 'width' => 'INT',
'height' => "INT", 'height' => 'INT',
'display' => "BOOLEAN DEFAULT ".Feed::MSG_DISPLAYED 'display' => 'BOOLEAN DEFAULT '.Feed::MSG_DISPLAYED
), ],
'constraints' => array 'constraints' =>
( [
Feed::MSG_TABLE => array("UNIQUE KEY `uni_ref_msg_id` (`ref_msg_id`)", "INDEX(`ref_msg_id`)"), Feed::MSG_TABLE => ['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::FEED_TABLE => ['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`)"), Feed::SPOT_TABLE => ['UNIQUE KEY `uni_ref_spot_id` (`ref_spot_id`)', 'INDEX(`ref_spot_id`)'],
Project::PROJ_TABLE => "UNIQUE KEY `uni_proj_name` (`codename`)", Project::PROJ_TABLE => 'UNIQUE KEY `uni_proj_name` (`codename`)',
Media::MEDIA_TABLE => "UNIQUE KEY `uni_file_name` (`filename`)", Media::MEDIA_TABLE => 'UNIQUE KEY `uni_file_name` (`filename`)',
User::USER_TABLE => "UNIQUE KEY `uni_email` (`email`)", User::USER_TABLE => 'UNIQUE KEY `uni_email` (`email`)',
Map::MAP_TABLE => "UNIQUE KEY `uni_map_name` (`codename`)", Map::MAP_TABLE => 'UNIQUE KEY `uni_map_name` (`codename`)',
Map::MAPPING_TABLE => "default_on_generic_map_only CHECK (`default_map` = 0 OR `id_project` IS NULL)" Map::MAPPING_TABLE => 'default_on_generic_map_only CHECK (`default_map` = 0 OR `id_project` IS NULL)'
), ],
'cascading_delete' => array 'cascading_delete' =>
( [
Feed::SPOT_TABLE => array(Feed::FEED_TABLE), Feed::SPOT_TABLE => [Feed::FEED_TABLE],
Feed::FEED_TABLE => array(Feed::MSG_TABLE), Feed::FEED_TABLE => [Feed::MSG_TABLE],
Project::PROJ_TABLE => array(Feed::FEED_TABLE, Media::MEDIA_TABLE, self::POST_TABLE, Map::MAPPING_TABLE), Project::PROJ_TABLE => [Feed::FEED_TABLE, Media::MEDIA_TABLE, self::POST_TABLE, Map::MAPPING_TABLE],
Map::MAP_TABLE => array(Map::MAPPING_TABLE) Map::MAP_TABLE => [Map::MAPPING_TABLE]
) ]
); ];
} }
public function getAppMainPage(string $sCsrfToken='') { public function getAppMainPage(string $sCsrfToken='') {
$asViteAssets = $this->getViteAssets(); $asViteAssets = $this->getViteAssets();
return parent::getMainPage( return parent::getMainPage(
array( [
'projects' => $this->oProject->getProjects(), 'projects' => $this->oProject->getProjects(),
'user' => $this->oUser->getUserInfo(), 'user' => $this->oUser->getUserInfo(),
'consts' => array( 'consts' => [
'modes' => Project::MODES, 'modes' => Project::MODES,
'clearances' => User::CLEARANCES, 'clearances' => User::CLEARANCES,
'default_timezone' => Settings::TIMEZONE, 'default_timezone' => Settings::TIMEZONE,
@@ -184,10 +179,10 @@ class Livetrail extends Main
'title' => self::PROJECT_NAME, 'title' => self::PROJECT_NAME,
'default_page' => 'project', 'default_page' => 'project',
'csrf_token' => $sCsrfToken 'csrf_token' => $sCsrfToken
) ]
), ],
self::MAIN_PAGE, self::MAIN_PAGE,
array( [
'tags' => [ 'tags' => [
'language' => $this->oLang->getLanguage(), 'language' => $this->oLang->getLanguage(),
'title' => self::PROJECT_NAME, 'title' => self::PROJECT_NAME,
@@ -197,7 +192,7 @@ class Livetrail extends Main
'css' => $asViteAssets['css'], 'css' => $asViteAssets['css'],
'module' => $asViteAssets['module'] 'module' => $asViteAssets['module']
] ]
) ]
); );
} }
@@ -206,31 +201,31 @@ class Livetrail extends Main
$asAppImport = $asManifest[self::VITE_APP]; $asAppImport = $asManifest[self::VITE_APP];
//Recursive search for chunk imports //Recursive search for chunk imports
$asImports = array(); $asImports = [];
$asSeenImports = array(self::VITE_APP => true); $asSeenImports = [self::VITE_APP => true];
$this->appendViteImportedChunks($asManifest, $asAppImport, $asSeenImports, $asImports); $this->appendViteImportedChunks($asManifest, $asAppImport, $asSeenImports, $asImports);
//CSS //CSS
$asCssFiles = array(); $asCssFiles = [];
foreach(array_merge(array($asAppImport), $asImports) as $asChunk) { foreach(array_merge([$asAppImport], $asImports) as $asChunk) {
foreach($asChunk['css'] ?? array() as $sCssFile) $asCssFiles[] = $sCssFile; foreach($asChunk['css'] ?? [] as $sCssFile) $asCssFiles[] = $sCssFile;
} }
//Modules //Modules
$asModuleFiles = array(); $asModuleFiles = [];
foreach($asImports as $asImport) { foreach($asImports as $asImport) {
if(str_ends_with($asImport['file'] ?? '', '.js')) $asModuleFiles[] = $asImport['file']; if(str_ends_with($asImport['file'] ?? '', '.js')) $asModuleFiles[] = $asImport['file'];
} }
return array( return [
'app' => $asAppImport['file'], 'app' => $asAppImport['file'],
'css' => $this->getViteAssetInstances($asCssFiles), 'css' => $this->getViteAssetInstances($asCssFiles),
'module' => $this->getViteAssetInstances($asModuleFiles) 'module' => $this->getViteAssetInstances($asModuleFiles)
); ];
} }
private function appendViteImportedChunks($asManifest, $asChunk, &$asSeenImports, &$asImports) { private function appendViteImportedChunks($asManifest, $asChunk, &$asSeenImports, &$asImports) {
foreach($asChunk['imports'] ?? array() as $sImport) { foreach($asChunk['imports'] ?? [] as $sImport) {
if(isset($asSeenImports[$sImport]) || !isset($asManifest[$sImport])) continue; if(isset($asSeenImports[$sImport]) || !isset($asManifest[$sImport])) continue;
$asSeenImports[$sImport] = true; $asSeenImports[$sImport] = true;
@@ -241,7 +236,7 @@ class Livetrail extends Main
private function getViteAssetInstances($asFilePaths) { private function getViteAssetInstances($asFilePaths) {
return array_map( return array_map(
function($sFilePath) { return array('filename' => $sFilePath); }, function($sFilePath) { return ['filename' => $sFilePath]; },
$asFilePaths $asFilePaths
); );
} }
@@ -283,7 +278,7 @@ class Livetrail extends Main
$oEmail->setDestInfo($this->oUser->getSubscribedUsersInfo()); $oEmail->setDestInfo($this->oUser->getSubscribedUsersInfo());
//Add Position //Add Position
$asSpotMessages = $this->getSpotMessages(array($this->oProject->getLastMessageId($this->getFeedConstraints(Feed::MSG_TABLE)))); $asSpotMessages = $this->getSpotMessages([$this->oProject->getLastMessageId($this->getFeedConstraints(Feed::MSG_TABLE))]);
$asLastMessage = array_shift($asSpotMessages); $asLastMessage = array_shift($asSpotMessages);
$oEmail->oTemplate->setTags($asLastMessage); $oEmail->oTemplate->setTags($asLastMessage);
$oEmail->oTemplate->setTag('date_time', 'time:'.$asLastMessage['unix_time'], 'd/m/Y, H:i'); $oEmail->oTemplate->setTag('date_time', 'time:'.$asLastMessage['unix_time'], 'd/m/Y, H:i');
@@ -294,11 +289,11 @@ class Livetrail extends Main
foreach($asNews as $asPost) { foreach($asNews as $asPost) {
if($asPost['type'] != 'message') { if($asPost['type'] != 'message') {
$oEmail->oTemplate->newInstance('news'); $oEmail->oTemplate->newInstance('news');
$oEmail->oTemplate->setInstanceTags('news', array( $oEmail->oTemplate->setInstanceTags('news', [
'local_server' => $this->asContext['serv_name'], 'local_server' => $this->asContext['serv_name'],
'project' => $this->oProject->getProjectCodeName(), 'project' => $this->oProject->getProjectCodeName(),
'type' => $asPost['type'], 'type' => $asPost['type'],
'id' => $asPost['id_'.$asPost['type']]) 'id' => $asPost['id_'.$asPost['type']]]
); );
$oEmail->oTemplate->addInstance($asPost['type'], $asPost); $oEmail->oTemplate->addInstance($asPost['type'], $asPost);
$oEmail->oTemplate->setInstanceTag($asPost['type'], 'local_server', $this->asContext['serv_name']); $oEmail->oTemplate->setInstanceTag($asPost['type'], 'local_server', $this->asContext['serv_name']);
@@ -310,8 +305,7 @@ class Livetrail extends Main
return $oEmail->send(); return $oEmail->send();
} }
public function getMarkers($asMessageIds=array(), $asMediaIds=array(), $bInternal=false) public function getMarkers($asMessageIds=[], $asMediaIds=[], $bInternal=false) {
{
//Get messages //Get messages
$asMessages = $this->getSpotMessages($asMessageIds); $asMessages = $this->getSpotMessages($asMessageIds);
foreach($asMessages as &$asMessage) { foreach($asMessages as &$asMessage) {
@@ -337,8 +331,8 @@ class Livetrail extends Main
//Assign medias to closest message //Assign medias to closest message
if(!empty($asMessages)) { if(!empty($asMessages)) {
usort($asMessages, function($a, $b){return (int) $a['unix_time'] <=> (int) $b['unix_time'];}); usort($asMessages, function($a, $b) {return (int) $a['unix_time'] <=> (int) $b['unix_time'];});
usort($asMedias, function($a, $b){return (int) $a['unix_time'] <=> (int) $b['unix_time'];}); usort($asMedias, function($a, $b) {return (int) $a['unix_time'] <=> (int) $b['unix_time'];});
$iIndex = 0; $iIndex = 0;
$iMaxIndex = count($asMessages) - 1; $iMaxIndex = count($asMessages) - 1;
@@ -359,18 +353,18 @@ class Livetrail extends Main
//Combine markers //Combine markers
$asMarkers = [...$asMessages, ...$asGeoMedias]; $asMarkers = [...$asMessages, ...$asGeoMedias];
usort($asMarkers, function($a, $b){return (int) $a['unix_time'] <=> (int) $b['unix_time'];}); usort($asMarkers, function($a, $b) {return (int) $a['unix_time'] <=> (int) $b['unix_time'];});
$asResult = array( $asResult = [
'markers' => $asMarkers, 'markers' => $asMarkers,
'maps' => $this->oMap->getProjectMaps($this->oProject->getProjectId()) 'maps' => $this->oMap->getProjectMaps($this->oProject->getProjectId())
); ];
return $bInternal?$asResult:self::getJsonResult(true, '', $asResult); return $bInternal?$asResult:self::getJsonResult(true, '', $asResult);
} }
public function getLastUpdate() { public function getLastUpdate() {
$asLastUpdate = array(); $asLastUpdate = [];
$this->addTimeStamp($asLastUpdate, $this->oProject->getLastUpdate()); $this->addTimeStamp($asLastUpdate, $this->oProject->getLastUpdate());
return self::getJsonResult(true, '', $asLastUpdate); return self::getJsonResult(true, '', $asLastUpdate);
} }
@@ -403,30 +397,28 @@ class Livetrail extends Main
return self::getJsonResult($asResult['result'], $asResult['desc_lang_id'], $this->oUser->getUserInfo(), $asResult['desc_lang_params']); return self::getJsonResult($asResult['result'], $asResult['desc_lang_id'], $this->oUser->getUserInfo(), $asResult['desc_lang_params']);
} }
private function getSpotMessages($asMsgIds=array()) private function getSpotMessages($asMsgIds=[]) {
{
$asConstraints = $this->getFeedConstraints(Feed::MSG_TABLE); $asConstraints = $this->getFeedConstraints(Feed::MSG_TABLE);
if(!empty($asMsgIds)) { if(!empty($asMsgIds)) {
$asConstraints['constraint'][Db::getId(Feed::MSG_TABLE)] = $asMsgIds; $asConstraints['constraint'][Db::getId(Feed::MSG_TABLE)] = $asMsgIds;
$asConstraints['constOpe'][Db::getId(Feed::MSG_TABLE)] = 'IN'; $asConstraints['constOpe'][Db::getId(Feed::MSG_TABLE)] = 'IN';
} }
$asCombinedMessages = array(); $asCombinedMessages = [];
//Get messages from all feeds belonging to the project //Get messages from all feeds belonging to the project
$asFeeds = $this->oProject->getFeedIds(); $asFeeds = $this->oProject->getFeedIds();
foreach($asFeeds as $iFeedId) { foreach($asFeeds as $iFeedId) {
$oFeed = new Feed($this->oDb, $iFeedId); $oFeed = new Feed($this->oDb, $iFeedId);
$asMessages = $oFeed->getMessages($asConstraints); $asMessages = $oFeed->getMessages($asConstraints);
foreach($asMessages as $asMessage) foreach($asMessages as $asMessage) {
{
$asMessage['latitude'] = floatval($asMessage['latitude']); $asMessage['latitude'] = floatval($asMessage['latitude']);
$asMessage['longitude'] = floatval($asMessage['longitude']); $asMessage['longitude'] = floatval($asMessage['longitude']);
$asMessage['lat_dms'] = self::decToDms($asMessage['latitude'], 'lat'); $asMessage['lat_dms'] = self::decToDms($asMessage['latitude'], 'lat');
$asMessage['lon_dms'] = self::decToDms($asMessage['longitude'], 'lon'); $asMessage['lon_dms'] = self::decToDms($asMessage['longitude'], 'lon');
$asMessage['displayed_id'] = $asMessage[Db::getId(Feed::MSG_TABLE)]; $asMessage['displayed_id'] = $asMessage[Db::getId(Feed::MSG_TABLE)];
$asMessage['static_img_url'] = $this->oMap->getMapUrl('static', array('x'=>$asMessage['longitude'], 'y'=>$asMessage['latitude'])); $asMessage['static_img_url'] = $this->oMap->getMapUrl('static', ['x'=>$asMessage['longitude'], 'y'=>$asMessage['latitude']]);
$asMessage['marker_img_url'] = $this->oMap->getMapUrl('static_marker', array('x'=>$asMessage['longitude'], 'y'=>$asMessage['latitude'])); $asMessage['marker_img_url'] = $this->oMap->getMapUrl('static_marker', ['x'=>$asMessage['longitude'], 'y'=>$asMessage['latitude']]);
$this->addTimeStamp($asMessage, $asMessage['unix_time'], $asMessage['timezone']); $this->addTimeStamp($asMessage, $asMessage['unix_time'], $asMessage['timezone']);
$asCombinedMessages[] = $asMessage; $asCombinedMessages[] = $asMessage;
@@ -443,8 +435,7 @@ class Livetrail extends Main
* @param String $sTimeRefField Field to calculate relative times: 'taken_on' or 'posted_on' * @param String $sTimeRefField Field to calculate relative times: 'taken_on' or 'posted_on'
* @return Array Medias info * @return Array Medias info
*/ */
private function getMedias($sTimeRefField, $asMediaIds=array(), $bOnlyGeoMedia=false) private function getMedias($sTimeRefField, $asMediaIds=[], $bOnlyGeoMedia=false) {
{
//Constraints //Constraints
$asConstraints = $this->getFeedConstraints(Media::MEDIA_TABLE, $sTimeRefField); $asConstraints = $this->getFeedConstraints(Media::MEDIA_TABLE, $sTimeRefField);
if(!empty($asMediaIds)) { if(!empty($asMediaIds)) {
@@ -478,13 +469,12 @@ class Livetrail extends Main
return $asMedias; return $asMedias;
} }
private function getPosts($asPostIds=array()) private function getPosts($asPostIds=[]) {
{ $asInfo = [
$asInfo = array( 'select' => [Db::getFullColumnName(self::POST_TABLE, '*'), 'gravatar'],
'select' => array(Db::getFullColumnName(self::POST_TABLE, '*'), 'gravatar'),
'from' => self::POST_TABLE, 'from' => self::POST_TABLE,
'join' => array(User::USER_TABLE => Db::getId(User::USER_TABLE)) 'join' => [User::USER_TABLE => Db::getId(User::USER_TABLE)]
); ];
$asInfo = array_merge($asInfo, $this->getFeedConstraints(self::POST_TABLE)); $asInfo = array_merge($asInfo, $this->getFeedConstraints(self::POST_TABLE));
if(!empty($asPostIds)) { if(!empty($asPostIds)) {
@@ -518,35 +508,35 @@ class Livetrail extends Main
} }
private function getFeedConstraints($sType, $sTimeField='site_time', $sReturnFormat='array') { private function getFeedConstraints($sType, $sTimeField='site_time', $sReturnFormat='array') {
$asConsArray = array(); $asConsArray = [];
$sConsSql = ""; $sConsSql = '';
$asActPeriod = $this->oProject->getActivePeriod(); $asActPeriod = $this->oProject->getActivePeriod();
//Filter on Project ID //Filter on Project ID
$sConsSql = "WHERE ".Db::getId(Project::PROJ_TABLE)." = ".$this->oProject->getProjectId(); $sConsSql = 'WHERE '.Db::getId(Project::PROJ_TABLE).' = '.$this->oProject->getProjectId();
$asConsArray = array( $asConsArray = [
'constraint'=> array(Db::getId(Project::PROJ_TABLE) => $this->oProject->getProjectId()), 'constraint'=> [Db::getId(Project::PROJ_TABLE) => $this->oProject->getProjectId()],
'constOpe' => array(Db::getId(Project::PROJ_TABLE) => "=") 'constOpe' => [Db::getId(Project::PROJ_TABLE) => '=']
); ];
//Time Filter //Time Filter
switch($sType) { switch($sType) {
case Feed::MSG_TABLE: case Feed::MSG_TABLE:
$asConsArray['constraint'][$sTimeField] = $asActPeriod; $asConsArray['constraint'][$sTimeField] = $asActPeriod;
$asConsArray['constOpe'][$sTimeField] = "BETWEEN"; $asConsArray['constOpe'][$sTimeField] = 'BETWEEN';
$asConsArray['constraint']['display'] = Feed::MSG_DISPLAYED; $asConsArray['constraint']['display'] = Feed::MSG_DISPLAYED;
$asConsArray['constOpe']['display'] = "="; $asConsArray['constOpe']['display'] = '=';
$sConsSql .= " AND ".$sTimeField." BETWEEN '".$asActPeriod['from']."' AND '".$asActPeriod['to']."' AND display = ".Feed::MSG_DISPLAYED; $sConsSql .= ' AND '.$sTimeField." BETWEEN '".$asActPeriod['from']."' AND '".$asActPeriod['to']."' AND display = ".Feed::MSG_DISPLAYED;
break; break;
case Media::MEDIA_TABLE: case Media::MEDIA_TABLE:
$asConsArray['constraint'][$sTimeField] = $asActPeriod['to']; $asConsArray['constraint'][$sTimeField] = $asActPeriod['to'];
$asConsArray['constOpe'][$sTimeField] = "<="; $asConsArray['constOpe'][$sTimeField] = '<=';
$sConsSql .= " AND ".$sTimeField." <= '".$asActPeriod['to']."'"; $sConsSql .= ' AND '.$sTimeField." <= '".$asActPeriod['to']."'";
break; break;
case self::POST_TABLE: case self::POST_TABLE:
$asConsArray['constraint'][$sTimeField] = $asActPeriod['to']; $asConsArray['constraint'][$sTimeField] = $asActPeriod['to'];
$asConsArray['constOpe'][$sTimeField] = "<="; $asConsArray['constOpe'][$sTimeField] = '<=';
$sConsSql .= " AND ".$sTimeField." <= '".$asActPeriod['to']."'"; $sConsSql .= ' AND '.$sTimeField." <= '".$asActPeriod['to']."'";
break; break;
} }
@@ -554,14 +544,14 @@ class Livetrail extends Main
} }
public function getNewFeed($iRefIdFirst) { public function getNewFeed($iRefIdFirst) {
$asResult = array(); $asResult = [];
$sLangId = ''; $sLangId = '';
if($this->oProject->isEditable()) { if($this->oProject->isEditable()) {
$asMessageIds = $asMediaIds = array(); $asMessageIds = $asMediaIds = [];
//New Feed Items //New Feed Items
$asResult = $this->getFeed($iRefIdFirst, ">", "DESC"); $asResult = $this->getFeed($iRefIdFirst, '>', 'DESC');
foreach($asResult['feed'] as $asItem) { foreach($asResult['feed'] as $asItem) {
switch($asItem['type']) { switch($asItem['type']) {
case 'message': case 'message':
@@ -575,8 +565,8 @@ class Livetrail extends Main
//New Markers //New Markers
$asMarkers = $this->getMarkers( $asMarkers = $this->getMarkers(
empty($asMessageIds)?array(0):$asMessageIds, empty($asMessageIds)?[0]:$asMessageIds,
empty($asMediaIds)?array(0):$asMediaIds, empty($asMediaIds)?[0]:$asMediaIds,
true true
); );
@@ -589,12 +579,12 @@ class Livetrail extends Main
public function getNextFeed($iRefIdLast=0, $bInternal=false) { public function getNextFeed($iRefIdLast=0, $bInternal=false) {
if($this->oProject->getMode() == Project::MODE_HISTO) { if($this->oProject->getMode() == Project::MODE_HISTO) {
$sDirection = ">"; $sDirection = '>';
$sSort = "ASC"; $sSort = 'ASC';
} }
else { else {
$sDirection = "<"; $sDirection = '<';
$sSort = "DESC"; $sSort = 'DESC';
} }
$asResult = $this->getFeed($iRefIdLast, $sDirection, $sSort); $asResult = $this->getFeed($iRefIdLast, $sDirection, $sSort);
return $bInternal?$asResult['feed']:self::getJsonResult(true, '', $asResult); return $bInternal?$asResult['feed']:self::getJsonResult(true, '', $asResult);
@@ -610,26 +600,26 @@ class Livetrail extends Main
$sMediaIdField = Db::getId(Media::MEDIA_TABLE); $sMediaIdField = Db::getId(Media::MEDIA_TABLE);
$sPostIdField = Db::getId(self::POST_TABLE); $sPostIdField = Db::getId(self::POST_TABLE);
$sFeedIdField = Db::getId(Feed::FEED_TABLE); $sFeedIdField = Db::getId(Feed::FEED_TABLE);
$sQuery = implode(" ", array( $sQuery = implode(' ', [
"SELECT type, id, ref", 'SELECT type, id, ref',
"FROM (", 'FROM (',
"SELECT {$sProjectIdField}, {$sMsgIdField} AS id, 'message' AS type, CONCAT(UNIX_TIMESTAMP(site_time), '.0', {$sMsgIdField}) AS ref", "SELECT {$sProjectIdField}, {$sMsgIdField} AS id, 'message' AS type, CONCAT(UNIX_TIMESTAMP(site_time), '.0', {$sMsgIdField}) AS ref",
"FROM ".Feed::MSG_TABLE, 'FROM '.Feed::MSG_TABLE,
"INNER JOIN ".Feed::FEED_TABLE." USING({$sFeedIdField})", 'INNER JOIN '.Feed::FEED_TABLE." USING({$sFeedIdField})",
$this->getFeedConstraints(Feed::MSG_TABLE, 'site_time', 'sql'), $this->getFeedConstraints(Feed::MSG_TABLE, 'site_time', 'sql'),
"UNION", 'UNION',
"SELECT {$sProjectIdField}, {$sMediaIdField} AS id, 'media' AS type, CONCAT(UNIX_TIMESTAMP(posted_on), '.1', {$sMediaIdField}) AS ref", "SELECT {$sProjectIdField}, {$sMediaIdField} AS id, 'media' AS type, CONCAT(UNIX_TIMESTAMP(posted_on), '.1', {$sMediaIdField}) AS ref",
"FROM ".Media::MEDIA_TABLE, 'FROM '.Media::MEDIA_TABLE,
$this->getFeedConstraints(Media::MEDIA_TABLE, 'posted_on', 'sql'), $this->getFeedConstraints(Media::MEDIA_TABLE, 'posted_on', 'sql'),
"UNION", 'UNION',
"SELECT {$sProjectIdField}, {$sPostIdField} AS id, 'post' AS type, CONCAT(UNIX_TIMESTAMP(site_time), '.2', {$sPostIdField}) AS ref", "SELECT {$sProjectIdField}, {$sPostIdField} AS id, 'post' AS type, CONCAT(UNIX_TIMESTAMP(site_time), '.2', {$sPostIdField}) AS ref",
"FROM ".self::POST_TABLE, 'FROM '.self::POST_TABLE,
$this->getFeedConstraints(self::POST_TABLE, 'site_time', 'sql'), $this->getFeedConstraints(self::POST_TABLE, 'site_time', 'sql'),
") AS items", ') AS items',
($sRefId !== '0')?("WHERE ref ".$sDirection." ".$sRefId):"", ($sRefId !== '0')?('WHERE ref '.$sDirection.' '.$sRefId):'',
"ORDER BY ref ".$sSort, 'ORDER BY ref '.$sSort,
"LIMIT ".self::FEED_CHUNK_SIZE 'LIMIT '.self::FEED_CHUNK_SIZE
)); ]);
//Get new chunk //Get new chunk
$asItems = $this->oDb->getArrayQuery($sQuery, true); $asItems = $this->oDb->getArrayQuery($sQuery, true);
@@ -642,18 +632,18 @@ class Livetrail extends Main
} }
//Sort Table IDs by type & Get attributes //Sort Table IDs by type & Get attributes
$asFeedIds = array('message'=>array(), 'media'=>array(), 'post'=>array()); $asFeedIds = ['message'=>[], 'media'=>[], 'post'=>[]];
foreach($asItems as $asItem) { foreach($asItems as $asItem) {
$asFeedIds[$asItem['type']][$asItem['id']] = $asItem; $asFeedIds[$asItem['type']][$asItem['id']] = $asItem;
} }
$asFeedAttrs = array( $asFeedAttrs = [
'message' => empty($asFeedIds['message'])?array():$this->getSpotMessages(array_keys($asFeedIds['message'])), 'message' => empty($asFeedIds['message'])?[]:$this->getSpotMessages(array_keys($asFeedIds['message'])),
'media' => empty($asFeedIds['media'])?array():$this->getMedias('posted_on', array_keys($asFeedIds['media'])), 'media' => empty($asFeedIds['media'])?[]:$this->getMedias('posted_on', array_keys($asFeedIds['media'])),
'post' => empty($asFeedIds['post'])?array():$this->getPosts(array_keys($asFeedIds['post'])) 'post' => empty($asFeedIds['post'])?[]:$this->getPosts(array_keys($asFeedIds['post']))
); ];
//Replace Array Key with Item ID //Replace Array Key with Item ID
$asFeeds = array(); $asFeeds = [];
foreach($asFeedAttrs as $sType=>$asFeedAttr) { foreach($asFeedAttrs as $sType=>$asFeedAttr) {
foreach($asFeedAttr as $asFeed) { foreach($asFeedAttr as $asFeed) {
$asFeeds[$sType][$asFeed['id_'.$sType]] = $asFeed; $asFeeds[$sType][$asFeed['id_'.$sType]] = $asFeed;
@@ -665,22 +655,21 @@ class Livetrail extends Main
$asItem = array_merge($asFeeds[$asItem['type']][$asItem['id']], $asItem); $asItem = array_merge($asFeeds[$asItem['type']][$asItem['id']], $asItem);
} }
return array('ref_id_last'=>$iRefIdLast, 'ref_id_first'=>$iRefIdFirst, 'sort'=>$sSort, 'feed'=>$asItems); return ['ref_id_last'=>$iRefIdLast, 'ref_id_first'=>$iRefIdFirst, 'sort'=>$sSort, 'feed'=>$asItems];
} }
public function addPost($sName, $sPost) public function addPost($sName, $sPost) {
{
$iPostId = 0; $iPostId = 0;
$sLangId = ''; $sLangId = '';
if($this->oProject->isEditable()) { if($this->oProject->isEditable()) {
$asData = array( $asData = [
Db::getId(Project::PROJ_TABLE) => $this->oProject->getProjectId(), Db::getId(Project::PROJ_TABLE) => $this->oProject->getProjectId(),
'name' => mb_strtolower(trim($sName)), 'name' => mb_strtolower(trim($sName)),
'content' => trim($sPost), 'content' => trim($sPost),
'site_time' => date(Db::TIMESTAMP_FORMAT), //Now in Site Time 'site_time' => date(Db::TIMESTAMP_FORMAT), //Now in Site Time
'timezone' => date_default_timezone_get() //Site Time Zone 'timezone' => date_default_timezone_get() //Site Time Zone
); ];
if($this->oUser->getUserId() > 0) $asData[Db::getId(User::USER_TABLE)] = $this->oUser->getUserId(); if($this->oUser->getUserId() > 0) $asData[Db::getId(User::USER_TABLE)] = $this->oUser->getUserId();
$iPostId = $this->oDb->insertRow(self::POST_TABLE, $asData); $iPostId = $this->oDb->insertRow(self::POST_TABLE, $asData);
@@ -693,8 +682,7 @@ class Livetrail extends Main
return self::getJsonResult(($iPostId > 0), $sLangId); return self::getJsonResult(($iPostId > 0), $sLangId);
} }
public function upload() public function upload() {
{
$oUploader = new Uploader($this->oMedia); $oUploader = new Uploader($this->oMedia);
return $oUploader->sBody; return $oUploader->sBody;
@@ -721,12 +709,12 @@ class Livetrail extends Main
public function getAdminSettings() { public function getAdminSettings() {
$oFeed = new Feed($this->oDb); $oFeed = new Feed($this->oDb);
$asData = array( $asData = [
'project' => $this->oProject->getProjects(), 'project' => $this->oProject->getProjects(),
'feed' => $oFeed->getFeeds(), 'feed' => $oFeed->getFeeds(),
'spot' => $oFeed->getSpots(), 'spot' => $oFeed->getSpots(),
'user' => $this->oUser->getSubscribedUsersInfo() 'user' => $this->oUser->getSubscribedUsersInfo()
); ];
foreach($asData['project'] as &$asProject) { foreach($asData['project'] as &$asProject) {
$asProject['active_from'] = substr($asProject['active_from'], 0, 10); $asProject['active_from'] = substr($asProject['active_from'], 0, 10);
@@ -739,10 +727,10 @@ class Livetrail extends Main
public function setAdminSettings($sType, $iId, $sField, $sValue) { public function setAdminSettings($sType, $iId, $sField, $sValue) {
$bSuccess = false; $bSuccess = false;
$sLangId = ''; $sLangId = '';
$asLangParams = array(); $asLangParams = [];
$asResult = array(); $asResult = [];
if($this->oDb->isId($sField) && $sValue <= 0) return self::getJsonResult(false, 'error.impossible_value', array(), array($sValue, $sField)); if($this->oDb->isId($sField) && $sValue <= 0) return self::getJsonResult(false, 'error.impossible_value', [], [$sValue, $sField]);
switch($sType) { switch($sType) {
case 'project': case 'project':
@@ -763,7 +751,7 @@ class Livetrail extends Main
break; break;
default: default:
$sLangId = 'error.unknown_field'; $sLangId = 'error.unknown_field';
$asLangParams = array($sField); $asLangParams = [$sField];
} }
//Identify missing GPX file //Identify missing GPX file
@@ -771,7 +759,7 @@ class Livetrail extends Main
if(!Converter::hasGpxFile($sProjectCodeName)) { if(!Converter::hasGpxFile($sProjectCodeName)) {
$bSuccess = true; $bSuccess = true;
$sLangId = 'error.file_missing'; $sLangId = 'error.file_missing';
$asLangParams = array('GPX', $sProjectCodeName.Gpx::EXT); $asLangParams = ['GPX', $sProjectCodeName.Gpx::EXT];
} }
$asResult = $oProject->getProject(); $asResult = $oProject->getProject();
@@ -792,7 +780,7 @@ class Livetrail extends Main
break; break;
default: default:
$sLangId = 'error.unknown_field'; $sLangId = 'error.unknown_field';
$asLangParams = array($sField); $asLangParams = [$sField];
} }
$asResult = $oFeed->getFeed(); $asResult = $oFeed->getFeed();
break; break;
@@ -806,20 +794,20 @@ class Livetrail extends Main
break; break;
default: default:
$sLangId = 'error.unknown_field'; $sLangId = 'error.unknown_field';
$asLangParams = array($sField); $asLangParams = [$sField];
} }
$asResult = $this->oUser->getUserById($iId); $asResult = $this->oUser->getUserById($iId);
break; break;
} }
if(!$bSuccess && $sLangId=='') $sLangId = 'error.commit_db'; if(!$bSuccess && $sLangId=='') $sLangId = 'error.commit_db';
return self::getJsonResult($bSuccess, $sLangId, array($sType=>array($asResult)), $asLangParams); return self::getJsonResult($bSuccess, $sLangId, [$sType=>[$asResult]], $asLangParams);
} }
public function createAdminSettings($sType) { public function createAdminSettings($sType) {
$bSuccess = false; $bSuccess = false;
$sLangId = ''; $sLangId = '';
$asResult = array(); $asResult = [];
switch($sType) { switch($sType) {
case 'project': case 'project':
@@ -830,18 +818,18 @@ class Livetrail extends Main
$oFeed->createFeedId($iNewProjectId); $oFeed->createFeedId($iNewProjectId);
$bSuccess = $iNewProjectId > 0; $bSuccess = $iNewProjectId > 0;
$asResult = array( $asResult = [
'project' => array($oProject->getProject()), 'project' => [$oProject->getProject()],
'feed' => array($oFeed->getFeed()) 'feed' => [$oFeed->getFeed()]
); ];
break; break;
case 'feed': case 'feed':
$oFeed = new Feed($this->oDb); $oFeed = new Feed($this->oDb);
$iNewFeedId = $oFeed->createFeedId($this->oProject->getProjectId()); $iNewFeedId = $oFeed->createFeedId($this->oProject->getProjectId());
$bSuccess = $iNewFeedId > 0; $bSuccess = $iNewFeedId > 0;
$asResult = array( $asResult = [
'feed' => array($oFeed->getFeed()) 'feed' => [$oFeed->getFeed()]
); ];
break; break;
} }
if(!$bSuccess && $sLangId=='') $sLangId = 'error.commit_db'; if(!$bSuccess && $sLangId=='') $sLangId = 'error.commit_db';
@@ -852,8 +840,8 @@ class Livetrail extends Main
public function deleteAdminSettings($sType, $iId) { public function deleteAdminSettings($sType, $iId) {
$bSuccess = false; $bSuccess = false;
$sLangId = ''; $sLangId = '';
$asLangParams = array(); $asLangParams = [];
$asResult = array(); $asResult = [];
switch($sType) { switch($sType) {
case 'project': case 'project':
@@ -865,7 +853,7 @@ class Livetrail extends Main
break; break;
case 'feed': case 'feed':
$oFeed = new Feed($this->oDb, $iId); $oFeed = new Feed($this->oDb, $iId);
$asResult = array('feed' => array($oFeed->delete())); $asResult = ['feed' => [$oFeed->delete()]];
$sLangId = $asResult['feed'][0]['desc_lang_id']; $sLangId = $asResult['feed'][0]['desc_lang_id'];
$asLangParams = $asResult['feed'][0]['desc_lang_params']; $asLangParams = $asResult['feed'][0]['desc_lang_params'];
$bSuccess = $asResult['feed'][0]['result']; $bSuccess = $asResult['feed'][0]['result'];
@@ -900,8 +888,8 @@ class Livetrail extends Main
$sDirection; $sDirection;
} }
public static function getNumberWithLeadingZeros($fValue, $iNbLeadingZeros, $iNbDigits){ public static function getNumberWithLeadingZeros($fValue, $iNbLeadingZeros, $iNbDigits) {
$sDecimalSeparator = "."; $sDecimalSeparator = '.';
if($iNbDigits > 0) $iNbLeadingZeros += mb_strlen($sDecimalSeparator) + $iNbDigits; if($iNbDigits > 0) $iNbLeadingZeros += mb_strlen($sDecimalSeparator) + $iNbDigits;
$sPattern = '%0'.$iNbLeadingZeros.$sDecimalSeparator.$iNbDigits.'f'; $sPattern = '%0'.$iNbLeadingZeros.$sDecimalSeparator.$iNbDigits.'f';
return sprintf($sPattern, $fValue); return sprintf($sPattern, $fValue);
@@ -915,7 +903,7 @@ class Livetrail extends Main
$sDate = $oDate->format('d/m/Y'); $sDate = $oDate->format('d/m/Y');
$sTime = $oDate->format('H:i'); $sTime = $oDate->format('H:i');
return $this->oLang->getTranslation('time.date_time', array($sDate, $sTime)); return $this->oLang->getTranslation('time.date_time', [$sDate, $sTime]);
} }
public static function getTimeZoneDayOffset($iTime, $sLocalTimeZone) { public static function getTimeZoneDayOffset($iTime, $sLocalTimeZone) {
+9 -9
View File
@@ -6,8 +6,8 @@ use Franzz\Objects\Db;
class Map extends PhpObject { class Map extends PhpObject {
const MAP_TABLE = 'maps'; public const MAP_TABLE = 'maps';
const MAPPING_TABLE = 'mappings'; public const MAPPING_TABLE = 'mappings';
private Db $oDb; private Db $oDb;
private $asMaps; private $asMaps;
@@ -15,11 +15,11 @@ class Map extends PhpObject {
public function __construct(Db &$oDb) { public function __construct(Db &$oDb) {
parent::__construct(__CLASS__); parent::__construct(__CLASS__);
$this->oDb = &$oDb; $this->oDb = &$oDb;
$this->asMaps = array(); $this->asMaps = [];
} }
private function setMaps() { private function setMaps() {
$asMaps = $this->oDb->selectRows(array('from'=>self::MAP_TABLE)); $asMaps = $this->oDb->selectRows(['from'=>self::MAP_TABLE]);
foreach($asMaps as $asMap) $this->asMaps[$asMap['codename']] = $asMap; foreach($asMaps as $asMap) $this->asMaps[$asMap['codename']] = $asMap;
} }
@@ -30,15 +30,15 @@ class Map extends PhpObject {
public function getProjectMaps($iProjectId) { public function getProjectMaps($iProjectId) {
$asMappings = $this->oDb->selectRows( $asMappings = $this->oDb->selectRows(
array( [
'select' => array(Db::getId(self::MAP_TABLE), 'default_map'), 'select' => [Db::getId(self::MAP_TABLE), 'default_map'],
'from' => self::MAPPING_TABLE, 'from' => self::MAPPING_TABLE,
'constraint'=> array("IFNULL(id_project, {$iProjectId})" => $iProjectId) 'constraint'=> ["IFNULL(id_project, {$iProjectId})" => $iProjectId]
), ],
Db::getId(self::MAP_TABLE) Db::getId(self::MAP_TABLE)
); );
$asProjectMaps = array(); $asProjectMaps = [];
foreach($this->getMaps() as $asMap) { foreach($this->getMaps() as $asMap) {
if(array_key_exists($asMap['id_map'], $asMappings)) { if(array_key_exists($asMap['id_map'], $asMappings)) {
$asMap['default_map'] = $asMappings[$asMap['id_map']]; $asMap['default_map'] = $asMappings[$asMap['id_map']];
+31 -34
View File
@@ -8,13 +8,13 @@ use Franzz\Objects\ToolBox;
class Media extends PhpObject { class Media extends PhpObject {
//DB Tables //DB Tables
const MEDIA_TABLE = 'medias'; public const MEDIA_TABLE = 'medias';
//Media folders (works because /public/files is a symlink of /files) //Media folders (works because /public/files is a symlink of /files)
const MEDIA_FOLDER = 'files'; public const MEDIA_FOLDER = 'files';
const THUMB_FOLDER = self::MEDIA_FOLDER.'/thumbs'; public const THUMB_FOLDER = self::MEDIA_FOLDER.'/thumbs';
const THUMB_MAX_WIDTH = 400; private const THUMB_MAX_WIDTH = 400;
private Db $oDb; private Db $oDb;
private Project $oProject; private Project $oProject;
@@ -27,8 +27,8 @@ class Media extends PhpObject {
parent::__construct(__CLASS__); parent::__construct(__CLASS__);
$this->oDb = &$oDb; $this->oDb = &$oDb;
$this->oProject = &$oProject; $this->oProject = &$oProject;
$this->asMedia = array(); $this->asMedia = [];
$this->asMedias = array(); $this->asMedias = [];
$this->setMediaId($iMediaId); $this->setMediaId($iMediaId);
} }
@@ -46,9 +46,9 @@ class Media extends PhpObject {
public function setComment($sComment) { public function setComment($sComment) {
$sLangId = ''; $sLangId = '';
$asData = array(); $asData = [];
if($this->iMediaId > 0) { if($this->iMediaId > 0) {
$bResult = $this->oDb->updateRow(self::MEDIA_TABLE, $this->iMediaId, array('comment'=>$sComment)); $bResult = $this->oDb->updateRow(self::MEDIA_TABLE, $this->iMediaId, ['comment'=>$sComment]);
if(!$bResult) $sLangId = 'error.commit_db'; if(!$bResult) $sLangId = 'error.commit_db';
else $asData = $this->getInfo(); else $asData = $this->getInfo();
} }
@@ -63,11 +63,11 @@ class Media extends PhpObject {
if($bOwnMedia && empty($this->asMedia) || !$bOwnMedia && empty($this->asMedias) || $bConstraintArray) { if($bOwnMedia && empty($this->asMedia) || !$bOwnMedia && empty($this->asMedias) || $bConstraintArray) {
if($this->oProject->getProjectId()) { if($this->oProject->getProjectId()) {
$asParams = array( $asParams = [
'select' => array(Db::getId(self::MEDIA_TABLE), 'filename', 'taken_on', 'posted_on', 'timezone', 'latitude', 'longitude', 'altitude', 'width', 'height', 'rotate', 'type AS subtype', 'comment'), 'select' => [Db::getId(self::MEDIA_TABLE), 'filename', 'taken_on', 'posted_on', 'timezone', 'latitude', 'longitude', 'altitude', 'width', 'height', 'rotate', 'type AS subtype', 'comment'],
'from' => self::MEDIA_TABLE, 'from' => self::MEDIA_TABLE,
'constraint'=> array(Db::getId(Project::PROJ_TABLE) => $this->oProject->getProjectId()) 'constraint'=> [Db::getId(Project::PROJ_TABLE) => $this->oProject->getProjectId()]
); ];
if($bOwnMedia) $asParams['constraint'][Db::getId(self::MEDIA_TABLE)] = $oMediaIds; if($bOwnMedia) $asParams['constraint'][Db::getId(self::MEDIA_TABLE)] = $oMediaIds;
if($bConstraintArray) $asParams = array_merge($asParams, $oMediaIds); if($bConstraintArray) $asParams = array_merge($asParams, $oMediaIds);
@@ -96,12 +96,12 @@ class Media extends PhpObject {
public function addMedia($sMediaName, $sMethod='upload') { public function addMedia($sMediaName, $sMethod='upload') {
$sLangId = ''; $sLangId = '';
$asParams = array(); $asParams = [];
if(!$this->isProjectEditable() && $sMethod!='sync') { if(!$this->isProjectEditable() && $sMethod!='sync') {
$sLangId = 'upload.mode_archived'; $sLangId = 'upload.mode_archived';
$asParams[] = $this->oProject->getProjectCodeName(); $asParams[] = $this->oProject->getProjectCodeName();
} }
elseif($this->oDb->pingValue(self::MEDIA_TABLE, array('filename'=>$sMediaName)) && $sMethod!='sync') { elseif($this->oDb->pingValue(self::MEDIA_TABLE, ['filename'=>$sMediaName]) && $sMethod!='sync') {
$sLangId = 'upload.media.exists'; $sLangId = 'upload.media.exists';
$asParams[] = $sMediaName; $asParams[] = $sMediaName;
} }
@@ -110,7 +110,7 @@ class Media extends PhpObject {
//Converting times to Site Time Zone, by using date() //Converting times to Site Time Zone, by using date()
//Media Timezone is kept in a separate field for later conversion to Local Time //Media Timezone is kept in a separate field for later conversion to Local Time
$asDbInfo = array( $asDbInfo = [
Db::getId(Project::PROJ_TABLE) => $this->oProject->getProjectId(), Db::getId(Project::PROJ_TABLE) => $this->oProject->getProjectId(),
'filename' => $sMediaName, 'filename' => $sMediaName,
'taken_on' => date(Db::TIMESTAMP_FORMAT, ($asMediaInfo['taken_ts'] > 0)?$asMediaInfo['taken_ts']:$asMediaInfo['file_ts']), 'taken_on' => date(Db::TIMESTAMP_FORMAT, ($asMediaInfo['taken_ts'] > 0)?$asMediaInfo['taken_ts']:$asMediaInfo['file_ts']),
@@ -123,9 +123,9 @@ class Media extends PhpObject {
'height' => $asMediaInfo['height'], 'height' => $asMediaInfo['height'],
'rotate' => $asMediaInfo['rotate'], 'rotate' => $asMediaInfo['rotate'],
'type' => $asMediaInfo['type'] 'type' => $asMediaInfo['type']
); ];
if($sMethod=='sync') $iMediaId = $this->oDb->insertUpdateRow(self::MEDIA_TABLE, $asDbInfo, array('filename')); if($sMethod=='sync') $iMediaId = $this->oDb->insertUpdateRow(self::MEDIA_TABLE, $asDbInfo, ['filename']);
else $iMediaId = $this->oDb->insertRow(self::MEDIA_TABLE, $asDbInfo); else $iMediaId = $this->oDb->insertRow(self::MEDIA_TABLE, $asDbInfo);
if(!$iMediaId) $sLangId = 'error.commit_db'; if(!$iMediaId) $sLangId = 'error.commit_db';
@@ -138,8 +138,7 @@ class Media extends PhpObject {
return Livetrail::getResult(($sLangId==''), $sLangId, $asParams); return Livetrail::getResult(($sLangId==''), $sLangId, $asParams);
} }
private function getMediaInfoFromFile($sMediaName) private function getMediaInfoFromFile($sMediaName) {
{
$sMediaPath = self::getMediaPath($sMediaName); $sMediaPath = self::getMediaPath($sMediaName);
$sType = self::getMediaType($sMediaName); $sType = self::getMediaType($sMediaName);
$iPostedOn = filemtime($sMediaPath); $iPostedOn = filemtime($sMediaPath);
@@ -153,8 +152,8 @@ class Media extends PhpObject {
$iAlt = null; $iAlt = null;
switch($sType) { switch($sType) {
case 'video': case 'video':
$asResult = array(); $asResult = [];
$sParams = implode(' ', array( $sParams = implode(' ', [
'-loglevel error', //Remove comments '-loglevel error', //Remove comments
'-select_streams v:0', //First video channel '-select_streams v:0', //First video channel
'-show_entries '. //filter tags : Width, Height, Creation Time, Location & Rotation '-show_entries '. //filter tags : Width, Height, Creation Time, Location & Rotation
@@ -163,7 +162,7 @@ class Media extends PhpObject {
'stream=width,height', 'stream=width,height',
'-print_format json', //output format: json '-print_format json', //output format: json
'-i' //input file '-i' //input file
)); ]);
exec('ffprobe '.$sParams.' '.escapeshellarg($sMediaPath), $asResult); exec('ffprobe '.$sParams.' '.escapeshellarg($sMediaPath), $asResult);
$asExif = json_decode(implode('', $asResult), true); $asExif = json_decode(implode('', $asResult), true);
@@ -188,7 +187,7 @@ class Media extends PhpObject {
break; break;
case 'image': case 'image':
$asExif = @exif_read_data($sMediaPath, 0, true); $asExif = @exif_read_data($sMediaPath, 0, true);
if($asExif === false) $asExif = array(); if($asExif === false) $asExif = [];
list($iWidth, $iHeight) = getimagesize($sMediaPath); list($iWidth, $iHeight) = getimagesize($sMediaPath);
//Posted On //Posted On
@@ -217,8 +216,7 @@ class Media extends PhpObject {
//Orientation //Orientation
if(array_key_exists('IFD0', $asExif) && array_key_exists('Orientation', $asExif['IFD0'])) { if(array_key_exists('IFD0', $asExif) && array_key_exists('Orientation', $asExif['IFD0'])) {
switch($asExif['IFD0']['Orientation']) switch($asExif['IFD0']['Orientation']) {
{
case 1: $sRotate = '0'; break; //None case 1: $sRotate = '0'; break; //None
case 3: $sRotate = '180'; break; //Flip over case 3: $sRotate = '180'; break; //Flip over
case 6: $sRotate = '90'; break; //Clockwise case 6: $sRotate = '90'; break; //Clockwise
@@ -236,7 +234,7 @@ class Media extends PhpObject {
$iTakenOn = $oTakenOn->format('U'); $iTakenOn = $oTakenOn->format('U');
} }
return array( return [
'timezone' => $sTimeZone, 'timezone' => $sTimeZone,
'latitude' => $fLat, 'latitude' => $fLat,
'longitude' => $fLng, 'longitude' => $fLng,
@@ -247,11 +245,10 @@ class Media extends PhpObject {
'height' => $iHeight, 'height' => $iHeight,
'rotate' => $sRotate, 'rotate' => $sRotate,
'type' => $sType 'type' => $sType
); ];
} }
private function getMediaThumbnail($sMediaName) private function getMediaThumbnail($sMediaName) {
{
$sMediaPath = self::getMediaPath($sMediaName); $sMediaPath = self::getMediaPath($sMediaName);
$sThumbPath = self::getMediaPath($sMediaName, 'thumbnail'); $sThumbPath = self::getMediaPath($sMediaName, 'thumbnail');
@@ -264,13 +261,13 @@ class Media extends PhpObject {
case 'video': case 'video':
//Get a screenshot of the video 1 second in //Get a screenshot of the video 1 second in
$sTempPath = self::getMediaPath(uniqid('temp_').'.png'); $sTempPath = self::getMediaPath(uniqid('temp_').'.png');
$asResult = array(); $asResult = [];
$sParams = implode(' ', array( $sParams = implode(' ', [
'-i '.escapeshellarg($sMediaPath), //input file '-i '.escapeshellarg($sMediaPath), //input file
'-ss 00:00:01.000', //Image taken after x seconds '-ss 00:00:01.000', //Image taken after x seconds
'-vframes 1', //number of video frames to output '-vframes 1', //number of video frames to output
escapeshellarg($sTempPath), //output file escapeshellarg($sTempPath), //output file
)); ]);
exec('ffmpeg '.$sParams, $asResult); exec('ffmpeg '.$sParams, $asResult);
//Resize //Resize
@@ -279,7 +276,7 @@ class Media extends PhpObject {
} }
} }
else $asThumbInfo = array('error'=>'', 'out'=>$sThumbPath); else $asThumbInfo = ['error'=>'', 'out'=>$sThumbPath];
return ($asThumbInfo['error']=='')?$asThumbInfo['out']:$sMediaPath; return ($asThumbInfo['error']=='')?$asThumbInfo['out']:$sMediaPath;
} }
@@ -323,6 +320,6 @@ class Media extends PhpObject {
private static function getLatLngAltFromISO6709($sIso6709) { private static function getLatLngAltFromISO6709($sIso6709) {
preg_match('/^(?P<lat>[\+\-][0,1]?\d{2}\.\d+)(?P<lng>[\+\-][0,1]?\d{2}\.\d+)(?P<alt>[\+\-]\d+)?/', $sIso6709, $asMatches); preg_match('/^(?P<lat>[\+\-][0,1]?\d{2}\.\d+)(?P<lng>[\+\-][0,1]?\d{2}\.\d+)(?P<alt>[\+\-]\d+)?/', $sIso6709, $asMatches);
return array(floatval($asMatches['lat']), floatval($asMatches['lng']), floatval($asMatches['alt'] ?? 0)); return [floatval($asMatches['lat']), floatval($asMatches['lng']), floatval($asMatches['alt'] ?? 0)];
} }
} }
+35 -36
View File
@@ -7,13 +7,13 @@ use Franzz\Objects\Db;
class Project extends PhpObject { class Project extends PhpObject {
//Spot Mode //Spot Mode
const MODE_PREVIZ = 'P'; public const MODE_PREVIZ = 'P';
const MODE_BLOG = 'B'; public const MODE_BLOG = 'B';
const MODE_HISTO = 'H'; public const MODE_HISTO = 'H';
const MODES = array('previz'=>self::MODE_PREVIZ, 'blog'=>self::MODE_BLOG, 'histo'=>self::MODE_HISTO); public const MODES = ['previz'=>self::MODE_PREVIZ, 'blog'=>self::MODE_BLOG, 'histo'=>self::MODE_HISTO];
//DB Tables //DB Tables
const PROJ_TABLE = 'projects'; public const PROJ_TABLE = 'projects';
/** /**
* Database Handle * Database Handle
@@ -21,7 +21,6 @@ class Project extends PhpObject {
*/ */
private $oDb; private $oDb;
private $iProjectId; private $iProjectId;
private $sName; private $sName;
private $sCodeName; private $sCodeName;
@@ -51,17 +50,17 @@ class Project extends PhpObject {
* Mode --P--][--------B--------][--P--][-----------B---------------][---P---][-----B-----][---------H---------- * Mode --P--][--------B--------][--P--][-----------B---------------][---P---][-----B-----][---------H----------
*/ */
$sQuery = $sQuery =
"SELECT MAX(id_project) ". 'SELECT MAX(id_project) '.
"FROM projects ". 'FROM projects '.
"WHERE active_to = (". 'WHERE active_to = ('.
"SELECT MIN(active_to) ". //Select closest project in the future 'SELECT MIN(active_to) '. //Select closest project in the future
"FROM projects ". 'FROM projects '.
"WHERE active_to > NOW() ". //Select Next project 'WHERE active_to > NOW() '. //Select Next project
"OR active_to = (". //In case there is no next project, select the last one 'OR active_to = ('. //In case there is no next project, select the last one
"SELECT MAX(active_to) ". 'SELECT MAX(active_to) '.
"FROM projects". 'FROM projects'.
")". ')'.
")"; ')';
$asResult = $this->oDb->getArrayQuery($sQuery, true); $asResult = $this->oDb->getArrayQuery($sQuery, true);
$this->iProjectId = array_shift($asResult); $this->iProjectId = array_shift($asResult);
} }
@@ -70,7 +69,7 @@ class Project extends PhpObject {
} }
public function createProjectId() { public function createProjectId() {
$this->setProjectId($this->oDb->insertRow(self::PROJ_TABLE, array('codename'=>''))); $this->setProjectId($this->oDb->insertRow(self::PROJ_TABLE, ['codename'=>'']));
return $this->getProjectId(); return $this->getProjectId();
} }
@@ -112,28 +111,28 @@ class Project extends PhpObject {
return $this->oDb->selectColumn( return $this->oDb->selectColumn(
Feed::FEED_TABLE, Feed::FEED_TABLE,
Db::getId(Feed::FEED_TABLE), Db::getId(Feed::FEED_TABLE),
array(Db::getId(self::PROJ_TABLE) => $this->getProjectId()) [Db::getId(self::PROJ_TABLE) => $this->getProjectId()]
); );
} }
public function getProjects($iProjectId=0) { public function getProjects($iProjectId=0) {
$bSpecificProj = ($iProjectId > 0); $bSpecificProj = ($iProjectId > 0);
$sDefaultProjectCodeName = $this->getProjectCodeName(); $sDefaultProjectCodeName = $this->getProjectCodeName();
$asInfo = array( $asInfo = [
'select'=> array( 'select'=> [
Db::getId(self::PROJ_TABLE)." AS id", Db::getId(self::PROJ_TABLE).' AS id',
'codename', 'codename',
'name', 'name',
'latitude', 'latitude',
'longitude', 'longitude',
'active_from', 'active_from',
'active_to', 'active_to',
"IF(NOW() BETWEEN active_from AND active_to, 1, IF(NOW() < active_from, 0, 2)) AS mode" 'IF(NOW() BETWEEN active_from AND active_to, 1, IF(NOW() < active_from, 0, 2)) AS mode'
), ],
'from' => self::PROJ_TABLE, 'from' => self::PROJ_TABLE,
'orderBy' => array('active_from' => 'ASC') 'orderBy' => ['active_from' => 'ASC']
); ];
if($bSpecificProj) $asInfo['constraint'] = array(Db::getId(self::PROJ_TABLE)=>$iProjectId); if($bSpecificProj) $asInfo['constraint'] = [Db::getId(self::PROJ_TABLE)=>$iProjectId];
$asProjects = $this->oDb->selectRows($asInfo, 'codename'); $asProjects = $this->oDb->selectRows($asInfo, 'codename');
foreach($asProjects as $sCodeName => &$asProject) { foreach($asProjects as $sCodeName => &$asProject) {
@@ -174,7 +173,7 @@ class Project extends PhpObject {
return $iLastUpdate; return $iLastUpdate;
} }
public function getLastMessageId($asConstraints=array()): int { public function getLastMessageId($asConstraints=[]): int {
$iLastMsg = 0; $iLastMsg = 0;
$asFeedIds = $this->getFeedIds(); $asFeedIds = $this->getFeedIds();
@@ -192,20 +191,20 @@ class Project extends PhpObject {
$this->sName = $asProject['name']; $this->sName = $asProject['name'];
$this->sCodeName = $asProject['codename']; $this->sCodeName = $asProject['codename'];
$this->sMode = $asProject['mode']; $this->sMode = $asProject['mode'];
$this->asActive = array('from'=>$asProject['active_from'], 'to'=>$asProject['active_to']); $this->asActive = ['from'=>$asProject['active_from'], 'to'=>$asProject['active_to']];
} }
else $this->addError('Error while setting project: no project ID'); else $this->addError('Error while setting project: no project ID');
} }
private function updateField($sField, $oValue) { private function updateField($sField, $oValue) {
$bResult = ($this->oDb->updateRow(self::PROJ_TABLE, $this->getProjectId(), array($sField=>$oValue)) > 0); $bResult = ($this->oDb->updateRow(self::PROJ_TABLE, $this->getProjectId(), [$sField=>$oValue]) > 0);
$this->setProjectInfo(); $this->setProjectInfo();
return $bResult; return $bResult;
} }
public function delete() { public function delete() {
$asResult = array(); $asResult = [];
if($this->getProjectId() > 0) { if($this->getProjectId() > 0) {
$asFeedIds = $this->getFeedIds(); $asFeedIds = $this->getFeedIds();
foreach($asFeedIds as $iFeedId) { foreach($asFeedIds as $iFeedId) {
@@ -213,14 +212,14 @@ class Project extends PhpObject {
} }
$bDeleted = $this->oDb->deleteRow(self::PROJ_TABLE, $this->getProjectId()); $bDeleted = $this->oDb->deleteRow(self::PROJ_TABLE, $this->getProjectId());
$asResult['project'][] = array( $asResult['project'][] = [
'id' => $this->getProjectId(), 'id' => $this->getProjectId(),
'del' => $bDeleted, 'del' => $bDeleted,
'desc_lang_id' => $bDeleted?'':'error.commit_db', 'desc_lang_id' => $bDeleted?'':'error.commit_db',
'desc_lang_params' => array() 'desc_lang_params' => []
); ];
} }
else $asResult['project'][] = array('del'=>false, 'desc_lang_id'=>'error.impossible_value', 'desc_lang_params'=>array($this->getProjectId(), 'project ID')); else $asResult['project'][] = ['del'=>false, 'desc_lang_id'=>'error.impossible_value', 'desc_lang_params'=>[$this->getProjectId(), 'project ID']];
return $asResult; return $asResult;
} }
@@ -229,7 +228,7 @@ class Project extends PhpObject {
return self::isModeEditable($this->getMode()); return self::isModeEditable($this->getMode());
} }
static public function isModeEditable($sMode) { public static function isModeEditable($sMode) {
return ($sMode != self::MODE_HISTO); return ($sMode != self::MODE_HISTO);
} }
} }
+8 -10
View File
@@ -3,22 +3,20 @@
namespace Franzz\Livetrail; namespace Franzz\Livetrail;
use Franzz\Objects\UploadHandler; use Franzz\Objects\UploadHandler;
class Uploader extends UploadHandler class Uploader extends UploadHandler {
{
private Media $oMedia; private Media $oMedia;
public string $sBody; public string $sBody;
function __construct(Media &$oMedia) public function __construct(Media &$oMedia) {
{
$this->oMedia = &$oMedia; $this->oMedia = &$oMedia;
$this->sBody = ''; $this->sBody = '';
parent::__construct(array( parent::__construct([
'upload_dir' => Media::MEDIA_FOLDER.'/', 'upload_dir' => Media::MEDIA_FOLDER.'/',
'image_versions' => array(), 'image_versions' => [],
'accept_file_types' => '/\.(gif|jpe?g|png|mov|mp4)$/i' 'accept_file_types' => '/\.(gif|jpe?g|png|mov|mp4)$/i'
)); ]);
} }
protected function validate($uploaded_file, $file, $error, $index, $content_range) { protected function validate($uploaded_file, $file, $error, $index, $content_range) {
@@ -28,7 +26,7 @@ class Uploader extends UploadHandler
if(!$this->oMedia->isProjectEditable()) { if(!$this->oMedia->isProjectEditable()) {
$file->error = true; $file->error = true;
$file->desc_lang_id = 'upload.mode_archived'; $file->desc_lang_id = 'upload.mode_archived';
$file->desc_lang_params = array($this->oMedia->getProjectCodeName()); $file->desc_lang_params = [$this->oMedia->getProjectCodeName()];
$bResult = false; $bResult = false;
} }
@@ -55,7 +53,7 @@ class Uploader extends UploadHandler
} }
if(!empty($file->error)) { if(!empty($file->error)) {
if(empty($file->desc_lang_id)) $file->desc_lang_id = is_string($file->error)?$file->error:'upload.error'; if(empty($file->desc_lang_id)) $file->desc_lang_id = is_string($file->error)?$file->error:'upload.error';
if(empty($file->desc_lang_params)) $file->desc_lang_params = array(); if(empty($file->desc_lang_params)) $file->desc_lang_params = [];
$file->error = true; $file->error = true;
} }
@@ -66,7 +64,7 @@ class Uploader extends UploadHandler
$this->sBody .= $sBodyPart; $this->sBody .= $sBodyPart;
} }
protected function get_error_message($sLangId, $asParams=array()) { protected function get_error_message($sLangId, $asParams=[]) {
return array_key_exists($sLangId, $this->error_messages)?'upload.error':$sLangId; return array_key_exists($sLangId, $this->error_messages)?'upload.error':$sLangId;
} }
} }
+42 -43
View File
@@ -7,23 +7,17 @@ use Franzz\Objects\Db;
class User extends PhpObject { class User extends PhpObject {
//DB Tables //DB Tables
const USER_TABLE = 'users'; public const USER_TABLE = 'users';
//Clearance Levels //Clearance Levels
const CLEARANCE_USER = 0; public const CLEARANCE_USER = 0;
const CLEARANCE_ADMIN = 9; public const CLEARANCE_ADMIN = 9;
const CLEARANCES = array('user'=>self::CLEARANCE_USER, 'admin'=>self::CLEARANCE_ADMIN); public const CLEARANCES = ['user'=>self::CLEARANCE_USER, 'admin'=>self::CLEARANCE_ADMIN];
const USER_SUBSCRIBED = 1; public const USER_SUBSCRIBED = 1;
const USER_UNSUBSCRIBED = 0; public const USER_UNSUBSCRIBED = 0;
//Session & Cookie public const DEFAULT_USER = [
const SESSION_ID_USER = 'id_user';
const SESSION_ADMIN = 'admin_authenticated';
const COOKIE_TOKEN = 'login';
const COOKIE_DURATION = 60 * 60 * 24 * 365; //1 year
const DEFAULT_USER = array(
'id' => 0, 'id' => 0,
'id_user' => 0, 'id_user' => 0,
'name' => '', 'name' => '',
@@ -32,7 +26,13 @@ class User extends PhpObject {
'timezone' => '', 'timezone' => '',
'subscribed'=> self::USER_UNSUBSCRIBED, 'subscribed'=> self::USER_UNSUBSCRIBED,
'clearance' => self::CLEARANCE_USER 'clearance' => self::CLEARANCE_USER
); ];
//Session & Cookie
private const SESSION_ID_USER = 'id_user';
private const SESSION_ADMIN = 'admin_authenticated';
private const COOKIE_TOKEN = 'login';
private const COOKIE_DURATION = 60 * 60 * 24 * 365; //1 year
/** /**
* Database Handle * Database Handle
@@ -73,22 +73,22 @@ class User extends PhpObject {
} }
public function getUserById($iUserId) { public function getUserById($iUserId) {
$asUsersInfo = array(); $asUsersInfo = [];
if($iUserId > 0) $asUsersInfo = $this->getUsersInfo($iUserId); if($iUserId > 0) $asUsersInfo = $this->getUsersInfo($iUserId);
return empty($asUsersInfo)?array():array_shift($asUsersInfo); return empty($asUsersInfo)?[]:array_shift($asUsersInfo);
} }
public function getUsersInfo($iUserId=-1) { public function getUsersInfo($iUserId=-1) {
//Mapping between user fields and DB fields //Mapping between user fields and DB fields
$asSelect = array_keys($this->asUserInfo); $asSelect = array_keys($this->asUserInfo);
$asSelect[array_search('id', $asSelect)] = Db::getId(self::USER_TABLE)." AS id"; $asSelect[array_search('id', $asSelect)] = Db::getId(self::USER_TABLE).' AS id';
$asInfo = array( $asInfo = [
'select' => $asSelect, 'select' => $asSelect,
'from' => self::USER_TABLE 'from' => self::USER_TABLE
); ];
if($iUserId != -1) $asInfo['constraint'] = array(Db::getId(self::USER_TABLE) => $iUserId); if($iUserId != -1) $asInfo['constraint'] = [Db::getId(self::USER_TABLE) => $iUserId];
return $this->oDb->selectRows($asInfo); return $this->oDb->selectRows($asInfo);
} }
@@ -103,7 +103,7 @@ class User extends PhpObject {
$iUserId = $this->oDb->insertRow( $iUserId = $this->oDb->insertRow(
self::USER_TABLE, self::USER_TABLE,
array('email'=>$sEmail, 'language'=>$sLang, 'timezone'=>$sTimezone) ['email'=>$sEmail, 'language'=>$sLang, 'timezone'=>$sTimezone]
); );
if($iUserId == 0) $sLangId = 'error.commit_db'; if($iUserId == 0) $sLangId = 'error.commit_db';
@@ -121,7 +121,7 @@ class User extends PhpObject {
public function setSubscription($bSubscribed) { public function setSubscription($bSubscribed) {
if($this->getUserId() > 0) { if($this->getUserId() > 0) {
$iSubscribed = $bSubscribed?1:0; $iSubscribed = $bSubscribed?1:0;
$iUserId = $this->oDb->updateRow(self::USER_TABLE, $this->getUserId(), array('subscribed'=>$iSubscribed)); $iUserId = $this->oDb->updateRow(self::USER_TABLE, $this->getUserId(), ['subscribed'=>$iSubscribed]);
if(!$iUserId) return Livetrail::getResult(false, 'error.commit_db'); if(!$iUserId) return Livetrail::getResult(false, 'error.commit_db');
$this->asUserInfo['subscribed'] = $iSubscribed; $this->asUserInfo['subscribed'] = $iSubscribed;
return Livetrail::getResult(true, $iSubscribed?'account.subscribed':'account.unsubscribed'); return Livetrail::getResult(true, $iSubscribed?'account.subscribed':'account.unsubscribed');
@@ -131,11 +131,11 @@ class User extends PhpObject {
public function getSubscribedUsersInfo() { public function getSubscribedUsersInfo() {
$asSelect = array_keys($this->asUserInfo); $asSelect = array_keys($this->asUserInfo);
$asSelect[array_search('id', $asSelect)] = Db::getId(self::USER_TABLE).' AS id'; $asSelect[array_search('id', $asSelect)] = Db::getId(self::USER_TABLE).' AS id';
return $this->oDb->selectRows(array( return $this->oDb->selectRows([
'select'=>$asSelect, 'select'=>$asSelect,
'from'=>self::USER_TABLE, 'from'=>self::USER_TABLE,
'constraint'=>array('subscribed'=>self::USER_SUBSCRIBED) 'constraint'=>['subscribed'=>self::USER_SUBSCRIBED]
)); ]);
} }
public function login($sEmail, $sPassword, $sLang, $sTimezone, $sNickName='') { public function login($sEmail, $sPassword, $sLang, $sTimezone, $sNickName='') {
@@ -152,8 +152,8 @@ class User extends PhpObject {
//Check Email presence in DB //Check Email presence in DB
$asDBUser = $this->oDb->selectRow( $asDBUser = $this->oDb->selectRow(
self::USER_TABLE, self::USER_TABLE,
array('email' => $sEmail), ['email' => $sEmail],
array(Db::getId(self::USER_TABLE), 'password', 'clearance') [Db::getId(self::USER_TABLE), 'password', 'clearance']
); );
$iUserId = $asDBUser[Db::getId(self::USER_TABLE)] ?? 0; $iUserId = $asDBUser[Db::getId(self::USER_TABLE)] ?? 0;
@@ -166,7 +166,7 @@ class User extends PhpObject {
//Set password //Set password
elseif(empty($asDBUser['password'])) { elseif(empty($asDBUser['password'])) {
if(!$this->oDb->updateRow(self::USER_TABLE, $iUserId, array('password' => password_hash($sPassword, PASSWORD_DEFAULT)))) $sLangId = 'error.commit_db'; if(!$this->oDb->updateRow(self::USER_TABLE, $iUserId, ['password' => password_hash($sPassword, PASSWORD_DEFAULT)])) $sLangId = 'error.commit_db';
else { else {
$sLangId = 'account.password_set'; $sLangId = 'account.password_set';
$bSuccess = true; $bSuccess = true;
@@ -201,11 +201,11 @@ class User extends PhpObject {
$this->setTokenCookie(); $this->setTokenCookie();
} }
return Livetrail::getResult($bSuccess, $sLangId, array('subscribe'=>$bSubscribe)); return Livetrail::getResult($bSuccess, $sLangId, ['subscribe'=>$bSubscribe]);
} }
public function logout() { public function logout() {
if($this->getUserId() > 0) $this->oDb->updateRow(self::USER_TABLE, $this->getUserId(), array('token' => '', 'token_exp' => '0000-00-00 00:00:00')); if($this->getUserId() > 0) $this->oDb->updateRow(self::USER_TABLE, $this->getUserId(), ['token' => '', 'token_exp' => '0000-00-00 00:00:00']);
$this->clearSession(); $this->clearSession();
$this->clearCookie(); $this->clearCookie();
$this->setUserId(0); $this->setUserId(0);
@@ -213,38 +213,37 @@ class User extends PhpObject {
} }
public function updateNickname($sNickname) { public function updateNickname($sNickname) {
if($this->getUserId() > 0 && $sNickname!='') $this->oDb->updateRow(self::USER_TABLE, $this->getUserId(), array('name'=>$sNickname)); if($this->getUserId() > 0 && $sNickname!='') $this->oDb->updateRow(self::USER_TABLE, $this->getUserId(), ['name'=>$sNickname]);
} }
private function updateGravatar($iUserId, $sEmail) { private function updateGravatar($iUserId, $sEmail) {
$sImage = ($sEmail != '')?@file_get_contents('https://www.gravatar.com/avatar/'.md5($sEmail).'.png?d=404&s=24'):''; $sImage = ($sEmail != '')?@file_get_contents('https://www.gravatar.com/avatar/'.md5($sEmail).'.png?d=404&s=24'):'';
$this->oDb->updateRow(self::USER_TABLE, $iUserId, array('gravatar' => base64_encode($sImage))); $this->oDb->updateRow(self::USER_TABLE, $iUserId, ['gravatar' => base64_encode($sImage)]);
} }
public function checkUserClearance($iClearance) public function checkUserClearance($iClearance) {
{
return ($this->asUserInfo['clearance'] >= $iClearance); return ($this->asUserInfo['clearance'] >= $iClearance);
} }
public function setUserClearance($iUserId, $iClearance) { public function setUserClearance($iUserId, $iClearance) {
$bSuccess = false; $bSuccess = false;
$sLangId = ''; $sLangId = '';
$asLangParams = array(); $asLangParams = [];
if(!$this->checkUserClearance(self::CLEARANCE_ADMIN)) $sLangId = 'error.no_auth'; if(!$this->checkUserClearance(self::CLEARANCE_ADMIN)) $sLangId = 'error.no_auth';
else { else {
if(!in_array($iClearance, self::CLEARANCES)) { if(!in_array($iClearance, self::CLEARANCES)) {
$sLangId = 'error.impossible_value'; $sLangId = 'error.impossible_value';
$asLangParams = array($iClearance, 'clearance'); $asLangParams = [$iClearance, 'clearance'];
} }
else { else {
$iUserId = $this->oDb->updateRow(self::USER_TABLE, $iUserId, array('clearance'=>$iClearance)); $iUserId = $this->oDb->updateRow(self::USER_TABLE, $iUserId, ['clearance'=>$iClearance]);
if(!$iUserId) $sLangId = 'error.commit_db'; if(!$iUserId) $sLangId = 'error.commit_db';
else $bSuccess = true; else $bSuccess = true;
} }
} }
return Livetrail::getResult($bSuccess, $sLangId, array(), $asLangParams); return Livetrail::getResult($bSuccess, $sLangId, [], $asLangParams);
} }
/* Session */ /* Session */
@@ -285,7 +284,7 @@ class User extends PhpObject {
$asUser = $this->oDb->selectRow( $asUser = $this->oDb->selectRow(
self::USER_TABLE, self::USER_TABLE,
$iUserId, $iUserId,
array('clearance', 'token', 'token_exp') ['clearance', 'token', 'token_exp']
); );
//Check token value //Check token value
@@ -308,10 +307,10 @@ class User extends PhpObject {
$this->oDb->updateRow( $this->oDb->updateRow(
self::USER_TABLE, self::USER_TABLE,
$this->getUserId(), $this->getUserId(),
array( [
'token' => hash('sha256', $sCookieValue), 'token' => hash('sha256', $sCookieValue),
'token_exp' => date(Db::TIMESTAMP_FORMAT, time() + self::COOKIE_DURATION) 'token_exp' => date(Db::TIMESTAMP_FORMAT, time() + self::COOKIE_DURATION)
) ]
); );
$this->setCookie($sCookieValue, time() + self::COOKIE_DURATION); $this->setCookie($sCookieValue, time() + self::COOKIE_DURATION);
@@ -325,13 +324,13 @@ class User extends PhpObject {
setcookie( setcookie(
self::COOKIE_TOKEN, self::COOKIE_TOKEN,
$sValue, $sValue,
array( [
'expires' => $iExpires, 'expires' => $iExpires,
'path' => '/', 'path' => '/',
'secure' => (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off') || (($_SERVER['HTTP_X_FORWARDED_PROTO'] ?? '') === 'https'), 'secure' => (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off') || (($_SERVER['HTTP_X_FORWARDED_PROTO'] ?? '') === 'https'),
'httponly' => true, 'httponly' => true,
'samesite' => 'Lax' 'samesite' => 'Lax'
) ]
); );
} }
+6 -1
View File
@@ -1,6 +1,10 @@
{ {
"devDependencies": { "devDependencies": {
"@eslint/js": "^9.19.0",
"@vitejs/plugin-vue": "^6.0.8", "@vitejs/plugin-vue": "^6.0.8",
"eslint": "^9.19.0",
"eslint-plugin-vue": "^9.32.0",
"globals": "^15.14.0",
"vite": "^8.1.5" "vite": "^8.1.5"
}, },
"name": "livetrail", "name": "livetrail",
@@ -10,7 +14,8 @@
"private": true, "private": true,
"scripts": { "scripts": {
"dev": "vite build --mode development --watch", "dev": "vite build --mode development --watch",
"prod": "vite build" "prod": "vite build",
"lint": "eslint src"
}, },
"keywords": [], "keywords": [],
"author": "Franzz", "author": "Franzz",
+1 -1
View File
@@ -4,4 +4,4 @@ require __DIR__.'/../vendor/autoload.php';
use Franzz\Livetrail\Controller; use Franzz\Livetrail\Controller;
echo (new Controller())->handle(__FILE__, $argv ?? array()); echo (new Controller())->handle(__FILE__, $argv ?? []);
+7
View File
@@ -52,6 +52,13 @@ COMPOSER=composer.dev.json composer update
This makes Composer link `vendor/franzz/objects` to `../objects` and autoload that namespace directly from the local source path. Production continues to use `composer.json`, which installs `franzz/objects` from its Git repository. Commit and publish `objects` changes before updating/deploying a Livetrail version that relies on them. This makes Composer link `vendor/franzz/objects` to `../objects` and autoload that namespace directly from the local source path. Production continues to use `composer.json`, which installs `franzz/objects` from its Git repository. Commit and publish `objects` changes before updating/deploying a Livetrail version that relies on them.
## Before Committing
Run manually before pushing:
* `composer lint` - checks PHP style (php-cs-fixer, dry-run); `composer lint-fix` applies it. Requires the dev dependencies (`COMPOSER=composer.dev.json composer update`, see Local Development above).
* `npm run lint` - checks JS/Vue style (ESLint) on demand. It also already runs automatically as part of `npm run dev` (reports issues but never blocks the watcher) and `npm run prod` (aborts the build if any lint error is found).
## To Do List ## To Do List
* Add mail frequency slider * Add mail frequency slider
+69644
View File
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -105,7 +105,7 @@ export default {
window.removeEventListener('hashchange', this.onBrowserHashChange); window.removeEventListener('hashchange', this.onBrowserHashChange);
this.mobileMediaQuery.removeEventListener('change', this.updateMobile); this.mobileMediaQuery.removeEventListener('change', this.updateMobile);
} }
} };
</script> </script>
<template> <template>
<div id="main"> <div id="main">
+8 -8
View File
@@ -43,7 +43,7 @@ export default {
for(const [sType, aoElems] of Object.entries(aoElemTypes)) { for(const [sType, aoElems] of Object.entries(aoElemTypes)) {
this.elems[sType] = {}; this.elems[sType] = {};
for(const [iKey, oElem] of Object.entries(aoElems)) { for(const oElem of Object.values(aoElems)) {
oElem.type = sType; oElem.type = sType;
this.elems[sType][oElem.id] = oElem; this.elems[sType][oElem.id] = oElem;
} }
@@ -53,7 +53,7 @@ export default {
this.api.post('admin_create', {type: sType}) this.api.post('admin_create', {type: sType})
.then((aoNewElemTypes) => { .then((aoNewElemTypes) => {
for(const [sType, aoNewElems] of Object.entries(aoNewElemTypes)) { for(const [sType, aoNewElems] of Object.entries(aoNewElemTypes)) {
for(const [iKey, oNewElem] of Object.entries(aoNewElems)) { for(const oNewElem of Object.values(aoNewElems)) {
oNewElem.type = sType; oNewElem.type = sType;
this.elems[sType][oNewElem.id] = oNewElem; this.elems[sType][oNewElem.id] = oNewElem;
this.addFeedback('success', this.l('admin.create_success'), {'create':sType}); this.addFeedback('success', this.l('admin.create_success'), {'create':sType});
@@ -114,7 +114,7 @@ export default {
.catch((oError) => {this.addFeedback('error', oError.desc_lang_text, {'update':'project'});}); .catch((oError) => {this.addFeedback('error', oError.desc_lang_text, {'update':'project'});});
} }
} }
} };
</script> </script>
<template> <template>
<div id="admin"> <div id="admin">
@@ -134,7 +134,7 @@ export default {
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
<tr v-for="project in elems.project"> <tr v-for="project in elems.project" :key="project.id">
<td>{{ project.id }}</td> <td>{{ project.id }}</td>
<td><AdminInput :type="'text'" :name="'name'" :elem="project" /></td> <td><AdminInput :type="'text'" :name="'name'" :elem="project" /></td>
<td>{{ project.mode }}</td> <td>{{ project.mode }}</td>
@@ -163,7 +163,7 @@ export default {
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
<tr v-for="feed in elems.feed"> <tr v-for="feed in elems.feed" :key="feed.id">
<td>{{ feed.id }}</td> <td>{{ feed.id }}</td>
<td><AdminInput :type="'text'" :name="'ref_feed_id'" :elem="feed" /></td> <td><AdminInput :type="'text'" :name="'ref_feed_id'" :elem="feed" /></td>
<td><AdminInput :type="'number'" :name="'id_spot'" :elem="feed" /></td> <td><AdminInput :type="'number'" :name="'id_spot'" :elem="feed" /></td>
@@ -189,7 +189,7 @@ export default {
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
<tr v-for="spot in elems.spot"> <tr v-for="spot in elems.spot" :key="spot.id">
<td>{{ spot.id }}</td> <td>{{ spot.id }}</td>
<td>{{ spot.ref_spot_id }}</td> <td>{{ spot.ref_spot_id }}</td>
<td>{{ spot.name }}</td> <td>{{ spot.name }}</td>
@@ -212,7 +212,7 @@ export default {
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
<tr v-for="user in elems.user"> <tr v-for="user in elems.user" :key="user.id">
<td>{{ user.id }}</td> <td>{{ user.id }}</td>
<td class="left">{{ user.name }}</td> <td class="left">{{ user.name }}</td>
<td class="left">{{ user.email }}</td> <td class="left">{{ user.email }}</td>
@@ -228,7 +228,7 @@ export default {
<AppButton :classes="'refresh'" :text="l('project.update_messages')" :icon="'refresh'" @click="updateProject" /> <AppButton :classes="'refresh'" :text="l('project.update_messages')" :icon="'refresh'" @click="updateProject" />
</div> </div>
<div id="feedback" class="feedback"> <div id="feedback" class="feedback">
<p v-for="feedback in feedbacks" :class="feedback.type">{{ feedback.msg }}</p> <p v-for="(feedback, index) in feedbacks" :key="index" :class="feedback.type">{{ feedback.msg }}</p>
</div> </div>
</div> </div>
</template> </template>
+1 -1
View File
@@ -10,7 +10,7 @@
return this.elem[this.name]; return this.elem[this.name];
} }
} }
} };
</script> </script>
<template> <template>
+1 -1
View File
@@ -12,7 +12,7 @@ export default {
iconClasses: String, iconClasses: String,
iconSize: String iconSize: String
} }
} };
</script> </script>
<template> <template>
<button :class="classes"><AppIcon :icon="icon" :text="text" :classes="iconClasses" :size="iconSize" /></button> <button :class="classes"><AppIcon :icon="icon" :text="text" :classes="iconClasses" :size="iconSize" /></button>
+1 -1
View File
@@ -37,7 +37,7 @@ export default {
return this.transform || null; return this.transform || null;
} }
} }
} };
</script> </script>
<template> <template>
+1 -1
View File
@@ -38,7 +38,7 @@ export default {
].filter(Boolean).join(' '); ].filter(Boolean).join(' ');
} }
} }
} };
</script> </script>
<template> <template>
+5 -5
View File
@@ -183,7 +183,7 @@ export default {
positionFromTop: 0, positionFromTop: 0,
resizeDuration: parseFloat(this.getStyleProperty('--trans-slow')), resizeDuration: parseFloat(this.getStyleProperty('--trans-slow')),
hasVideo: true, hasVideo: true,
onMediaChange: async (oMedia) => { onMediaChange: async(oMedia) => {
this.hash.items = [this.project.codename, 'media', oMedia.id]; this.hash.items = [this.project.codename, 'media', oMedia.id];
if(oMedia.set == 'post-medias') { if(oMedia.set == 'post-medias') {
(await this.feed.findPost('media', oMedia.id))?.panMapToMarker(); (await this.feed.findPost('media', oMedia.id))?.panMapToMarker();
@@ -439,7 +439,7 @@ export default {
onMarkerClick(oEvent, oMarker) { onMarkerClick(oEvent, oMarker) {
oEvent.preventDefault(); oEvent.preventDefault();
oEvent.stopPropagation(); oEvent.stopPropagation();
switch (oMarker.type) { switch(oMarker.type) {
case 'project': case 'project':
this.hash.items = [oMarker.codename]; this.hash.items = [oMarker.codename];
break; break;
@@ -448,7 +448,7 @@ export default {
} }
}, },
onMarkerHover(oEvent, oMarker) { onMarkerHover(oEvent, oMarker) {
switch (oMarker.type) { switch(oMarker.type) {
case 'project': case 'project':
if(oEvent.type == 'mouseenter') this.openProjectPopup(oMarker); if(oEvent.type == 'mouseenter') this.openProjectPopup(oMarker);
else this.closePopup(); else this.closePopup();
@@ -651,7 +651,7 @@ export default {
getStyleProperty(sProperty) { getStyleProperty(sProperty) {
return getComputedStyle(this.$el).getPropertyValue(sProperty).trim(); return getComputedStyle(this.$el).getPropertyValue(sProperty).trim();
}, },
isMarkerVisible(oLngLat){ isMarkerVisible(oLngLat) {
return !!this.map && this.map.getBounds().contains(oLngLat); return !!this.map && this.map.getBounds().contains(oLngLat);
}, },
onPanelToggle(sPanel, bNewValue, iAnimDuration=500) { onPanelToggle(sPanel, bNewValue, iAnimDuration=500) {
@@ -674,7 +674,7 @@ export default {
this.settings = vPanel; this.settings = vPanel;
} }
} }
} };
</script> </script>
<template> <template>
+2 -2
View File
@@ -55,7 +55,7 @@ export default {
manageLogin() { manageLogin() {
if(this.loginLoading) return; if(this.loginLoading) return;
var regexEmail = /^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/; let regexEmail = /^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
if(!regexEmail.test(this.user.email)) this.feedbacks.push({type:'error', 'msg':this.lang.get('account.invalid_email')}); if(!regexEmail.test(this.user.email)) this.feedbacks.push({type:'error', 'msg':this.lang.get('account.invalid_email')});
else if(this.settingPassword && this.password !== this.passwordConfirmation) this.feedbacks.push({type:'error', 'msg':this.lang.get('account.password_mismatch')}); else if(this.settingPassword && this.password !== this.passwordConfirmation) this.feedbacks.push({type:'error', 'msg':this.lang.get('account.password_mismatch')});
else { else {
@@ -90,7 +90,7 @@ export default {
} }
} }
} }
} };
</script> </script>
<template> <template>
+2 -2
View File
@@ -215,7 +215,7 @@ export default {
return this.$el.getBoundingClientRect().width; return this.$el.getBoundingClientRect().width;
} }
} }
} };
</script> </script>
<template> <template>
@@ -226,7 +226,7 @@ export default {
<ProjectPost v-else :options="{type: 'poster', relative_time: lang.get('post.new_message')}" /> <ProjectPost v-else :options="{type: 'poster', relative_time: lang.get('post.new_message')}" />
</div> </div>
<div v-if="project" v-show="!loadingPost" id="feed-posts"> <div v-if="project" v-show="!loadingPost" id="feed-posts">
<ProjectPost v-for="post in posts" :options="post" ref="posts" /> <ProjectPost v-for="post in posts" :key="post.ref" :options="post" ref="posts" />
</div> </div>
<div id="feed-footer" v-if="loading"> <div id="feed-footer" v-if="loading">
<ProjectPost :options="{type: 'loading', headerless: true}" /> <ProjectPost :options="{type: 'loading', headerless: true}" />
+1 -1
View File
@@ -4,7 +4,7 @@ export default {
options: Object options: Object
}, },
inject: ['lang'] inject: ['lang']
} };
</script> </script>
<template> <template>
+2 -2
View File
@@ -17,7 +17,7 @@ export default {
data() { data() {
return { return {
title:'' title:''
} };
}, },
inject: ['lang', 'isMobile'], inject: ['lang', 'isMobile'],
mounted() { mounted() {
@@ -32,7 +32,7 @@ export default {
this.$refs.link.click(); this.$refs.link.click();
} }
} }
} };
</script> </script>
<template> <template>
+2 -2
View File
@@ -48,7 +48,7 @@ export default {
; ;
} }
} }
} };
</script> </script>
<template> <template>
@@ -98,7 +98,7 @@ export default {
<div v-if="options.medias" class="section medias"> <div v-if="options.medias" class="section medias">
<appIcon v-if="options.type=='message'" icon="media" width="fixed" size="lg" :text="lang.get('media.nearby')" /> <appIcon v-if="options.type=='message'" icon="media" width="fixed" size="lg" :text="lang.get('media.nearby')" />
<div class="medias-list"> <div class="medias-list">
<projectMediaLink v-for="media in options?.medias" :options="media" :type="'marker'" /> <projectMediaLink v-for="media in options?.medias" :key="media.id_media" :options="media" :type="'marker'" />
</div> </div>
</div> </div>
</div> </div>
+2 -1
View File
@@ -159,6 +159,7 @@
case 'media': case 'media':
this.$refs.medialink.openMedia(); this.$refs.medialink.openMedia();
if(this.relatedMarker) return this.openMarkerPopup(); if(this.relatedMarker) return this.openMarkerPopup();
else return Promise.resolve();
default: default:
return Promise.resolve(); return Promise.resolve();
} }
@@ -168,7 +169,7 @@
//Auto-adjust text area height //Auto-adjust text area height
if(this.options.type == 'poster') autosize(this.$refs.post); if(this.options.type == 'poster') autosize(this.$refs.post);
} }
} };
</script> </script>
<template> <template>
+1 -1
View File
@@ -27,7 +27,7 @@ export default {
(bDifferentTimeZone?sTime:null); (bDifferentTimeZone?sTime:null);
} }
} }
} };
</script> </script>
<template> <template>
+2 -2
View File
@@ -83,7 +83,7 @@ export default {
return this.$el.getBoundingClientRect().width; return this.$el.getBoundingClientRect().width;
} }
} }
} };
</script> </script>
<template> <template>
@@ -154,7 +154,7 @@ export default {
</div> </div>
<div v-if="project?.id && !isMobile()" id="legend" class="panel-control panel-control-bottom"> <div v-if="project?.id && !isMobile()" id="legend" class="panel-control panel-control-bottom">
<div class="panel-control-elem"> <div class="panel-control-elem">
<div v-for="(color, hikeType) in hikes.colors" class="track"> <div v-for="(color, hikeType) in hikes.colors" :key="hikeType" class="track">
<span class="line" :style="'background-color:'+color+';'"></span> <span class="line" :style="'background-color:'+color+';'"></span>
<span class="desc">{{ lang.get('track.'+hikeType) }}</span> <span class="desc">{{ lang.get('track.'+hikeType) }}</span>
</div> </div>
+1 -1
View File
@@ -123,7 +123,7 @@ export default {
else this.addLog('upload.position.unsupported'); else this.addLog('upload.position.unsupported');
} }
} }
} };
</script> </script>
<template> <template>
<div id="upload"> <div id="upload">
+3 -3
View File
@@ -2,7 +2,7 @@
export function copyTextToClipboard(text) { export function copyTextToClipboard(text) {
if(!navigator.clipboard) { if(!navigator.clipboard) {
var textArea = document.createElement('textarea'); let textArea = document.createElement('textarea');
textArea.value = text; textArea.value = text;
// Avoid scrolling to bottom // Avoid scrolling to bottom
@@ -15,9 +15,9 @@ export function copyTextToClipboard(text) {
textArea.select(); textArea.select();
try { try {
var successful = document.execCommand('copy'); let successful = document.execCommand('copy');
if(!successful) console.error('Fallback: Oops, unable to copy', text); if(!successful) console.error('Fallback: Oops, unable to copy', text);
} catch (err) { } catch(err) {
console.error('Fallback: Oops, unable to copy', err); console.error('Fallback: Oops, unable to copy', err);
} }
+53 -53
View File
@@ -37,7 +37,7 @@ export default class Lightbox {
} }
init() { init() {
if (document.readyState === 'loading') { if(document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', () => { document.addEventListener('DOMContentLoaded', () => {
this.build(); this.build();
this.enable(); this.enable();
@@ -58,7 +58,7 @@ export default class Lightbox {
onBodyClick(event) { onBodyClick(event) {
const link = event.target.closest('a[data-lightbox], area[data-lightbox]'); const link = event.target.closest('a[data-lightbox], area[data-lightbox]');
if (!link) return; if(!link) return;
event.preventDefault(); event.preventDefault();
this.start(link); this.start(link);
} }
@@ -68,7 +68,7 @@ export default class Lightbox {
} }
build() { build() {
if (!document.getElementById('lightbox')) { if(!document.getElementById('lightbox')) {
const wrapper = document.createElement('div'); const wrapper = document.createElement('div');
wrapper.innerHTML = ` wrapper.innerHTML = `
<div id="lightboxOverlay" tabindex="-1" class="lightboxOverlay"></div> <div id="lightboxOverlay" tabindex="-1" class="lightboxOverlay"></div>
@@ -129,21 +129,21 @@ export default class Lightbox {
this.overlay.addEventListener('click', () => this.end()); this.overlay.addEventListener('click', () => this.end());
this.dataContainer.addEventListener('click', () => this.end()); this.dataContainer.addEventListener('click', () => this.end());
this.lightbox.addEventListener('click', (event) => { this.lightbox.addEventListener('click', (event) => {
if (event.target === this.lightbox) this.end(); if(event.target === this.lightbox) this.end();
}); });
this.outerContainer.addEventListener('click', (event) => { this.outerContainer.addEventListener('click', (event) => {
if (event.target === this.outerContainer) this.end(); if(event.target === this.outerContainer) this.end();
event.stopPropagation(); event.stopPropagation();
}); });
this.prev.addEventListener('click', (event) => { this.prev.addEventListener('click', (event) => {
event.preventDefault(); event.preventDefault();
if (this.currentImageIndex === 0) this.changeImage(this.album.length - 1); if(this.currentImageIndex === 0) this.changeImage(this.album.length - 1);
else this.changeImage(this.currentImageIndex - 1); else this.changeImage(this.currentImageIndex - 1);
}); });
this.next.addEventListener('click', (event) => { this.next.addEventListener('click', (event) => {
event.preventDefault(); event.preventDefault();
if (this.currentImageIndex === this.album.length - 1) this.changeImage(0); if(this.currentImageIndex === this.album.length - 1) this.changeImage(0);
else this.changeImage(this.currentImageIndex + 1); else this.changeImage(this.currentImageIndex + 1);
}); });
@@ -157,7 +157,7 @@ export default class Lightbox {
this.end(); this.end();
}); });
this.closeButton.addEventListener('keyup', (event) => { this.closeButton.addEventListener('keyup', (event) => {
if (event.key === 'Enter' || event.key === ' ') this.end(); if(event.key === 'Enter' || event.key === ' ') this.end();
}); });
this.nav.addEventListener('wheel', this.boundOnWheel, { passive: false }); this.nav.addEventListener('wheel', this.boundOnWheel, { passive: false });
@@ -184,13 +184,13 @@ export default class Lightbox {
const links = [...document.querySelectorAll(`${link.tagName}[data-lightbox="${CSS.escape(setName)}"]`)]; const links = [...document.querySelectorAll(`${link.tagName}[data-lightbox="${CSS.escape(setName)}"]`)];
links.forEach((item, index) => { links.forEach((item, index) => {
this.addToAlbum(item); this.addToAlbum(item);
if (item === link) imageNumber = index; if(item === link) imageNumber = index;
}); });
this.fade(this.overlay, true, this.options.fadeDuration); this.fade(this.overlay, true, this.options.fadeDuration);
this.fade(this.lightbox, true, this.options.fadeDuration); this.fade(this.lightbox, true, this.options.fadeDuration);
if (this.options.disableScrolling) document.body.classList.add('lb-disable-scrolling'); if(this.options.disableScrolling) document.body.classList.add('lb-disable-scrolling');
window.addEventListener('resize', this.boundOnResize); window.addEventListener('resize', this.boundOnResize);
this.changeImage(imageNumber); this.changeImage(imageNumber);
@@ -217,15 +217,15 @@ export default class Lightbox {
refreshAlbum() { refreshAlbum() {
const current = this.album[this.currentImageIndex]; const current = this.album[this.currentImageIndex];
if (!current?.set) return; if(!current?.set) return;
const links = [...document.querySelectorAll(`a[data-lightbox="${CSS.escape(current.set)}"], area[data-lightbox="${CSS.escape(current.set)}"]`)]; const links = [...document.querySelectorAll(`a[data-lightbox="${CSS.escape(current.set)}"], area[data-lightbox="${CSS.escape(current.set)}"]`)];
if (!links.length) return; if(!links.length) return;
const existingKeys = new Set(this.album.map((media) => this.getMediaKey(media))); const existingKeys = new Set(this.album.map((media) => this.getMediaKey(media)));
links.forEach((link) => { links.forEach((link) => {
const key = this.getLinkMediaKey(link); const key = this.getLinkMediaKey(link);
if (existingKeys.has(key)) return; if(existingKeys.has(key)) return;
this.addToAlbum(link); this.addToAlbum(link);
existingKeys.add(key); existingKeys.add(key);
@@ -257,10 +257,10 @@ export default class Lightbox {
} }
getDataContainerHeight(width = null) { getDataContainerHeight(width = null) {
if (!this.dataContainer) return 0; if(!this.dataContainer) return 0;
const currentWidth = this.dataContainer.style.width; const currentWidth = this.dataContainer.style.width;
if (width !== null) this.dataContainer.style.width = `${width}px`; if(width !== null) this.dataContainer.style.width = `${width}px`;
const height = Math.ceil(this.dataContainer.getBoundingClientRect().height || this.dataContainer.offsetHeight || 0); const height = Math.ceil(this.dataContainer.getBoundingClientRect().height || this.dataContainer.offsetHeight || 0);
this.dataContainer.style.width = currentWidth; this.dataContainer.style.width = currentWidth;
@@ -268,7 +268,7 @@ export default class Lightbox {
} }
getMediaSize(media, maxWidth, maxHeight) { getMediaSize(media, maxWidth, maxHeight) {
if (media.width <= maxWidth && media.height <= maxHeight) { if(media.width <= maxWidth && media.height <= maxHeight) {
return { return {
width: media.width, width: media.width,
height: media.height height: media.height
@@ -278,7 +278,7 @@ export default class Lightbox {
const widthRatio = media.width / maxWidth; const widthRatio = media.width / maxWidth;
const heightRatio = media.height / maxHeight; const heightRatio = media.height / maxHeight;
if (widthRatio > heightRatio) { if(widthRatio > heightRatio) {
return { return {
width: maxWidth, width: maxWidth,
height: Math.round(media.height / widthRatio) height: Math.round(media.height / widthRatio)
@@ -296,12 +296,12 @@ export default class Lightbox {
const maxOuterHeight = Math.max(window.innerHeight - this.options.positionFromTop, 1); const maxOuterHeight = Math.max(window.innerHeight - this.options.positionFromTop, 1);
let fittedSize = size; let fittedSize = size;
for (let i = 0; i < 5; i++) { for(let i = 0; i < 5; i++) {
const containerWidth = fittedSize.width + this.containerPadding.left + this.containerPadding.right + border.left + border.right; const containerWidth = fittedSize.width + this.containerPadding.left + this.containerPadding.right + border.left + border.right;
const containerHeight = fittedSize.height + this.containerPadding.top + this.containerPadding.bottom + border.top + border.bottom; const containerHeight = fittedSize.height + this.containerPadding.top + this.containerPadding.bottom + border.top + border.bottom;
const dataHeight = this.getDataContainerHeight(containerWidth); const dataHeight = this.getDataContainerHeight(containerWidth);
const overflow = Math.ceil(containerHeight + dataHeight - maxOuterHeight); const overflow = Math.ceil(containerHeight + dataHeight - maxOuterHeight);
if (overflow <= 0 || fittedSize.height <= 1) break; if(overflow <= 0 || fittedSize.height <= 1) break;
const height = Math.max(fittedSize.height - overflow, 1); const height = Math.max(fittedSize.height - overflow, 1);
fittedSize = { fittedSize = {
@@ -328,7 +328,7 @@ export default class Lightbox {
changeImage(index) { changeImage(index) {
const media = this.album[index]; const media = this.album[index];
if (!media) return; if(!media) return;
this.updateDetails(media, false); this.updateDetails(media, false);
this.hideElements([this.dataContainer]); this.hideElements([this.dataContainer]);
@@ -343,7 +343,7 @@ export default class Lightbox {
this.options.onMediaChange(media); this.options.onMediaChange(media);
if (media.type === 'video') { if(media.type === 'video') {
this.image.removeAttribute('src'); this.image.removeAttribute('src');
this.container.classList.add('lb-video-nav'); this.container.classList.add('lb-video-nav');
this.video.onloadedmetadata = () => { this.video.onloadedmetadata = () => {
@@ -360,7 +360,7 @@ export default class Lightbox {
this.image.alt = media.alt; this.image.alt = media.alt;
let width = this.image.naturalWidth; let width = this.image.naturalWidth;
let height = this.image.naturalHeight; let height = this.image.naturalHeight;
if (Math.abs(media.orientation) === 90 && width > height) { if(Math.abs(media.orientation) === 90 && width > height) {
const tmp = width; const tmp = width;
width = height; width = height;
height = tmp; height = tmp;
@@ -375,13 +375,13 @@ export default class Lightbox {
} }
sizeOverlay() { sizeOverlay() {
if (this.resizeTimer) clearTimeout(this.resizeTimer); if(this.resizeTimer) clearTimeout(this.resizeTimer);
if (!this.album.length) return; if(!this.album.length) return;
this.resizeTimer = window.setTimeout(() => { this.resizeTimer = window.setTimeout(() => {
const current = this.album[this.currentImageIndex]; const current = this.album[this.currentImageIndex];
if (!current) return; if(!current) return;
if (current.type === 'image') this.changeImage(this.currentImageIndex); if(current.type === 'image') this.changeImage(this.currentImageIndex);
else this.updateSize(this.currentImageIndex); else this.updateSize(this.currentImageIndex);
}, 200); }, 200);
} }
@@ -406,7 +406,7 @@ export default class Lightbox {
showImage() { showImage() {
this.fade(this.loader, false, 0); this.fade(this.loader, false, 0);
if (this.options.hasVideo && this.album[this.currentImageIndex].type === 'video') this.fade(this.video, true, this.options.imageFadeDuration); if(this.options.hasVideo && this.album[this.currentImageIndex].type === 'video') this.fade(this.video, true, this.options.imageFadeDuration);
else this.fade(this.image, true, this.options.imageFadeDuration); else this.fade(this.image, true, this.options.imageFadeDuration);
this.updateNav(); this.updateNav();
@@ -421,17 +421,17 @@ export default class Lightbox {
this.setVisible(this.next, false); this.setVisible(this.next, false);
const alwaysShowNav = ('ontouchstart' in window) && this.options.alwaysShowNavOnTouchDevices; const alwaysShowNav = ('ontouchstart' in window) && this.options.alwaysShowNavOnTouchDevices;
if (this.album.length <= 1) return; if(this.album.length <= 1) return;
if (this.options.wrapAround) { if(this.options.wrapAround) {
this.setVisible(this.prev, true); this.setVisible(this.prev, true);
this.setVisible(this.next, true); this.setVisible(this.next, true);
} else { } else {
if (this.currentImageIndex > 0) this.setVisible(this.prev, true); if(this.currentImageIndex > 0) this.setVisible(this.prev, true);
if (this.currentImageIndex < this.album.length - 1) this.setVisible(this.next, true); if(this.currentImageIndex < this.album.length - 1) this.setVisible(this.next, true);
} }
if (alwaysShowNav) { if(alwaysShowNav) {
this.prev.style.opacity = '1'; this.prev.style.opacity = '1';
this.next.style.opacity = '1'; this.next.style.opacity = '1';
} else { } else {
@@ -441,19 +441,19 @@ export default class Lightbox {
} }
updateDetails(media = this.album[this.currentImageIndex], show = true) { updateDetails(media = this.album[this.currentImageIndex], show = true) {
if (!media) return; if(!media) return;
if (media.title) { if(media.title) {
if (this.options.sanitizeTitle) this.caption.textContent = media.title; if(this.options.sanitizeTitle) this.caption.textContent = media.title;
else this.caption.innerHTML = media.title; else this.caption.innerHTML = media.title;
if (show) this.fade(this.caption, true, 200); if(show) this.fade(this.caption, true, 200);
else this.setVisible(this.caption, true); else this.setVisible(this.caption, true);
} else { } else {
this.caption.textContent = ''; this.caption.textContent = '';
this.setVisible(this.caption, false); this.setVisible(this.caption, false);
} }
if (show) { if(show) {
this.fade(this.closeButton, true, 200); this.fade(this.closeButton, true, 200);
this.outerContainer.classList.remove('animating'); this.outerContainer.classList.remove('animating');
this.fade(this.dataContainer, true, this.options.resizeDuration); this.fade(this.dataContainer, true, this.options.resizeDuration);
@@ -468,11 +468,11 @@ export default class Lightbox {
preloadNeighboringImages() { preloadNeighboringImages() {
const next = this.album[this.currentImageIndex + 1]; const next = this.album[this.currentImageIndex + 1];
const prev = this.album[this.currentImageIndex - 1]; const prev = this.album[this.currentImageIndex - 1];
if (next && next.type === 'image') { if(next && next.type === 'image') {
const preloadNext = new Image(); const preloadNext = new Image();
preloadNext.src = next.link; preloadNext.src = next.link;
} }
if (prev && prev.type === 'image') { if(prev && prev.type === 'image') {
const preloadPrev = new Image(); const preloadPrev = new Image();
preloadPrev.src = prev.link; preloadPrev.src = prev.link;
} }
@@ -490,25 +490,25 @@ export default class Lightbox {
} }
keyboardAction(event) { keyboardAction(event) {
switch (event.key) { switch(event.key) {
case 'Escape': case 'Escape':
event.stopPropagation(); event.stopPropagation();
this.end(); this.end();
break; break;
case 'ArrowLeft': case 'ArrowLeft':
if (this.currentImageIndex !== 0) this.changeImage(this.currentImageIndex - 1); if(this.currentImageIndex !== 0) this.changeImage(this.currentImageIndex - 1);
else if (this.options.wrapAround && this.album.length > 1) this.changeImage(this.album.length - 1); else if(this.options.wrapAround && this.album.length > 1) this.changeImage(this.album.length - 1);
break; break;
case 'ArrowRight': case 'ArrowRight':
if (this.currentImageIndex !== this.album.length - 1) this.changeImage(this.currentImageIndex + 1); if(this.currentImageIndex !== this.album.length - 1) this.changeImage(this.currentImageIndex + 1);
else if (this.options.wrapAround && this.album.length > 1) this.changeImage(0); else if(this.options.wrapAround && this.album.length > 1) this.changeImage(0);
break; break;
} }
} }
onWheel(event) { onWheel(event) {
const media = this.album[this.currentImageIndex]; const media = this.album[this.currentImageIndex];
if (!media || media.type === 'video') return; if(!media || media.type === 'video') return;
event.preventDefault(); event.preventDefault();
const rect = this.image.getBoundingClientRect(); const rect = this.image.getBoundingClientRect();
@@ -534,7 +534,7 @@ export default class Lightbox {
onDragStart(event) { onDragStart(event) {
const scale = parseFloat(this.image.style.getPropertyValue('--scale') || '1'); const scale = parseFloat(this.image.style.getPropertyValue('--scale') || '1');
if (scale <= 1) return; if(scale <= 1) return;
this.gMouseDownOffsetX = event.clientX - parseFloat(this.image.style.getPropertyValue('--translate-x') || '0'); this.gMouseDownOffsetX = event.clientX - parseFloat(this.image.style.getPropertyValue('--translate-x') || '0');
this.gMouseDownOffsetY = event.clientY - parseFloat(this.image.style.getPropertyValue('--translate-y') || '0'); this.gMouseDownOffsetY = event.clientY - parseFloat(this.image.style.getPropertyValue('--translate-y') || '0');
@@ -582,7 +582,7 @@ export default class Lightbox {
} }
setImageTransform(transform) { setImageTransform(transform) {
if (!this.image) return; if(!this.image) return;
this.image.style.setProperty('--scale', String(transform.scale)); this.image.style.setProperty('--scale', String(transform.scale));
this.image.style.setProperty('--translate-x', `${transform.translateX}px`); this.image.style.setProperty('--translate-x', `${transform.translateX}px`);
this.image.style.setProperty('--translate-y', `${transform.translateY}px`); this.image.style.setProperty('--translate-y', `${transform.translateY}px`);
@@ -595,17 +595,17 @@ export default class Lightbox {
} }
setVisible(element, visible) { setVisible(element, visible) {
if (!element) return; if(!element) return;
element.style.visibility = visible ? 'visible' : 'hidden'; element.style.visibility = visible ? 'visible' : 'hidden';
element.style.pointerEvents = visible ? '' : 'none'; element.style.pointerEvents = visible ? '' : 'none';
} }
fade(element, show, duration, done) { fade(element, show, duration, done) {
if (!element) return; if(!element) return;
const safeDuration = duration || 0; const safeDuration = duration || 0;
element.style.transition = `opacity ${safeDuration}ms`; element.style.transition = `opacity ${safeDuration}ms`;
if (show) { if(show) {
this.setVisible(element, true); this.setVisible(element, true);
requestAnimationFrame(() => { requestAnimationFrame(() => {
element.style.opacity = element === this.overlay ? '0.8' : '1'; element.style.opacity = element === this.overlay ? '0.8' : '1';
@@ -618,7 +618,7 @@ export default class Lightbox {
}, safeDuration); }, safeDuration);
} }
if (typeof done === 'function') { if(typeof done === 'function') {
window.setTimeout(done, safeDuration); window.setTimeout(done, safeDuration);
} }
} }
@@ -631,7 +631,7 @@ export default class Lightbox {
window.removeEventListener('resize', this.boundOnResize); window.removeEventListener('resize', this.boundOnResize);
window.removeEventListener('mousemove', this.boundOnDragMove); window.removeEventListener('mousemove', this.boundOnDragMove);
if(dispose){ if(dispose) {
this.disable(); this.disable();
if(this.resizeTimer) clearTimeout(this.resizeTimer); if(this.resizeTimer) clearTimeout(this.resizeTimer);
window.removeEventListener('mouseup', this.boundOnDragEnd); window.removeEventListener('mouseup', this.boundOnDragEnd);
@@ -645,6 +645,6 @@ export default class Lightbox {
this.options.onClosing(); this.options.onClosing();
} }
if (this.options.disableScrolling) document.body.classList.remove('lb-disable-scrolling'); if(this.options.disableScrolling) document.body.classList.remove('lb-disable-scrolling');
} }
} }
+32
View File
@@ -16,6 +16,7 @@ export default defineConfig(({ mode }) => {
publicDir: false, publicDir: false,
plugins: [ plugins: [
livetrailPublicAssets(), livetrailPublicAssets(),
livetrailLint(isDev),
vue() vue()
], ],
build: { build: {
@@ -62,6 +63,37 @@ export default defineConfig(({ mode }) => {
}; };
}); });
function livetrailLint(isDev) {
return {
name: 'livetrail-lint',
apply: 'build',
//In `--watch` mode Vite re-runs the whole plugin pipeline (including
//buildStart) on every rebuild it triggers from a file change, so this
//alone gives re-linting on every save with no separate watchChange
//wiring needed.
async buildStart() {
await runLint(isDev);
}
};
}
async function runLint(isDev) {
const { ESLint } = await import('eslint');
const eslint = new ESLint({ cwd: ROOT });
const results = await eslint.lintFiles(['src']);
const formatter = await eslint.loadFormatter('stylish');
const output = await formatter.format(results);
if (output) process.stdout.write(output + '\n');
//Dev keeps watching regardless - the point is fast feedback, not a gate.
//Prod aborts the build so bad code can't ship.
const hasErrors = results.some((result) => result.errorCount > 0);
if (hasErrors && !isDev) {
throw new Error('ESLint found errors in src/ - aborting production build.');
}
}
function livetrailPublicAssets() { function livetrailPublicAssets() {
return { return {
name: 'livetrail-public-assets', name: 'livetrail-public-assets',