@@ -14,14 +14,14 @@ DEBUG = 0
|
||||
; How to behave on 'non-fatal' errors
|
||||
; If TRUE, the framework, after having logged stack trace and errors, stops execution
|
||||
; (die without any status) when a non-fatal error is detected.
|
||||
; Tip: You should not change this.
|
||||
; Hint: You should not change this.
|
||||
; Syntax: TRUE | FALSE
|
||||
; Default: FALSE
|
||||
HALT = FALSE
|
||||
|
||||
; Timezone to use
|
||||
; Sync Pathfinder with EVE server time.
|
||||
; Tip: You should not change this.
|
||||
; Hint: You should not change this.
|
||||
; Default: UTC
|
||||
TZ = UTC
|
||||
|
||||
@@ -35,26 +35,45 @@ LANGUAGE = en-US
|
||||
; Cache key prefix
|
||||
; Same for all cache values for this installation.
|
||||
; CLI (cronjob) scripts use it for cache manipulation.
|
||||
; Tip: You should not change this.
|
||||
; Hint: You should not change this.
|
||||
; Syntax String
|
||||
; Default: {{ md5(@SERVER.SERVER_NAME) }}
|
||||
SEED = {{ md5(@SERVER.SERVER_NAME) }}
|
||||
|
||||
; Cache backend
|
||||
; This sets the primary cache backend for Pathfinder. Used for e.g.:
|
||||
; DB query, DB schema, HTTP response, or even simple key->value caches
|
||||
; Can handle Redis, Memcache module, APC, WinCache, XCache and a filesystem-based cache.
|
||||
; Tip: Redis is recommended and gives the best performance.
|
||||
; Hint: Redis is recommended and gives the best performance.
|
||||
; Syntax: folder=[DIR] | redis=[SERVER]
|
||||
; Default: folder=tmp/cache/
|
||||
; Value: folder=[DIR]
|
||||
; Value: FALSE
|
||||
; - Disables caching
|
||||
; folder=[DIR]
|
||||
; - Cache data is stored on disc
|
||||
; redis=[SERVER]
|
||||
; - Cache data is stored in Redis (e.g. redis=localhost:6379)
|
||||
; - Cache data is stored in Redis. redis=[host]:[port]:[db] (e.g. redis=localhost:6379:1)
|
||||
CACHE = folder=tmp/cache/
|
||||
|
||||
; Cache backend for API data
|
||||
; This sets the cache backend for API response data and other temp data relates to API requests.
|
||||
; Response data with proper 'Expire' HTTP Header will be cached here and speed up further requests.
|
||||
; As default 'API_CACHE' and 'CACHE' share the same backend (cache location)
|
||||
; Hint1: You can specify e.g. a dedicated Redis DB here, then 'CACHE' and 'API_CACHE' can be cleared independently
|
||||
; Hint2: Redis is recommended and gives the best performance.
|
||||
; Default: {{@CACHE}}
|
||||
; Value: FALSE
|
||||
; - Disables caching
|
||||
; folder=[DIR]
|
||||
; - Cache data is stored on disc
|
||||
; redis=[SERVER]
|
||||
; - Cache data is stored in Redis. redis=[host]:[port]:[db] (e.g. redis=localhost:6379:2)
|
||||
API_CACHE = {{@CACHE}}
|
||||
|
||||
; Cache backend used by PHPs Session handler.
|
||||
; Tip1: Best performance and recommended configuration for Pathfinder is to configured Redis as PHPs default Session handler
|
||||
; Hint1: Best performance and recommended configuration for Pathfinder is to configured Redis as PHPs default Session handler
|
||||
; in your php.ini and set 'default' value here in order to use Redis (fastest)
|
||||
; Tip2: If Redis is not available for you, leave this at 'mysql' (faster than PHPs default files bases Sessions)
|
||||
; Hint2: If Redis is not available for you, leave this at 'mysql' (faster than PHPs default files bases Sessions)
|
||||
; Syntax: mysql | default
|
||||
; Default: mysql
|
||||
; Value: mysql
|
||||
|
||||
@@ -56,6 +56,9 @@ deleteExpiredCacheData = Cron\Cache->deleteExpiredData, @downtime
|
||||
; delete old statistics (activity log) data
|
||||
deleteStatisticsData = Cron\StatisticsUpdate->deleteStatisticsData, @weekly
|
||||
|
||||
; truncate map history log files
|
||||
truncateMapHistoryLogFiles = Cron\MapHistory->truncateFiles, @halfHour
|
||||
|
||||
; updates small amount of static system data from CCP API
|
||||
;updateUniverseSystems = Cron\Universe->updateUniverseSystems, @instant
|
||||
|
||||
|
||||
@@ -33,7 +33,7 @@ CCP_SSO_SECRET_KEY =
|
||||
CCP_SSO_DOWNTIME = 11:00
|
||||
|
||||
; CCP ESI API
|
||||
CCP_ESI_URL = https://esi.tech.ccp.is
|
||||
CCP_ESI_URL = https://esi.evetech.net
|
||||
CCP_ESI_DATASOURCE = singularity
|
||||
CCP_ESI_SCOPES = esi-location.read_online.v1,esi-location.read_location.v1,esi-location.read_ship_type.v1,esi-ui.write_waypoint.v1,esi-ui.open_window.v1,esi-universe.read_structures.v1,esi-corporations.read_corporation_membership.v1
|
||||
CCP_ESI_SCOPES_ADMIN =
|
||||
@@ -86,7 +86,7 @@ CCP_SSO_SECRET_KEY =
|
||||
CCP_SSO_DOWNTIME = 11:00
|
||||
|
||||
; CCP ESI API
|
||||
CCP_ESI_URL = https://esi.tech.ccp.is
|
||||
CCP_ESI_URL = https://esi.evetech.net
|
||||
CCP_ESI_DATASOURCE = tranquility
|
||||
CCP_ESI_SCOPES = esi-location.read_online.v1,esi-location.read_location.v1,esi-location.read_ship_type.v1,esi-ui.write_waypoint.v1,esi-ui.open_window.v1,esi-universe.read_structures.v1,esi-corporations.read_corporation_membership.v1
|
||||
CCP_ESI_SCOPES_ADMIN =
|
||||
@@ -103,4 +103,4 @@ SMTP_ERROR = admin@pathfinder-w.space
|
||||
|
||||
; TCP Socket configuration (optional) (advanced)
|
||||
;SOCKET_HOST = 127.0.0.1
|
||||
;SOCKET_PORT = 5555
|
||||
;SOCKET_PORT = 5555
|
||||
|
||||
@@ -1,5 +1,48 @@
|
||||
CHANGELOG
|
||||
|
||||
3.6.5 (24 December 2018)
|
||||
* NEW: Log, added timestamp to each line
|
||||
* NEW: Auth, added support for custom compare method, [#116](https://github.com/bcosca/fatfree-core/issues/116)
|
||||
* NEW: cache tag support for mongo & jig mapper, ref [#166](https://github.com/bcosca/fatfree-core/issues/116)
|
||||
* NEW: Allow PHP functions as template token filters
|
||||
* Web: Fix double redirect bug when running cURL with open_basedir disabled
|
||||
* Web: Cope with responses from HTTP/2 servers
|
||||
* Web->filler: remove very first space, when $std is false
|
||||
* Web\OAuth2: Cope with HTTP/2 responses
|
||||
* Web\OAuth2: take Content-Type header into account for json decoding, [#250](https://github.com/bcosca/fatfree-core/issues/250) [#251](https://github.com/bcosca/fatfree-core/issues/251)
|
||||
* Web\OAuth2: fixed empty results on some endpoints [#250](https://github.com/bcosca/fatfree-core/issues/250)
|
||||
* DB\SQL\Mapper: optimize mapper->count memory usage
|
||||
* DB\SQL\Mapper: New table alias operator
|
||||
* DB\SQL\Mapper: fix count() performance on non-grouped result sets, [bcosca/fatfree#1114](https://github.com/bcosca/fatfree/issues/1114)
|
||||
* DB\SQL: Support for CTE in postgreSQL, [bcosca/fatfree#1107](https://github.com/bcosca/fatfree/issues/1107), [bcosca/fatfree#1116](https://github.com/bcosca/fatfree/issues/1116), [bcosca/fatfree#1021](https://github.com/bcosca/fatfree/issues/1021)
|
||||
* DB\SQL->log: Remove extraneous whitespace
|
||||
* DB\SQL: Added ability to add inline comments per SQL query
|
||||
* CLI\WS, Refactoring: Streamline socket server
|
||||
* CLI\WS: Add option for dropping query in OAuth2 URI
|
||||
* CLI\WS: Add URL-safe base64 encoding
|
||||
* CLI\WS: Detect errors in returned JSON values
|
||||
* CLI\WS: Added support for Sec-WebSocket-Protocol header
|
||||
* Matrix->calendar: Allow unix timestamp as date argument
|
||||
* Basket: Access basket item by _id [#260](https://github.com/bcosca/fatfree-core/issues/260)
|
||||
* SMTP: Added TLS 1.2 support [bcosca/fatfree#1115](https://github.com/bcosca/fatfree/issues/1115)
|
||||
* SMTP->send: Respect $log argument
|
||||
* Base->cast: recognize binary and octal numbers in config
|
||||
* Base->cast: add awareness of hexadecimal literals
|
||||
* Base->abort: Remove unnecessary Content-Encoding header
|
||||
* Base->abort: Ensure headers have not been flushed
|
||||
* Base->format: Differentiate between long- and full-date (with localized weekday) formats
|
||||
* Base->format: Conform with intl extension's number output
|
||||
* Enable route handler to override Access-Control headers in response to OPTIONS request, [#257](https://github.com/bcosca/fatfree-core/issues/257)
|
||||
* Augment filters with a var_export function
|
||||
* Bug fix php7.3: Fix template parse regex to be compatible with strict PCRE2 rules for hyphen placement in a character class
|
||||
* Bug fix, Cache->set: update creation time when updating existing cache entries
|
||||
* Bug fix: incorrect ICU date/time formatting
|
||||
* Bug fix, Jig: lazy write on empty data
|
||||
* Bug fix: Method uppercase to avoid route failure [#252](https://github.com/bcosca/fatfree-core/issues/252)
|
||||
* Fixed error description when (PSR-11) `CONTAINER` fails to resolve a class [#253](https://github.com/bcosca/fatfree-core/issues/253)
|
||||
* Mitigate CSRF predictability/vulnerability
|
||||
* Expose Mapper->factory() method
|
||||
|
||||
3.6.4 (19 April 2018)
|
||||
* NEW: Added Dependency Injection support with CONTAINER variable [#221](https://github.com/bcosca/fatfree-core/issues/221)
|
||||
* NEW: configurable LOGGABLE error codes [#1091](https://github.com/bcosca/fatfree/issues/1091#issuecomment-364674701)
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
/*
|
||||
|
||||
Copyright (c) 2009-2017 F3::Factory/Bong Cosca, All rights reserved.
|
||||
Copyright (c) 2009-2018 F3::Factory/Bong Cosca, All rights reserved.
|
||||
|
||||
This file is part of the Fat-Free Framework (http://fatfreeframework.com).
|
||||
|
||||
@@ -35,7 +35,9 @@ class Auth {
|
||||
//! Mapper object
|
||||
$mapper,
|
||||
//! Storage options
|
||||
$args;
|
||||
$args,
|
||||
//! Custom compare function
|
||||
$func;
|
||||
|
||||
/**
|
||||
* Jig storage handler
|
||||
@@ -45,22 +47,26 @@ class Auth {
|
||||
* @param $realm string
|
||||
**/
|
||||
protected function _jig($id,$pw,$realm) {
|
||||
return (bool)
|
||||
$success = (bool)
|
||||
call_user_func_array(
|
||||
[$this->mapper,'load'],
|
||||
[
|
||||
array_merge(
|
||||
[
|
||||
'@'.$this->args['id'].'==? AND '.
|
||||
'@'.$this->args['pw'].'==?'.
|
||||
'@'.$this->args['id'].'==?'.
|
||||
($this->func?'':' AND @'.$this->args['pw'].'==?').
|
||||
(isset($this->args['realm'])?
|
||||
(' AND @'.$this->args['realm'].'==?'):''),
|
||||
$id,$pw
|
||||
$id
|
||||
],
|
||||
($this->func?[]:[$pw]),
|
||||
(isset($this->args['realm'])?[$realm]:[])
|
||||
)
|
||||
]
|
||||
);
|
||||
if ($success && $this->func)
|
||||
$success = call_user_func($this->func,$pw,$this->mapper->get($this->args['pw']));
|
||||
return $success;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -71,15 +77,16 @@ class Auth {
|
||||
* @param $realm string
|
||||
**/
|
||||
protected function _mongo($id,$pw,$realm) {
|
||||
return (bool)
|
||||
$success = (bool)
|
||||
$this->mapper->load(
|
||||
[
|
||||
$this->args['id']=>$id,
|
||||
$this->args['pw']=>$pw
|
||||
]+
|
||||
[$this->args['id']=>$id]+
|
||||
($this->func?[]:[$this->args['pw']=>$pw])+
|
||||
(isset($this->args['realm'])?
|
||||
[$this->args['realm']=>$realm]:[])
|
||||
);
|
||||
if ($success && $this->func)
|
||||
$success = call_user_func($this->func,$pw,$this->mapper->get($this->args['pw']));
|
||||
return $success;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -90,22 +97,26 @@ class Auth {
|
||||
* @param $realm string
|
||||
**/
|
||||
protected function _sql($id,$pw,$realm) {
|
||||
return (bool)
|
||||
$success = (bool)
|
||||
call_user_func_array(
|
||||
[$this->mapper,'load'],
|
||||
[
|
||||
array_merge(
|
||||
[
|
||||
$this->args['id'].'=? AND '.
|
||||
$this->args['pw'].'=?'.
|
||||
$this->args['id'].'=?'.
|
||||
($this->func?'':' AND '.$this->args['pw'].'=?').
|
||||
(isset($this->args['realm'])?
|
||||
(' AND '.$this->args['realm'].'=?'):''),
|
||||
$id,$pw
|
||||
$id
|
||||
],
|
||||
($this->func?[]:[$pw]),
|
||||
(isset($this->args['realm'])?[$realm]:[])
|
||||
)
|
||||
]
|
||||
);
|
||||
if ($success && $this->func)
|
||||
$success = call_user_func($this->func,$pw,$this->mapper->get($this->args['pw']));
|
||||
return $success;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -234,8 +245,9 @@ class Auth {
|
||||
* @return object
|
||||
* @param $storage string|object
|
||||
* @param $args array
|
||||
* @param $func callback
|
||||
**/
|
||||
function __construct($storage,array $args=NULL) {
|
||||
function __construct($storage,array $args=NULL,$func=NULL) {
|
||||
if (is_object($storage) && is_a($storage,'DB\Cursor')) {
|
||||
$this->storage=$storage->dbtype();
|
||||
$this->mapper=$storage;
|
||||
@@ -244,6 +256,7 @@ class Auth {
|
||||
else
|
||||
$this->storage=$storage;
|
||||
$this->args=$args;
|
||||
$this->func=$func;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
165
app/lib/base.php
165
app/lib/base.php
@@ -45,7 +45,7 @@ final class Base extends Prefab implements ArrayAccess {
|
||||
//@{ Framework details
|
||||
const
|
||||
PACKAGE='Fat-Free Framework',
|
||||
VERSION='3.6.4-Release';
|
||||
VERSION='3.6.5-Release';
|
||||
//@}
|
||||
|
||||
//@{ HTTP status codes (RFC 2616)
|
||||
@@ -207,11 +207,13 @@ final class Base extends Prefab implements ArrayAccess {
|
||||
}
|
||||
|
||||
/**
|
||||
* cast string variable to php type or constant
|
||||
* Cast string variable to PHP type or constant
|
||||
* @param $val
|
||||
* @return mixed
|
||||
*/
|
||||
function cast($val) {
|
||||
if (preg_match('/^(?:0x[0-9a-f]+|0[0-7]+|0b[01]+)$/i',$val))
|
||||
return intval($val,0);
|
||||
if (is_numeric($val))
|
||||
return $val+0;
|
||||
$val=trim($val);
|
||||
@@ -241,13 +243,13 @@ final class Base extends Prefab implements ArrayAccess {
|
||||
$out='['.
|
||||
(isset($sub[3])?
|
||||
$this->compile($sub[3]):
|
||||
var_export($sub[1],TRUE)).
|
||||
$this->export($sub[1])).
|
||||
']';
|
||||
}
|
||||
else
|
||||
$out=function_exists($sub[1])?
|
||||
$sub[0]:
|
||||
('['.var_export($sub[1],TRUE).']'.$sub[2]);
|
||||
('['.$this->export($sub[1]).']'.$sub[2]);
|
||||
return $out;
|
||||
},
|
||||
$expr[2]
|
||||
@@ -680,7 +682,7 @@ final class Base extends Prefab implements ArrayAccess {
|
||||
$str='';
|
||||
foreach (get_object_vars($arg) as $key=>$val)
|
||||
$str.=($str?',':'').
|
||||
var_export($key,TRUE).'=>'.
|
||||
$this->export($key).'=>'.
|
||||
$this->stringify($val,
|
||||
array_merge($stack,[$arg]));
|
||||
return get_class($arg).'::__set_state(['.$str.'])';
|
||||
@@ -690,11 +692,11 @@ final class Base extends Prefab implements ArrayAccess {
|
||||
ctype_digit(implode('',array_keys($arg)));
|
||||
foreach ($arg as $key=>$val)
|
||||
$str.=($str?',':'').
|
||||
($num?'':(var_export($key,TRUE).'=>')).
|
||||
($num?'':($this->export($key).'=>')).
|
||||
$this->stringify($val,array_merge($stack,[$arg]));
|
||||
return '['.$str.']';
|
||||
default:
|
||||
return var_export($arg,TRUE);
|
||||
return $this->export($arg);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -890,8 +892,14 @@ final class Base extends Prefab implements ArrayAccess {
|
||||
return $expr[0];
|
||||
if (isset($type)) {
|
||||
if (isset($this->hive['FORMATS'][$type]))
|
||||
return $this->call($this->hive['FORMATS'][$type],
|
||||
[$args[$pos],isset($mod)?$mod:null,isset($prop)?$prop:null]);
|
||||
return $this->call(
|
||||
$this->hive['FORMATS'][$type],
|
||||
[
|
||||
$args[$pos],
|
||||
isset($mod)?$mod:null,
|
||||
isset($prop)?$prop:null
|
||||
]
|
||||
);
|
||||
switch ($type) {
|
||||
case 'plural':
|
||||
preg_match_all('/(?<tag>\w+)'.
|
||||
@@ -964,16 +972,22 @@ final class Base extends Prefab implements ArrayAccess {
|
||||
$args[$pos]*100,0,$decimal_point,
|
||||
$thousands_sep).'%';
|
||||
}
|
||||
$frac=$args[$pos]-(int)$args[$pos];
|
||||
return number_format(
|
||||
$args[$pos],isset($prop)?$prop:2,
|
||||
$args[$pos],
|
||||
isset($prop)?
|
||||
$prop:
|
||||
$frac?strlen($frac)-2:0,
|
||||
$decimal_point,$thousands_sep);
|
||||
case 'date':
|
||||
$prop='%d %B %Y';
|
||||
if (empty($mod) || $mod=='short')
|
||||
$prop='%x';
|
||||
elseif ($mod=='long')
|
||||
$prop='%A, %d %B %Y';
|
||||
elseif ($mod=='full')
|
||||
$prop='%A, '.$prop;
|
||||
return strftime($prop,$args[$pos]);
|
||||
case 'time':
|
||||
$prop='%r';
|
||||
if (empty($mod) || $mod=='short')
|
||||
$prop='%X';
|
||||
return strftime($prop,$args[$pos]);
|
||||
@@ -987,6 +1001,15 @@ final class Base extends Prefab implements ArrayAccess {
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return string representation of expression
|
||||
* @return string
|
||||
* @param $expr mixed
|
||||
**/
|
||||
function export($expr) {
|
||||
return var_export($expr,TRUE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Assign/auto-detect language
|
||||
* @return string
|
||||
@@ -1244,7 +1267,8 @@ final class Base extends Prefab implements ArrayAccess {
|
||||
if (!is_array($loggable))
|
||||
$loggable=$this->split($loggable);
|
||||
foreach ($loggable as $status)
|
||||
if ($status=='*' || preg_match('/^'.preg_replace('/\D/','\d',$status).'$/',$code)) {
|
||||
if ($status=='*' ||
|
||||
preg_match('/^'.preg_replace('/\D/','\d',$status).'$/',$code)) {
|
||||
error_log($text);
|
||||
foreach (explode("\n",$trace) as $nexus)
|
||||
if ($nexus)
|
||||
@@ -1270,7 +1294,14 @@ final class Base extends Prefab implements ArrayAccess {
|
||||
'beforeroute,afterroute')===FALSE) &&
|
||||
!$prior && !$this->hive['CLI'] && !$this->hive['QUIET'])
|
||||
echo $this->hive['AJAX']?
|
||||
json_encode(array_diff_key($this->hive['ERROR'],$this->hive['DEBUG']?[]:['trace'=>1])):
|
||||
json_encode(
|
||||
array_diff_key(
|
||||
$this->hive['ERROR'],
|
||||
$this->hive['DEBUG']?
|
||||
[]:
|
||||
['trace'=>1]
|
||||
)
|
||||
):
|
||||
('<!DOCTYPE html>'.$eol.
|
||||
'<html>'.$eol.
|
||||
'<head>'.
|
||||
@@ -1557,7 +1588,7 @@ final class Base extends Prefab implements ArrayAccess {
|
||||
$cors=$this->hive['CORS'];
|
||||
header('Access-Control-Allow-Origin: '.$cors['origin']);
|
||||
header('Access-Control-Allow-Credentials: '.
|
||||
var_export($cors['credentials'],TRUE));
|
||||
$this->export($cors['credentials']));
|
||||
$preflight=
|
||||
isset($this->hive['HEADERS']['Access-Control-Request-Method']);
|
||||
}
|
||||
@@ -1674,12 +1705,15 @@ final class Base extends Prefab implements ArrayAccess {
|
||||
// URL doesn't match any route
|
||||
$this->error(404);
|
||||
elseif (!$this->hive['CLI']) {
|
||||
// Unhandled HTTP method
|
||||
header('Allow: '.implode(',',array_unique($allowed)));
|
||||
if (!preg_grep('/Allow:/',$headers_send=headers_list()))
|
||||
// Unhandled HTTP method
|
||||
header('Allow: '.implode(',',array_unique($allowed)));
|
||||
if ($cors) {
|
||||
header('Access-Control-Allow-Methods: OPTIONS,'.
|
||||
implode(',',$allowed));
|
||||
if ($cors['headers'])
|
||||
if (!preg_grep('/Access-Control-Allow-Methods:/',$headers_send))
|
||||
header('Access-Control-Allow-Methods: OPTIONS,'.
|
||||
implode(',',$allowed));
|
||||
if ($cors['headers'] &&
|
||||
!preg_grep('/Access-Control-Allow-Headers:/',$headers_send))
|
||||
header('Access-Control-Allow-Headers: '.
|
||||
(is_array($cors['headers'])?
|
||||
implode(',',$cors['headers']):
|
||||
@@ -1733,7 +1767,9 @@ final class Base extends Prefab implements ArrayAccess {
|
||||
}
|
||||
|
||||
/**
|
||||
* Disconnect HTTP client
|
||||
* Disconnect HTTP client;
|
||||
* Set FcgidOutputBufferSize to zero if server uses mod_fcgid;
|
||||
* Disable mod_deflate when rendering text/html output
|
||||
**/
|
||||
function abort() {
|
||||
if (!headers_sent() && session_status()!=PHP_SESSION_ACTIVE)
|
||||
@@ -1741,9 +1777,10 @@ final class Base extends Prefab implements ArrayAccess {
|
||||
$out='';
|
||||
while (ob_get_level())
|
||||
$out=ob_get_clean().$out;
|
||||
header('Content-Encoding: none');
|
||||
header('Content-Length: '.strlen($out));
|
||||
header('Connection: close');
|
||||
if (!headers_sent()) {
|
||||
header('Content-Length: '.strlen($out));
|
||||
header('Connection: close');
|
||||
}
|
||||
session_commit();
|
||||
echo $out;
|
||||
flush();
|
||||
@@ -1771,11 +1808,13 @@ final class Base extends Prefab implements ArrayAccess {
|
||||
$parts[1]=call_user_func([$container,'get'],$parts[1]);
|
||||
elseif (is_callable($container))
|
||||
$parts[1]=call_user_func($container,$parts[1],$args);
|
||||
elseif (is_string($container) && is_subclass_of($container,'Prefab'))
|
||||
$parts[1]=call_user_func($container.'::instance')->get($parts[1]);
|
||||
elseif (is_string($container) &&
|
||||
is_subclass_of($container,'Prefab'))
|
||||
$parts[1]=call_user_func($container.'::instance')->
|
||||
get($parts[1]);
|
||||
else
|
||||
user_error(sprintf(self::E_Class,
|
||||
$this->stringify($container)),
|
||||
$this->stringify($parts[1])),
|
||||
E_USER_ERROR);
|
||||
}
|
||||
else {
|
||||
@@ -1916,7 +1955,8 @@ final class Base extends Prefab implements ArrayAccess {
|
||||
call_user_func_array(
|
||||
[$this,$cmd[1]],
|
||||
array_merge([$match['lval']],
|
||||
str_getcsv($cmd[1]=='config'?$this->cast($match['rval']):
|
||||
str_getcsv($cmd[1]=='config'?
|
||||
$this->cast($match['rval']):
|
||||
$match['rval']))
|
||||
);
|
||||
}
|
||||
@@ -1931,9 +1971,11 @@ final class Base extends Prefab implements ArrayAccess {
|
||||
$args=array_map(
|
||||
function($val) {
|
||||
$val=$this->cast($val);
|
||||
return is_string($val)
|
||||
? preg_replace('/\\\\"/','"',$val)
|
||||
: $val;
|
||||
if (is_string($val))
|
||||
$val=strlen($val)?
|
||||
preg_replace('/\\\\"/','"',$val):
|
||||
NULL;
|
||||
return $val;
|
||||
},
|
||||
// Mark quoted strings with 0x00 whitespace
|
||||
str_getcsv(preg_replace(
|
||||
@@ -2221,6 +2263,7 @@ final class Base extends Prefab implements ArrayAccess {
|
||||
);
|
||||
if (!isset($_SERVER['SERVER_NAME']) || $_SERVER['SERVER_NAME']==='')
|
||||
$_SERVER['SERVER_NAME']=gethostname();
|
||||
$headers=[];
|
||||
if ($cli=PHP_SAPI=='cli') {
|
||||
// Emulate HTTP request
|
||||
$_SERVER['REQUEST_METHOD']='GET';
|
||||
@@ -2251,33 +2294,30 @@ final class Base extends Prefab implements ArrayAccess {
|
||||
$_SERVER['REQUEST_URI']=$req;
|
||||
parse_str($query,$GLOBALS['_GET']);
|
||||
}
|
||||
$headers=[];
|
||||
if (!$cli) {
|
||||
if (function_exists('getallheaders')) {
|
||||
foreach (getallheaders() as $key=>$val) {
|
||||
$tmp=strtoupper(strtr($key,'-','_'));
|
||||
// TODO: use ucwords delimiters for php 5.4.32+ & 5.5.16+
|
||||
$key=strtr(ucwords(strtolower(strtr($key,'-',' '))),' ','-');
|
||||
$headers[$key]=$val;
|
||||
if (isset($_SERVER['HTTP_'.$tmp]))
|
||||
$headers[$key]=&$_SERVER['HTTP_'.$tmp];
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (isset($_SERVER['CONTENT_LENGTH']))
|
||||
$headers['Content-Length']=&$_SERVER['CONTENT_LENGTH'];
|
||||
if (isset($_SERVER['CONTENT_TYPE']))
|
||||
$headers['Content-Type']=&$_SERVER['CONTENT_TYPE'];
|
||||
foreach (array_keys($_SERVER) as $key)
|
||||
if (substr($key,0,5)=='HTTP_')
|
||||
$headers[strtr(ucwords(strtolower(strtr(
|
||||
substr($key,5),'_',' '))),' ','-')]=&$_SERVER[$key];
|
||||
elseif (function_exists('getallheaders')) {
|
||||
foreach (getallheaders() as $key=>$val) {
|
||||
$tmp=strtoupper(strtr($key,'-','_'));
|
||||
// TODO: use ucwords delimiters for php 5.4.32+ & 5.5.16+
|
||||
$key=strtr(ucwords(strtolower(strtr($key,'-',' '))),' ','-');
|
||||
$headers[$key]=$val;
|
||||
if (isset($_SERVER['HTTP_'.$tmp]))
|
||||
$headers[$key]=&$_SERVER['HTTP_'.$tmp];
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (isset($_SERVER['CONTENT_LENGTH']))
|
||||
$headers['Content-Length']=&$_SERVER['CONTENT_LENGTH'];
|
||||
if (isset($_SERVER['CONTENT_TYPE']))
|
||||
$headers['Content-Type']=&$_SERVER['CONTENT_TYPE'];
|
||||
foreach (array_keys($_SERVER) as $key)
|
||||
if (substr($key,0,5)=='HTTP_')
|
||||
$headers[strtr(ucwords(strtolower(strtr(
|
||||
substr($key,5),'_',' '))),' ','-')]=&$_SERVER[$key];
|
||||
}
|
||||
if (isset($headers['X-HTTP-Method-Override']))
|
||||
$_SERVER['REQUEST_METHOD']=$headers['X-HTTP-Method-Override'];
|
||||
elseif ($_SERVER['REQUEST_METHOD']=='POST' && isset($_POST['_method']))
|
||||
$_SERVER['REQUEST_METHOD']=$_POST['_method'];
|
||||
$_SERVER['REQUEST_METHOD']=strtoupper($_POST['_method']);
|
||||
$scheme=isset($_SERVER['HTTPS']) && $_SERVER['HTTPS']=='on' ||
|
||||
isset($headers['X-Forwarded-Proto']) &&
|
||||
$headers['X-Forwarded-Proto']=='https'?'https':'http';
|
||||
@@ -2493,10 +2533,9 @@ class Cache extends Prefab {
|
||||
if (!$this->dsn)
|
||||
return TRUE;
|
||||
$ndx=$this->prefix.'.'.$key;
|
||||
$time=microtime(TRUE);
|
||||
if ($cached=$this->exists($key))
|
||||
list($time,$ttl)=$cached;
|
||||
$data=$fw->serialize([$val,$time,$ttl]);
|
||||
$ttl=$cached[1];
|
||||
$data=$fw->serialize([$val,microtime(TRUE),$ttl]);
|
||||
$parts=explode('=',$this->dsn,2);
|
||||
switch ($parts[0]) {
|
||||
case 'apc':
|
||||
@@ -2513,7 +2552,8 @@ class Cache extends Prefab {
|
||||
case 'xcache':
|
||||
return xcache_set($ndx,$data,$ttl);
|
||||
case 'folder':
|
||||
return $fw->write($parts[1].str_replace(['/','\\'],'',$ndx),$data);
|
||||
return $fw->write($parts[1].
|
||||
str_replace(['/','\\'],'',$ndx),$data);
|
||||
}
|
||||
return FALSE;
|
||||
}
|
||||
@@ -2824,6 +2864,7 @@ class Preview extends View {
|
||||
'c'=>'$this->c',
|
||||
'esc'=>'$this->esc',
|
||||
'raw'=>'$this->raw',
|
||||
'export'=>'Base::instance()->export',
|
||||
'alias'=>'Base::instance()->alias',
|
||||
'format'=>'Base::instance()->format'
|
||||
];
|
||||
@@ -2833,7 +2874,7 @@ class Preview extends View {
|
||||
$interpolation=true;
|
||||
|
||||
/**
|
||||
* enable/disable markup parsing interpolation
|
||||
* Enable/disable markup parsing interpolation
|
||||
* mainly used for adding appropriate newlines
|
||||
* @param $bool bool
|
||||
*/
|
||||
@@ -2867,16 +2908,18 @@ class Preview extends View {
|
||||
$str,$parts)) {
|
||||
$str=trim($parts[1]);
|
||||
foreach ($fw->split(trim($parts[2],"\xC2\xA0")) as $func)
|
||||
$str=is_string($cmd=$this->filter($func))?
|
||||
$str=((empty($this->filter[$cmd=$func]) &&
|
||||
function_exists($cmd)) ||
|
||||
is_string($cmd=$this->filter($func)))?
|
||||
$cmd.'('.$str.')':
|
||||
'Base::instance()->'.
|
||||
'call($this->filter(\''.$func.'\'),['.$str.'])';
|
||||
'call($this->filter(\''.$func.'\'),['.$str.'])';
|
||||
}
|
||||
return $str;
|
||||
}
|
||||
|
||||
/**
|
||||
* Register or get (a specific one or all) token filters
|
||||
* Register or get (one specific or all) token filters
|
||||
* @param string $key
|
||||
* @param string|closure $func
|
||||
* @return array|closure|string
|
||||
|
||||
@@ -90,7 +90,8 @@ class Basket extends Magic {
|
||||
if (isset($_SESSION[$this->key])) {
|
||||
foreach ($_SESSION[$this->key] as $id=>$item)
|
||||
if (!isset($key) ||
|
||||
array_key_exists($key,$item) && $item[$key]==$val) {
|
||||
array_key_exists($key,$item) && $item[$key]==$val ||
|
||||
$key=='_id' && $id==$val) {
|
||||
$obj=clone($this);
|
||||
$obj->id=$id;
|
||||
$obj->item=$item;
|
||||
|
||||
@@ -52,6 +52,7 @@ class WS {
|
||||
$ctx,
|
||||
$wait,
|
||||
$sockets,
|
||||
$protocol,
|
||||
$agents=[],
|
||||
$events=[];
|
||||
|
||||
@@ -61,16 +62,14 @@ class WS {
|
||||
* @param $socket resource
|
||||
**/
|
||||
function alloc($socket) {
|
||||
if (is_bool($str=$this->read($socket))) {
|
||||
$this->close($socket);
|
||||
if (is_bool($buf=$this->read($socket)))
|
||||
return;
|
||||
}
|
||||
// Get WebSocket headers
|
||||
$hdrs=[];
|
||||
$CRLF="\r\n";
|
||||
$EOL="\r\n";
|
||||
$verb=NULL;
|
||||
$uri=NULL;
|
||||
foreach (explode($CRLF,trim($str)) as $line)
|
||||
foreach (explode($EOL,trim($buf)) as $line)
|
||||
if (preg_match('/^(\w+)\s(.+)\sHTTP\/1\.\d$/',
|
||||
trim($line),$match)) {
|
||||
$verb=$match[1];
|
||||
@@ -98,35 +97,29 @@ class WS {
|
||||
if ($verb && $uri)
|
||||
$this->write(
|
||||
$socket,
|
||||
$str='HTTP/1.1 400 Bad Request'.$CRLF.
|
||||
'Connection: close'.$CRLF.$CRLF
|
||||
'HTTP/1.1 400 Bad Request'.$EOL.
|
||||
'Connection: close'.$EOL.$EOL
|
||||
);
|
||||
$this->close($socket);
|
||||
return;
|
||||
}
|
||||
// Handshake
|
||||
$bytes=$this->write(
|
||||
$socket,
|
||||
$str='HTTP/1.1 101 Switching Protocols'.$CRLF.
|
||||
'Upgrade: websocket'.$CRLF.
|
||||
'Connection: Upgrade'.$CRLF.
|
||||
'Sec-WebSocket-Accept: '.
|
||||
base64_encode(
|
||||
sha1(
|
||||
$hdrs['Sec-Websocket-Key'].
|
||||
self::Magic,
|
||||
TRUE
|
||||
)
|
||||
).$CRLF.$CRLF
|
||||
);
|
||||
if ($bytes) {
|
||||
$buf='HTTP/1.1 101 Switching Protocols'.$EOL.
|
||||
'Upgrade: websocket'.$EOL.
|
||||
'Connection: Upgrade'.$EOL;
|
||||
if (isset($hdrs['Sec-Websocket-Protocol']))
|
||||
$buf.='Sec-WebSocket-Protocol: '.
|
||||
$hdrs['Sec-Websocket-Protocol'].$EOL;
|
||||
$buf.='Sec-WebSocket-Accept: '.
|
||||
base64_encode(
|
||||
sha1($hdrs['Sec-Websocket-Key'].WS::Magic,TRUE)
|
||||
).$EOL.$EOL;
|
||||
if ($this->write($socket,$buf)) {
|
||||
// Connect agent to server
|
||||
$this->sockets[]=$socket;
|
||||
$this->sockets[(int)$socket]=$socket;
|
||||
$this->agents[(int)$socket]=
|
||||
new Agent($this,$socket,$verb,$uri,$hdrs);
|
||||
}
|
||||
else
|
||||
$this->close($socket);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -135,34 +128,26 @@ class WS {
|
||||
* @param $socket resource
|
||||
**/
|
||||
function close($socket) {
|
||||
if (isset($this->agents[(int)$socket]))
|
||||
unset($this->sockets[(int)$socket],$this->agents[(int)$socket]);
|
||||
stream_socket_shutdown($socket,STREAM_SHUT_WR);
|
||||
@fclose($socket);
|
||||
}
|
||||
|
||||
/**
|
||||
* Free stream socket
|
||||
* @return bool
|
||||
* @param $socket resource
|
||||
**/
|
||||
function free($socket) {
|
||||
unset($this->sockets[array_search($socket,$this->sockets)]);
|
||||
unset($this->agents[(int)$socket]);
|
||||
$this->close($socket);
|
||||
}
|
||||
|
||||
/**
|
||||
* Read from stream socket
|
||||
* @return string|FALSE
|
||||
* @param $socket resource
|
||||
**/
|
||||
function read($socket) {
|
||||
if (is_string($str=@fread($socket,self::Packet)) &&
|
||||
strlen($str) &&
|
||||
strlen($str)<self::Packet)
|
||||
return $str;
|
||||
if (is_string($buf=@fread($socket,WS::Packet)) &&
|
||||
strlen($buf) &&
|
||||
strlen($buf)<WS::Packet)
|
||||
return $buf;
|
||||
if (isset($this->events['error']) &&
|
||||
is_callable($func=$this->events['error']))
|
||||
$func($this);
|
||||
$this->close($socket);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
@@ -170,16 +155,17 @@ class WS {
|
||||
* Write to stream socket
|
||||
* @return int|FALSE
|
||||
* @param $socket resource
|
||||
* @param $str string
|
||||
* @param $buf string
|
||||
**/
|
||||
function write($socket,$str) {
|
||||
for ($i=0,$bytes=0;$i<strlen($str);$i+=$bytes) {
|
||||
if (($bytes=@fwrite($socket,substr($str,$i))) &&
|
||||
function write($socket,$buf) {
|
||||
for ($i=0,$bytes=0;$i<strlen($buf);$i+=$bytes) {
|
||||
if (($bytes=@fwrite($socket,substr($buf,$i))) &&
|
||||
@fflush($socket))
|
||||
continue;
|
||||
if (isset($this->events['error']) &&
|
||||
is_callable($func=$this->events['error']))
|
||||
$func($this);
|
||||
$this->close($socket);
|
||||
return FALSE;
|
||||
}
|
||||
return $bytes;
|
||||
@@ -248,7 +234,7 @@ class WS {
|
||||
register_shutdown_function(function() use($listen) {
|
||||
foreach ($this->sockets as $socket)
|
||||
if ($socket!=$listen)
|
||||
$this->free($socket);
|
||||
$this->close($socket);
|
||||
$this->close($listen);
|
||||
if (isset($this->events['stop']) &&
|
||||
is_callable($func=$this->events['stop']))
|
||||
@@ -259,7 +245,7 @@ class WS {
|
||||
if (isset($this->events['start']) &&
|
||||
is_callable($func=$this->events['start']))
|
||||
$func($this);
|
||||
$this->sockets=[$listen];
|
||||
$this->sockets=[(int)$listen=>$listen];
|
||||
$empty=[];
|
||||
$wait=$this->wait;
|
||||
while (TRUE) {
|
||||
@@ -289,26 +275,8 @@ class WS {
|
||||
}
|
||||
else {
|
||||
$id=(int)$socket;
|
||||
if (isset($this->agents[$id]) &&
|
||||
$raw=$this->agents[$id]->fetch()) {
|
||||
list($op,$data)=$raw;
|
||||
// Dispatch
|
||||
switch ($op & self::OpCode) {
|
||||
case self::Ping:
|
||||
$this->agents[$id]->send(self::Pong);
|
||||
break;
|
||||
case self::Close:
|
||||
$this->free($socket);
|
||||
break;
|
||||
case self::Text:
|
||||
$data=trim($data);
|
||||
case self::Binary:
|
||||
if (isset($this->events['receive']) &&
|
||||
is_callable($func=$this->events['receive']))
|
||||
$func($this->agents[$id],$op,$data);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (isset($this->agents[$id]))
|
||||
$this->agents[$id]->fetch();
|
||||
}
|
||||
}
|
||||
$wait-=microtime(TRUE)-$mark;
|
||||
@@ -319,10 +287,9 @@ class WS {
|
||||
}
|
||||
if (!$count) {
|
||||
$mark=microtime(TRUE);
|
||||
foreach ($this->sockets as $socket) {
|
||||
foreach ($this->sockets as $id=>$socket) {
|
||||
if (!is_resource($socket))
|
||||
continue;
|
||||
$id=(int)$socket;
|
||||
if ($socket!=$listen &&
|
||||
isset($this->agents[$id]) &&
|
||||
isset($this->events['idle']) &&
|
||||
@@ -362,8 +329,7 @@ class Agent {
|
||||
$verb,
|
||||
$uri,
|
||||
$headers,
|
||||
$events,
|
||||
$buffer;
|
||||
$events;
|
||||
|
||||
/**
|
||||
* Return server instance
|
||||
@@ -381,6 +347,14 @@ class Agent {
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return socket
|
||||
* @return object
|
||||
**/
|
||||
function socket() {
|
||||
return $this->socket;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return request method
|
||||
* @return string
|
||||
@@ -413,22 +387,20 @@ class Agent {
|
||||
* @param $payload string
|
||||
**/
|
||||
function send($op,$data='') {
|
||||
$server=$this->server;
|
||||
$mask=WS::Finale | $op & WS::OpCode;
|
||||
$len=strlen($data);
|
||||
$str='';
|
||||
$buf='';
|
||||
if ($len>0xffff)
|
||||
$str=pack('CCNN',$mask,0x7f,$len);
|
||||
$buf=pack('CCNN',$mask,0x7f,$len);
|
||||
else
|
||||
if ($len>0x7d)
|
||||
$str=pack('CCn',$mask,0x7e,$len);
|
||||
$buf=pack('CCn',$mask,0x7e,$len);
|
||||
else
|
||||
$str=pack('CC',$mask,$len);
|
||||
$str.=$data;
|
||||
$server=$this->server();
|
||||
if (is_bool($server->write($this->socket,$str))) {
|
||||
$this->free();
|
||||
$buf=pack('CC',$mask,$len);
|
||||
$buf.=$data;
|
||||
if (is_bool($server->write($this->socket,$buf)))
|
||||
return FALSE;
|
||||
}
|
||||
if (!in_array($op,[WS::Pong,WS::Close]) &&
|
||||
isset($this->events['send']) &&
|
||||
is_callable($func=$this->events['send']))
|
||||
@@ -442,12 +414,9 @@ class Agent {
|
||||
**/
|
||||
function fetch() {
|
||||
// Unmask payload
|
||||
$server=$this->server();
|
||||
if (is_bool($buf=$server->read($this->socket))) {
|
||||
$this->free();
|
||||
$server=$this->server;
|
||||
if (is_bool($buf=$server->read($this->socket)))
|
||||
return FALSE;
|
||||
}
|
||||
$buf=($this->buffer.=$buf);
|
||||
$op=ord($buf[0]) & WS::OpCode;
|
||||
$len=ord($buf[1]) & WS::Length;
|
||||
$pos=2;
|
||||
@@ -468,18 +437,25 @@ class Agent {
|
||||
return FALSE;
|
||||
for ($i=0,$data='';$i<$len;$i++)
|
||||
$data.=chr(ord($buf[$pos+$i])^$mask[$i%4]);
|
||||
$this->buffer='';
|
||||
// Dispatch
|
||||
switch ($op & WS::OpCode) {
|
||||
case WS::Ping:
|
||||
$this->send(WS::Pong);
|
||||
break;
|
||||
case WS::Close:
|
||||
$server->close($this->socket);
|
||||
break;
|
||||
case WS::Text:
|
||||
$data=trim($data);
|
||||
case WS::Binary:
|
||||
if (isset($this->events['receive']) &&
|
||||
is_callable($func=$this->events['receive']))
|
||||
$func($this,$op,$data);
|
||||
break;
|
||||
}
|
||||
return [$op,$data];
|
||||
}
|
||||
|
||||
/**
|
||||
* Free stream socket
|
||||
* @return NULL
|
||||
**/
|
||||
function free() {
|
||||
$this->server->free($this->socket);
|
||||
}
|
||||
|
||||
/**
|
||||
* Destroy object
|
||||
* @return NULL
|
||||
@@ -507,7 +483,6 @@ class Agent {
|
||||
$this->uri=$uri;
|
||||
$this->headers=$hdrs;
|
||||
$this->events=$server->events();
|
||||
$this->buffer='';
|
||||
if (isset($this->events['connect']) &&
|
||||
is_callable($func=$this->events['connect']))
|
||||
$func($this);
|
||||
|
||||
@@ -167,7 +167,7 @@ class Jig {
|
||||
function __destruct() {
|
||||
if ($this->lazy) {
|
||||
$this->lazy = FALSE;
|
||||
foreach ($this->data as $file => $data)
|
||||
foreach ($this->data?:[] as $file => $data)
|
||||
$this->write($file,$data);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -93,7 +93,7 @@ class Mapper extends \DB\Cursor {
|
||||
* @param $id string
|
||||
* @param $row array
|
||||
**/
|
||||
protected function factory($id,$row) {
|
||||
function factory($id,$row) {
|
||||
$mapper=clone($this);
|
||||
$mapper->reset();
|
||||
$mapper->id=$id;
|
||||
@@ -153,7 +153,7 @@ class Mapper extends \DB\Cursor {
|
||||
* @return static[]|FALSE
|
||||
* @param $filter array
|
||||
* @param $options array
|
||||
* @param $ttl int
|
||||
* @param $ttl int|array
|
||||
* @param $log bool
|
||||
**/
|
||||
function find($filter=NULL,array $options=NULL,$ttl=0,$log=TRUE) {
|
||||
@@ -170,9 +170,12 @@ class Mapper extends \DB\Cursor {
|
||||
$db=$this->db;
|
||||
$now=microtime(TRUE);
|
||||
$data=[];
|
||||
$tag='';
|
||||
if (is_array($ttl))
|
||||
list($ttl,$tag)=$ttl;
|
||||
if (!$fw->CACHE || !$ttl || !($cached=$cache->exists(
|
||||
$hash=$fw->hash($this->db->dir().
|
||||
$fw->stringify([$filter,$options])).'.jig',$data)) ||
|
||||
$fw->stringify([$filter,$options])).($tag?'.'.$tag:'').'.jig',$data)) ||
|
||||
$cached[0]+$ttl<microtime(TRUE)) {
|
||||
$data=$db->read($this->file);
|
||||
if (is_null($data))
|
||||
@@ -347,7 +350,7 @@ class Mapper extends \DB\Cursor {
|
||||
* @return int
|
||||
* @param $filter array
|
||||
* @param $options array
|
||||
* @param $ttl int
|
||||
* @param $ttl int|array
|
||||
**/
|
||||
function count($filter=NULL,array $options=NULL,$ttl=0) {
|
||||
$now=microtime(TRUE);
|
||||
|
||||
@@ -180,7 +180,11 @@ class Session extends Mapper {
|
||||
register_shutdown_function('session_commit');
|
||||
$fw=\Base::instance();
|
||||
$headers=$fw->HEADERS;
|
||||
$this->_csrf=$fw->SEED.'.'.$fw->hash(mt_rand());
|
||||
$this->_csrf=$fw->hash($fw->SEED.
|
||||
extension_loaded('openssl')?
|
||||
implode(unpack('L',openssl_random_pseudo_bytes(4))):
|
||||
mt_rand()
|
||||
);
|
||||
if ($key)
|
||||
$fw->$key=$this->_csrf;
|
||||
$this->_agent=isset($headers['User-Agent'])?$headers['User-Agent']:'';
|
||||
|
||||
@@ -91,7 +91,7 @@ class Mapper extends \DB\Cursor {
|
||||
* @return static
|
||||
* @param $row array
|
||||
**/
|
||||
protected function factory($row) {
|
||||
function factory($row) {
|
||||
$mapper=clone($this);
|
||||
$mapper->reset();
|
||||
foreach ($row as $key=>$val)
|
||||
@@ -119,7 +119,7 @@ class Mapper extends \DB\Cursor {
|
||||
* @param $fields string
|
||||
* @param $filter array
|
||||
* @param $options array
|
||||
* @param $ttl int
|
||||
* @param $ttl int|array
|
||||
**/
|
||||
function select($fields=NULL,$filter=NULL,array $options=NULL,$ttl=0) {
|
||||
if (!$options)
|
||||
@@ -130,10 +130,13 @@ class Mapper extends \DB\Cursor {
|
||||
'limit'=>0,
|
||||
'offset'=>0
|
||||
];
|
||||
$tag='';
|
||||
if (is_array($ttl))
|
||||
list($ttl,$tag)=$ttl;
|
||||
$fw=\Base::instance();
|
||||
$cache=\Cache::instance();
|
||||
if (!($cached=$cache->exists($hash=$fw->hash($this->db->dsn().
|
||||
$fw->stringify([$fields,$filter,$options])).'.mongo',
|
||||
$fw->stringify([$fields,$filter,$options])).($tag?'.'.$tag:'').'.mongo',
|
||||
$result)) || !$ttl || $cached[0]+$ttl<microtime(TRUE)) {
|
||||
if ($options['group']) {
|
||||
$grp=$this->collection->group(
|
||||
@@ -194,7 +197,7 @@ class Mapper extends \DB\Cursor {
|
||||
* @return static[]
|
||||
* @param $filter array
|
||||
* @param $options array
|
||||
* @param $ttl int
|
||||
* @param $ttl int|array
|
||||
**/
|
||||
function find($filter=NULL,array $options=NULL,$ttl=0) {
|
||||
if (!$options)
|
||||
@@ -213,13 +216,16 @@ class Mapper extends \DB\Cursor {
|
||||
* @return int
|
||||
* @param $filter array
|
||||
* @param $options array
|
||||
* @param $ttl int
|
||||
* @param $ttl int|array
|
||||
**/
|
||||
function count($filter=NULL,array $options=NULL,$ttl=0) {
|
||||
$fw=\Base::instance();
|
||||
$cache=\Cache::instance();
|
||||
$tag='';
|
||||
if (is_array($ttl))
|
||||
list($ttl,$tag)=$ttl;
|
||||
if (!($cached=$cache->exists($hash=$fw->hash($fw->stringify(
|
||||
[$filter])).'.mongo',$result)) || !$ttl ||
|
||||
[$filter])).($tag?'.'.$tag:'').'.mongo',$result)) || !$ttl ||
|
||||
$cached[0]+$ttl<microtime(TRUE)) {
|
||||
$result=$this->collection->count($filter?:[]);
|
||||
if ($fw->CACHE && $ttl)
|
||||
|
||||
@@ -180,7 +180,11 @@ class Session extends Mapper {
|
||||
register_shutdown_function('session_commit');
|
||||
$fw=\Base::instance();
|
||||
$headers=$fw->HEADERS;
|
||||
$this->_csrf=$fw->SEED.'.'.$fw->hash(mt_rand());
|
||||
$this->_csrf=$fw->hash($fw->SEED.
|
||||
extension_loaded('openssl')?
|
||||
implode(unpack('L',openssl_random_pseudo_bytes(4))):
|
||||
mt_rand()
|
||||
);
|
||||
if ($key)
|
||||
$fw->$key=$this->_csrf;
|
||||
$this->_agent=isset($headers['User-Agent'])?$headers['User-Agent']:'';
|
||||
|
||||
@@ -220,7 +220,7 @@ class SQL {
|
||||
'/';
|
||||
}
|
||||
if ($log)
|
||||
$this->log.=($stamp?(date('r').' '):'').' (-0ms) '.
|
||||
$this->log.=($stamp?(date('r').' '):'').'(-0ms) '.
|
||||
preg_replace($keys,$vals,
|
||||
str_replace('?',chr(0).'?',$cmd),1).PHP_EOL;
|
||||
$query->execute();
|
||||
@@ -235,7 +235,7 @@ class SQL {
|
||||
user_error('PDOStatement: '.$error[2],E_USER_ERROR);
|
||||
}
|
||||
if (preg_match('/(?:^[\s\(]*'.
|
||||
'(?:EXPLAIN|SELECT|PRAGMA|SHOW)|RETURNING)\b/is',$cmd) ||
|
||||
'(?:WITH|EXPLAIN|SELECT|PRAGMA|SHOW)|RETURNING)\b/is',$cmd) ||
|
||||
(preg_match('/^\s*(?:CALL|EXEC)\b/is',$cmd) &&
|
||||
$query->columnCount())) {
|
||||
$result=$query->fetchall(\PDO::FETCH_ASSOC);
|
||||
|
||||
@@ -34,6 +34,8 @@ class Mapper extends \DB\Cursor {
|
||||
$source,
|
||||
//! SQL table (quoted)
|
||||
$table,
|
||||
//! Alias for SQL table
|
||||
$as,
|
||||
//! Last insert ID
|
||||
$_id,
|
||||
//! Defined fields
|
||||
@@ -156,7 +158,7 @@ class Mapper extends \DB\Cursor {
|
||||
* @return static
|
||||
* @param $row array
|
||||
**/
|
||||
protected function factory($row) {
|
||||
function factory($row) {
|
||||
$mapper=clone($this);
|
||||
$mapper->reset();
|
||||
foreach ($row as $key=>$val) {
|
||||
@@ -207,10 +209,13 @@ class Mapper extends \DB\Cursor {
|
||||
'group'=>NULL,
|
||||
'order'=>NULL,
|
||||
'limit'=>0,
|
||||
'offset'=>0
|
||||
'offset'=>0,
|
||||
'comment'=>NULL
|
||||
];
|
||||
$db=$this->db;
|
||||
$sql='SELECT '.$fields.' FROM '.$this->table;
|
||||
if (isset($this->as))
|
||||
$sql.=' AS '.$this->db->quotekey($this->as);
|
||||
$args=[];
|
||||
if (is_array($filter)) {
|
||||
$args=isset($filter[1]) && is_array($filter[1])?
|
||||
@@ -237,9 +242,10 @@ class Mapper extends \DB\Cursor {
|
||||
}
|
||||
if ($options['order']) {
|
||||
$char=substr($db->quotekey(''),0,1);// quoting char
|
||||
$order=' ORDER BY '.(FALSE===strpos($options['order'],$char)?
|
||||
$order=' ORDER BY '.(is_bool(strpos($options['order'],$char))?
|
||||
implode(',',array_map(function($str) use($db) {
|
||||
return preg_match('/^\h*(\w+[._\-\w]*)(?:\h+((?:ASC|DESC)[\w\h]*))?\h*$/i',
|
||||
return preg_match('/^\h*(\w+[._\-\w]*)'.
|
||||
'(?:\h+((?:ASC|DESC)[\w\h]*))?\h*$/i',
|
||||
$str,$parts)?
|
||||
($db->quotekey($parts[1]).
|
||||
(isset($parts[2])?(' '.$parts[2]):'')):$str;
|
||||
@@ -281,6 +287,8 @@ class Mapper extends \DB\Cursor {
|
||||
if ($options['offset'])
|
||||
$sql.=' OFFSET '.(int)$options['offset'];
|
||||
}
|
||||
if ($options['comment'])
|
||||
$sql.="\n".' /* '.$options['comment'].' */';
|
||||
return [$sql,$args];
|
||||
}
|
||||
|
||||
@@ -345,19 +353,30 @@ class Mapper extends \DB\Cursor {
|
||||
* @param $ttl int|array
|
||||
**/
|
||||
function count($filter=NULL,array $options=NULL,$ttl=0) {
|
||||
$adhoc='';
|
||||
if (!($subquery_mode=($options && !empty($options['group']))))
|
||||
$this->adhoc['_rows']=['expr'=>'COUNT(*)','value'=>NULL];
|
||||
$adhoc=[];
|
||||
foreach ($this->adhoc as $key=>$field)
|
||||
$adhoc.=','.$field['expr'].' AS '.$this->db->quotekey($key);
|
||||
$fields='*'.$adhoc;
|
||||
if (preg_match('/mssql|dblib|sqlsrv/',$this->engine))
|
||||
$fields='TOP 100 PERCENT '.$fields;
|
||||
// Add all adhoc fields
|
||||
// (make them available for grouping, sorting, having)
|
||||
$adhoc[]=$field['expr'].' AS '.$this->db->quotekey($key);
|
||||
$fields=implode(',',$adhoc);
|
||||
if ($subquery_mode) {
|
||||
if (empty($fields))
|
||||
// Select at least one field, ideally the grouping fields
|
||||
// or sqlsrv fails
|
||||
$fields=preg_replace('/HAVING.+$/i','',$options['group']);
|
||||
if (preg_match('/mssql|dblib|sqlsrv/',$this->engine))
|
||||
$fields='TOP 100 PERCENT '.$fields;
|
||||
}
|
||||
list($sql,$args)=$this->stringify($fields,$filter,$options);
|
||||
$sql='SELECT COUNT(*) AS '.$this->db->quotekey('_rows').' '.
|
||||
'FROM ('.$sql.') AS '.$this->db->quotekey('_temp');
|
||||
if ($subquery_mode)
|
||||
$sql='SELECT COUNT(*) AS '.$this->db->quotekey('_rows').' '.
|
||||
'FROM ('.$sql.') AS '.$this->db->quotekey('_temp');
|
||||
$result=$this->db->exec($sql,$args,$ttl);
|
||||
unset($this->adhoc['_rows']);
|
||||
return (int)$result[0]['_rows'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Return record at specified offset using same criteria as
|
||||
* previous load() call and make it active
|
||||
@@ -651,6 +670,15 @@ class Mapper extends \DB\Cursor {
|
||||
return new \ArrayIterator($this->cast());
|
||||
}
|
||||
|
||||
/**
|
||||
* Assign alias for table
|
||||
* @param $alias string
|
||||
**/
|
||||
function alias($alias) {
|
||||
$this->as=$alias;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Instantiate class
|
||||
* @param $db \DB\SQL
|
||||
|
||||
@@ -204,7 +204,11 @@ class Session extends Mapper {
|
||||
register_shutdown_function('session_commit');
|
||||
$fw=\Base::instance();
|
||||
$headers=$fw->HEADERS;
|
||||
$this->_csrf=$fw->SEED.'.'.$fw->hash(mt_rand());
|
||||
$this->_csrf=$fw->hash($fw->SEED.
|
||||
extension_loaded('openssl')?
|
||||
implode(unpack('L',openssl_random_pseudo_bytes(4))):
|
||||
mt_rand()
|
||||
);
|
||||
if ($key)
|
||||
$fw->$key=$this->_csrf;
|
||||
$this->_agent=isset($headers['User-Agent'])?$headers['User-Agent']:'';
|
||||
|
||||
@@ -35,14 +35,15 @@ class Log {
|
||||
**/
|
||||
function write($text,$format='r') {
|
||||
$fw=Base::instance();
|
||||
$fw->write(
|
||||
$this->file,
|
||||
date($format).
|
||||
(isset($_SERVER['REMOTE_ADDR'])?
|
||||
(' ['.$_SERVER['REMOTE_ADDR'].']'):'').' '.
|
||||
trim($text).PHP_EOL,
|
||||
TRUE
|
||||
);
|
||||
foreach (preg_split('/\r?\n|\r/',trim($text)) as $line)
|
||||
$fw->write(
|
||||
$this->file,
|
||||
date($format).
|
||||
(isset($_SERVER['REMOTE_ADDR'])?
|
||||
(' ['.$_SERVER['REMOTE_ADDR'].']'):'').' '.
|
||||
trim($line).PHP_EOL,
|
||||
TRUE
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -92,13 +92,15 @@ class Matrix extends Prefab {
|
||||
* Return month calendar of specified date, with optional setting for
|
||||
* first day of week (0 for Sunday)
|
||||
* @return array
|
||||
* @param $date string
|
||||
* @param $date string|int
|
||||
* @param $first int
|
||||
**/
|
||||
function calendar($date='now',$first=0) {
|
||||
$out=FALSE;
|
||||
if (extension_loaded('calendar')) {
|
||||
$parts=getdate(strtotime($date));
|
||||
if (is_string($date))
|
||||
$date=strtotime($date);
|
||||
$parts=getdate($date);
|
||||
$days=cal_days_in_month(CAL_GREGORIAN,$parts['mon'],$parts['year']);
|
||||
$ref=date('w',strtotime(date('Y-m',$parts[0]).'-01'))+(7-$first)%7;
|
||||
$out=[];
|
||||
|
||||
@@ -182,7 +182,11 @@ class Session {
|
||||
register_shutdown_function('session_commit');
|
||||
$fw=\Base::instance();
|
||||
$headers=$fw->HEADERS;
|
||||
$this->_csrf=$fw->SEED.'.'.$fw->hash(mt_rand());
|
||||
$this->_csrf=$fw->hash($fw->SEED.
|
||||
extension_loaded('openssl')?
|
||||
implode(unpack('L',openssl_random_pseudo_bytes(4))):
|
||||
mt_rand()
|
||||
);
|
||||
if ($key)
|
||||
$fw->$key=$this->_csrf;
|
||||
$this->_agent=isset($headers['User-Agent'])?$headers['User-Agent']:'';
|
||||
|
||||
@@ -204,14 +204,19 @@ class SMTP extends Magic {
|
||||
stream_set_blocking($socket,TRUE);
|
||||
}
|
||||
// Get server's initial response
|
||||
$this->dialog(NULL,TRUE,$mock);
|
||||
$this->dialog(NULL,$log,$mock);
|
||||
// Announce presence
|
||||
$reply=$this->dialog('EHLO '.$fw->HOST,$log,$mock);
|
||||
if (strtolower($this->scheme)=='tls') {
|
||||
$this->dialog('STARTTLS',$log,$mock);
|
||||
if (!$mock)
|
||||
stream_socket_enable_crypto(
|
||||
$socket,TRUE,STREAM_CRYPTO_METHOD_TLS_CLIENT);
|
||||
if (!$mock) {
|
||||
$method=STREAM_CRYPTO_METHOD_TLS_CLIENT;
|
||||
if (defined('STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT')) {
|
||||
$method|=STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT;
|
||||
$method|=STREAM_CRYPTO_METHOD_TLSv1_1_CLIENT;
|
||||
}
|
||||
stream_socket_enable_crypto($socket,TRUE,$method);
|
||||
}
|
||||
$reply=$this->dialog('EHLO '.$fw->HOST,$log,$mock);
|
||||
}
|
||||
$message=wordwrap($message,998);
|
||||
|
||||
@@ -273,7 +273,7 @@ class Template extends Preview {
|
||||
// Build tree structure
|
||||
for ($ptr=0,$w=5,$len=strlen($text),$tree=[],$tmp='';$ptr<$len;)
|
||||
if (preg_match('/^(.{0,'.$w.'}?)<(\/?)(?:F3:)?'.
|
||||
'('.$this->tags.')\b((?:\s+[\w-.:@!]+'.
|
||||
'('.$this->tags.')\b((?:\s+[\w.:@!-]+'.
|
||||
'(?:\h*=\h*(?:"(?:.*?)"|\'(?:.*?)\'))?|'.
|
||||
'\h*\{\{.+?\}\})*)\h*(\/?)>/is',
|
||||
substr($text,$ptr),$match)) {
|
||||
|
||||
@@ -269,7 +269,7 @@ class Web extends Prefab {
|
||||
**/
|
||||
protected function _curl($url,$options) {
|
||||
$curl=curl_init($url);
|
||||
if (!ini_get('open_basedir'))
|
||||
if (!$open_basedir=ini_get('open_basedir'))
|
||||
curl_setopt($curl,CURLOPT_FOLLOWLOCATION,
|
||||
$options['follow_location']);
|
||||
curl_setopt($curl,CURLOPT_MAXREDIRS,
|
||||
@@ -306,7 +306,7 @@ class Web extends Prefab {
|
||||
curl_close($curl);
|
||||
$body=ob_get_clean();
|
||||
if (!$err &&
|
||||
$options['follow_location'] &&
|
||||
$options['follow_location'] && $open_basedir &&
|
||||
preg_grep('/HTTP\/1\.\d 3\d{2}/',$headers) &&
|
||||
preg_match('/^Location: (.+)$/m',implode(PHP_EOL,$headers),$loc)) {
|
||||
$options['max_redirects']--;
|
||||
@@ -350,7 +350,7 @@ class Web extends Prefab {
|
||||
if (is_string($body)) {
|
||||
$match=NULL;
|
||||
foreach ($headers as $header)
|
||||
if (preg_match('/Content-Encoding: (.+)/',$header,$match))
|
||||
if (preg_match('/Content-Encoding: (.+)/i',$header,$match))
|
||||
break;
|
||||
if ($match)
|
||||
switch ($match[1]) {
|
||||
@@ -442,7 +442,7 @@ class Web extends Prefab {
|
||||
$headers=array_merge($headers,$current=explode($eol,$html[0]));
|
||||
$match=NULL;
|
||||
foreach ($current as $header)
|
||||
if (preg_match('/Content-Encoding: (.+)/',$header,$match))
|
||||
if (preg_match('/Content-Encoding: (.+)/i',$header,$match))
|
||||
break;
|
||||
if ($match)
|
||||
switch ($match[1]) {
|
||||
@@ -550,7 +550,7 @@ class Web extends Prefab {
|
||||
);
|
||||
if (isset($options['content']) && is_string($options['content'])) {
|
||||
if ($options['method']=='POST' &&
|
||||
!preg_grep('/^Content-Type:/',$options['header']))
|
||||
!preg_grep('/^Content-Type:/i',$options['header']))
|
||||
$this->subst($options['header'],
|
||||
'Content-Type: application/x-www-form-urlencoded');
|
||||
$this->subst($options['header'],
|
||||
@@ -588,7 +588,7 @@ class Web extends Prefab {
|
||||
$result['cached']=TRUE;
|
||||
}
|
||||
elseif (preg_match('/Cache-Control:(?:.*)max-age=(\d+)(?:,?.*'.
|
||||
preg_quote($eol).')/',implode($eol,$result['headers']),$exp))
|
||||
preg_quote($eol).')/i',implode($eol,$result['headers']),$exp))
|
||||
$cache->set($hash,$result,$exp[1]);
|
||||
}
|
||||
$req=[$options['method'].' '.$url];
|
||||
@@ -903,7 +903,7 @@ class Web extends Prefab {
|
||||
for ($i=0,$add=$count-(int)$std;$i<$add;$i++) {
|
||||
shuffle($rnd);
|
||||
$words=array_slice($rnd,0,mt_rand(3,$max));
|
||||
$out.=' '.ucfirst(implode(' ',$words)).'.';
|
||||
$out.=(!$std&&$i==0?'':' ').ucfirst(implode(' ',$words)).'.';
|
||||
}
|
||||
return $out;
|
||||
}
|
||||
|
||||
@@ -64,8 +64,11 @@ class Geo extends \Prefab {
|
||||
$out=@geoip_record_by_name($ip)) {
|
||||
$out['request']=$ip;
|
||||
$out['region_code']=$out['region'];
|
||||
$out['region_name']=(!empty($out['country_code']) && !empty($out['region']))
|
||||
? geoip_region_name_by_code($out['country_code'],$out['region']) : '';
|
||||
$out['region_name']='';
|
||||
if (!empty($out['country_code']) && !empty($out['region']))
|
||||
$out['region_name']=geoip_region_name_by_code(
|
||||
$out['country_code'],$out['region']
|
||||
);
|
||||
unset($out['country_code3'],$out['region'],$out['postal_code']);
|
||||
return $out;
|
||||
}
|
||||
|
||||
@@ -33,9 +33,10 @@ class OAuth2 extends \Magic {
|
||||
* Return OAuth2 authentication URI
|
||||
* @return string
|
||||
* @param $endpoint string
|
||||
* @param $query bool
|
||||
**/
|
||||
function uri($endpoint) {
|
||||
return $endpoint.'?'.http_build_query($this->args);
|
||||
function uri($endpoint,$query=TRUE) {
|
||||
return $endpoint.($query?('?'.http_build_query($this->args)):'');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -54,7 +55,7 @@ class OAuth2 extends \Magic {
|
||||
];
|
||||
if ($token)
|
||||
array_push($options['header'],'Authorization: Bearer '.$token);
|
||||
elseif ($method=='POST')
|
||||
elseif ($method=='POST' && isset($this->args['client_id']))
|
||||
array_push($options['header'],'Authorization: Basic '.
|
||||
base64_encode(
|
||||
$this->args['client_id'].':'.
|
||||
@@ -64,10 +65,20 @@ class OAuth2 extends \Magic {
|
||||
$response=$web->request($uri,$options);
|
||||
if ($response['error'])
|
||||
user_error($response['error'],E_USER_ERROR);
|
||||
return $response['body'] &&
|
||||
preg_grep('/HTTP\/1\.\d 200/',$response['headers'])?
|
||||
json_decode($response['body'],TRUE):
|
||||
FALSE;
|
||||
if (isset($response['body'])) {
|
||||
if (preg_grep('/^Content-Type:.*application\/json/i',
|
||||
$response['headers'])) {
|
||||
$token=json_decode($response['body'],TRUE);
|
||||
if (isset($token['error_description']))
|
||||
user_error($token['error_description'],E_USER_ERROR);
|
||||
if (isset($token['error']))
|
||||
user_error($token['error'],E_USER_ERROR);
|
||||
return $token;
|
||||
}
|
||||
else
|
||||
return $response['body'];
|
||||
}
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -78,16 +89,21 @@ class OAuth2 extends \Magic {
|
||||
function jwt($token) {
|
||||
return json_decode(
|
||||
base64_decode(
|
||||
str_replace(
|
||||
['-','_'],
|
||||
['+','/'],
|
||||
explode('.',$token)[1]
|
||||
)
|
||||
str_replace(['-','_'],['+','/'],explode('.',$token)[1])
|
||||
),
|
||||
TRUE
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* URL-safe base64 encoding
|
||||
* @return array
|
||||
* @param $data string
|
||||
**/
|
||||
function b64url($data) {
|
||||
return trim(strtr(base64_encode($data),'+/','-_'),'=');
|
||||
}
|
||||
|
||||
/**
|
||||
* Return TRUE if scope/claim exists
|
||||
* @return bool
|
||||
|
||||
@@ -26,7 +26,7 @@ class AccessController extends Controller {
|
||||
if($return = parent::beforeroute($f3, $params)){
|
||||
// Any route/endpoint of a child class of this one,
|
||||
// requires a valid logged in user!
|
||||
if( !$this->isLoggedIn($f3) ){
|
||||
if($this->isLoggedIn($f3) !== 'OK'){
|
||||
// no character found or login timer expired
|
||||
$this->logoutCharacter($f3);
|
||||
// skip route handler and afterroute()
|
||||
@@ -40,51 +40,40 @@ class AccessController extends Controller {
|
||||
/**
|
||||
* get current character and check if it is a valid character
|
||||
* @param \Base $f3
|
||||
* @return bool
|
||||
* @return string
|
||||
* @throws \Exception
|
||||
*/
|
||||
protected function isLoggedIn(\Base $f3): bool {
|
||||
$loginCheck = false;
|
||||
if( $character = $this->getCharacter() ){
|
||||
if($this->checkLogTimer($f3, $character)){
|
||||
if($character->isAuthorized() === 'OK'){
|
||||
$loginCheck = true;
|
||||
protected function isLoggedIn(\Base $f3): string {
|
||||
$loginStatus = 'UNKNOWN';
|
||||
if($character = $this->getCharacter()){
|
||||
if($character->checkLoginTimer()){
|
||||
if(( $authStatus = $character->isAuthorized()) === 'OK'){
|
||||
$loginStatus = 'OK';
|
||||
}else{
|
||||
$loginStatus = $authStatus;
|
||||
}
|
||||
}else{
|
||||
$loginStatus = 'MAX LOGIN TIME EXCEEDED';
|
||||
}
|
||||
}else{
|
||||
$loginStatus = 'NO SESSION FOUND';
|
||||
}
|
||||
|
||||
return $loginCheck;
|
||||
}
|
||||
|
||||
/**
|
||||
* checks whether a user/character is currently logged in
|
||||
* @param \Base $f3
|
||||
* @param Model\CharacterModel $character
|
||||
* @return bool
|
||||
*/
|
||||
private function checkLogTimer(\Base $f3, Model\CharacterModel $character){
|
||||
$loginCheck = false;
|
||||
|
||||
// log character access status in debug mode
|
||||
if(
|
||||
!$character->dry() &&
|
||||
$character->lastLogin
|
||||
$loginStatus !== 'OK' &&
|
||||
$f3->get('DEBUG') === 3
|
||||
){
|
||||
// check logIn time
|
||||
$logInTime = new \DateTime($character->lastLogin);
|
||||
$now = new \DateTime();
|
||||
|
||||
$timeDiff = $now->diff($logInTime);
|
||||
|
||||
$minutes = $timeDiff->days * 60 * 24 * 60;
|
||||
$minutes += $timeDiff->h * 60;
|
||||
$minutes += $timeDiff->i;
|
||||
|
||||
if($minutes <= Config::getPathfinderData('timer.logged')){
|
||||
$loginCheck = true;
|
||||
}
|
||||
self::getLogger('CHARACTER_ACCESS')->write(
|
||||
sprintf(Model\CharacterModel::LOG_ACCESS,
|
||||
$character->_id ,
|
||||
$loginStatus,
|
||||
$character->name
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
return $loginCheck;
|
||||
return $loginStatus;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<?php
|
||||
<?php /** @noinspection PhpUndefinedMethodInspection */
|
||||
|
||||
/**
|
||||
* Created by PhpStorm.
|
||||
* User: exodus4d
|
||||
@@ -7,6 +8,8 @@
|
||||
*/
|
||||
|
||||
namespace Controller\Api;
|
||||
|
||||
|
||||
use lib\Config;
|
||||
use Controller;
|
||||
|
||||
@@ -18,135 +21,68 @@ use Controller;
|
||||
*/
|
||||
class GitHub extends Controller\Controller {
|
||||
|
||||
protected function getBaseRequestOptions() : array {
|
||||
return [
|
||||
'timeout' => 3,
|
||||
'user_agent' => $this->getUserAgent(),
|
||||
'follow_location' => false // otherwise CURLOPT_FOLLOWLOCATION will fail
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* get HTTP request options for API (curl) request
|
||||
* @return array
|
||||
*/
|
||||
protected function getRequestReleaseOptions() : array {
|
||||
$options = $this->getBaseRequestOptions();
|
||||
$options['method'] = 'GET';
|
||||
return $options;
|
||||
}
|
||||
|
||||
/**
|
||||
* get HTTP request options for API (curl) request
|
||||
* @param string $text
|
||||
* @return array
|
||||
*/
|
||||
protected function getRequestMarkdownOptions(string $text) : array {
|
||||
$params = [
|
||||
'text' => $text,
|
||||
'mode' => 'gfm',
|
||||
'context' => 'exodus4d/pathfinder'
|
||||
];
|
||||
|
||||
$options = $this->getBaseRequestOptions();
|
||||
$options['method'] = 'POST';
|
||||
$options['content'] = json_encode($params, JSON_UNESCAPED_SLASHES);
|
||||
return $options;
|
||||
}
|
||||
|
||||
/**
|
||||
* get release information from GitHub
|
||||
* @param \Base $f3
|
||||
*/
|
||||
public function releases(\Base $f3){
|
||||
$cacheKey = 'CACHE_GITHUB_RELEASES';
|
||||
$ttl = 60 * 30; // 30min
|
||||
$releaseCount = 4;
|
||||
|
||||
if( !$f3->exists($cacheKey, $return) ){
|
||||
$apiReleasePath = Config::getPathfinderData('api.git_hub') . '/repos/exodus4d/pathfinder/releases';
|
||||
$apiMarkdownPath = Config::getPathfinderData('api.git_hub') . '/markdown';
|
||||
$return = (object) [];
|
||||
$return->releasesData = [];
|
||||
$return->version = (object) [];
|
||||
$return->version->current = Config::getPathfinderData('version');
|
||||
$return->version->last = '';
|
||||
$return->version->delta = null;
|
||||
$return->version->dev = false;
|
||||
|
||||
// build request URL
|
||||
$apiResponse = \Web::instance()->request($apiReleasePath, $this->getRequestReleaseOptions() );
|
||||
$md = \Markdown::instance();
|
||||
|
||||
if($apiResponse['body']){
|
||||
$return = (object) [];
|
||||
$return->releasesData = [];
|
||||
$return->version = (object) [];
|
||||
$return->version->current = Config::getPathfinderData('version');
|
||||
$return->version->last = '';
|
||||
$return->version->delta = null;
|
||||
$return->version->dev = false;
|
||||
$releases = $f3->gitHubClient()->getProjectReleases('exodus4d/pathfinder', $releaseCount);
|
||||
|
||||
// request succeeded -> format "Markdown" to "HTML"
|
||||
// result is JSON formed
|
||||
$releasesData = (array)json_decode($apiResponse['body']);
|
||||
|
||||
// check max release count
|
||||
if(count($releasesData) > $releaseCount){
|
||||
$releasesData = array_slice($releasesData, 0, $releaseCount);
|
||||
foreach($releases as $key => &$release){
|
||||
// check version ------------------------------------------------------------------------------------------
|
||||
if($key === 0){
|
||||
$return->version->last = $release['name'];
|
||||
if(version_compare( $return->version->current, $return->version->last, '>')){
|
||||
$return->version->dev = true;
|
||||
}
|
||||
|
||||
$md = \Markdown::instance();
|
||||
foreach($releasesData as $key => &$releaseData){
|
||||
// check version ----------------------------------------------------------------------------------
|
||||
if($key === 0){
|
||||
$return->version->last = $releaseData->tag_name;
|
||||
|
||||
if(version_compare( $return->version->current, $return->version->last, '>')){
|
||||
$return->version->dev = true;
|
||||
}
|
||||
}
|
||||
|
||||
if(
|
||||
!$return->version->dev &&
|
||||
version_compare( $releaseData->tag_name, $return->version->current, '>=')
|
||||
){
|
||||
$return->version->delta = ($key === count($releasesData) - 1) ? '>= ' . $key : $key;
|
||||
}
|
||||
|
||||
// format body ------------------------------------------------------------------------------------
|
||||
if(isset($releaseData->body)){
|
||||
$body = $releaseData->body;
|
||||
|
||||
// remove "update information" from release text
|
||||
// -> keep everything until first "***" -> horizontal line
|
||||
if( ($pos = strpos($body, '***')) !== false){
|
||||
$body = substr($body, 0, $pos);
|
||||
}
|
||||
|
||||
// convert list style
|
||||
$body = str_replace(' - ', '* ', $body );
|
||||
|
||||
// convert Markdown to HTML -> use either gitHub API (in oder to create abs, issue links)
|
||||
// -> or F3´s markdown as fallback
|
||||
$markdownResponse = \Web::instance()->request($apiMarkdownPath, $this->getRequestMarkdownOptions($body) );
|
||||
|
||||
if($markdownResponse['body']){
|
||||
$body = $markdownResponse['body'];
|
||||
}else{
|
||||
$body = $md->convert( trim($body) );
|
||||
}
|
||||
|
||||
$releaseData->body = $body;
|
||||
}
|
||||
}
|
||||
|
||||
$return->releasesData = $releasesData;
|
||||
|
||||
$f3->set($cacheKey, $return, $ttl);
|
||||
}else{
|
||||
// request failed -> cache failed result (respect API request limit)
|
||||
$f3->set($cacheKey, false, 60 * 15);
|
||||
}
|
||||
|
||||
if(
|
||||
!$return->version->dev &&
|
||||
version_compare($release['name'], $return->version->current, '>=')
|
||||
){
|
||||
$return->version->delta = ($key === count($releases) - 1) ? '>= ' . $key : $key;
|
||||
}
|
||||
|
||||
// format body ------------------------------------------------------------------------------------
|
||||
$body = $release['body'];
|
||||
|
||||
// remove "update information" from release text
|
||||
// -> keep everything until first "***" -> horizontal line
|
||||
if( ($pos = strpos($body, '***')) !== false){
|
||||
$body = substr($body, 0, $pos);
|
||||
}
|
||||
|
||||
// convert list style
|
||||
$body = str_replace(' - ', '* ', $body);
|
||||
|
||||
// convert Markdown to HTML -> use either gitHub API (in oder to create abs, issue links)
|
||||
// -> or F3´s markdown as fallback
|
||||
$html = $f3->gitHubClient()->markdownToHtml('exodus4d/pathfinder', $body);
|
||||
|
||||
if(!empty($html)){
|
||||
$body = $html;
|
||||
}else{
|
||||
$body = $md->convert(trim($body));
|
||||
}
|
||||
|
||||
$release['body'] = $body;
|
||||
}
|
||||
|
||||
// set 503 if service unavailable or temp cached data = false
|
||||
if( !$f3->get($cacheKey) ){
|
||||
$f3->status(503);
|
||||
}
|
||||
$return->releasesData = $releases;
|
||||
|
||||
echo json_encode($f3->get($cacheKey));
|
||||
echo json_encode($return);
|
||||
}
|
||||
}
|
||||
@@ -183,6 +183,11 @@ class Map extends Controller\AccessController {
|
||||
'zKillboard' => Config::getPathfinderData('api.z_killboard')
|
||||
];
|
||||
|
||||
// Character default config -------------------------------------------------------------------------------
|
||||
$return->character = [
|
||||
'autoLocationSelect' => (bool)Config::getPathfinderData('character.auto_location_select')
|
||||
];
|
||||
|
||||
// Slack integration status -------------------------------------------------------------------------------
|
||||
$return->slack = [
|
||||
'status' => (bool)Config::getPathfinderData('slack.status')
|
||||
@@ -204,6 +209,9 @@ class Map extends Controller\AccessController {
|
||||
$validInitData = $validInitData ? !empty($structureData) : $validInitData;
|
||||
|
||||
// get available wormhole types ---------------------------------------------------------------------------
|
||||
/**
|
||||
* @var $wormhole Model\Universe\WormholeModel
|
||||
*/
|
||||
$wormhole = Model\Universe\BasicUniverseModel::getNew('WormholeModel');
|
||||
$wormholesData = [];
|
||||
if($rows = $wormhole->find(null, ['order' => 'name asc'])){
|
||||
@@ -419,7 +427,7 @@ class Map extends Controller\AccessController {
|
||||
$return->error = [];
|
||||
|
||||
if( isset($formData['id']) ){
|
||||
$activeCharacter = $this->getCharacter(0);
|
||||
$activeCharacter = $this->getCharacter();
|
||||
|
||||
/**
|
||||
* @var $map Model\MapModel
|
||||
@@ -865,13 +873,13 @@ class Map extends Controller\AccessController {
|
||||
$getMapUserData = (bool)$postData['getMapUserData'];
|
||||
$mapTracking = (bool)$postData['mapTracking'];
|
||||
$systemData = (array)$postData['systemData'];
|
||||
$activeCharacter = $this->getCharacter(0);
|
||||
$activeCharacter = $this->getCharacter();
|
||||
|
||||
$return = (object)[];
|
||||
|
||||
// update current location
|
||||
// -> suppress temporary timeout errors
|
||||
$activeCharacter = $activeCharacter->updateLog(['suppressHTTPErrors' => true]);
|
||||
$activeCharacter = $activeCharacter->updateLog();
|
||||
|
||||
if( !empty($mapIds) ){
|
||||
// IMPORTANT for now -> just update a single map (save performance)
|
||||
|
||||
@@ -92,6 +92,9 @@ class Route extends Controller\AccessController {
|
||||
$rows = $this->getDB()->exec($query, null, $this->staticJumpDataCacheTime);
|
||||
|
||||
if(count($rows) > 0){
|
||||
array_walk($rows, function(&$row){
|
||||
$row['jumpNodes'] = array_map('intval', explode(':', $row['jumpNodes']));
|
||||
});
|
||||
$this->updateJumpData($rows);
|
||||
}
|
||||
}
|
||||
@@ -172,58 +175,65 @@ class Route extends Controller\AccessController {
|
||||
$whereQuery .= " `connection`.`eolUpdated` IS NULL AND ";
|
||||
}
|
||||
|
||||
$query = "SELECT
|
||||
`system_src`.`systemId` systemId,
|
||||
(
|
||||
SELECT
|
||||
GROUP_CONCAT( NULLIF(`system_tar`.`systemId`, NULL) SEPARATOR ':')
|
||||
$query = "SELECT
|
||||
`system_src`.`systemId` systemSourceId,
|
||||
`system_tar`.`systemId` systemTargetId
|
||||
FROM
|
||||
`connection` INNER JOIN
|
||||
`system` system_tar ON
|
||||
`system_tar`.`id` = `connection`.`source` OR
|
||||
`system_tar`.`id` = `connection`.`target`
|
||||
`map` ON
|
||||
`map`.`id` = `connection`.`mapId` AND
|
||||
`map`.`active` = 1 INNER JOIN
|
||||
`system` `system_src` ON
|
||||
`system_src`.`id` = `connection`.`source` AND
|
||||
`system_src`.`active` = 1 INNER JOIN
|
||||
`system` `system_tar` ON
|
||||
`system_tar`.`id` = `connection`.`target` AND
|
||||
`system_tar`.`active` = 1
|
||||
WHERE
|
||||
`connection`.`mapId` " . $whereMapIdsQuery . " AND
|
||||
`connection`.`active` = 1 AND
|
||||
(
|
||||
`connection`.`source` = `system_src`.`id` OR
|
||||
`connection`.`target` = `system_src`.`id`
|
||||
) AND
|
||||
" . $whereQuery . "
|
||||
`system_tar`.`id` != `system_src`.`id` AND
|
||||
`system_tar`.`active` = 1
|
||||
) jumpNodes
|
||||
FROM
|
||||
`system` `system_src` INNER JOIN
|
||||
`map` ON
|
||||
`map`.`id` = `system_src`.`mapId`
|
||||
WHERE
|
||||
`system_src`.`mapId` " . $whereMapIdsQuery . " AND
|
||||
`system_src`.`active` = 1 AND
|
||||
`map`.`active` = 1
|
||||
HAVING
|
||||
-- skip systems without neighbors (e.g. WHs)
|
||||
jumpNodes IS NOT NULL
|
||||
";
|
||||
" . $whereQuery . "
|
||||
`connection`.`active` = 1 AND
|
||||
`connection`.`mapId` " . $whereMapIdsQuery . "
|
||||
";
|
||||
|
||||
$rows = $this->getDB()->exec($query, null, $this->dynamicJumpDataCacheTime);
|
||||
|
||||
if(count($rows) > 0){
|
||||
// enrich $row data with static system data (from universe DB)
|
||||
$jumpData = [];
|
||||
$universe = new Universe();
|
||||
for($i = 0; $i < count($rows); $i++){
|
||||
if($staticData = $universe->getSystemData($rows[$i]['systemId'])){
|
||||
$rows[$i]['systemName'] = $staticData->name;
|
||||
$rows[$i]['constellationId'] = $staticData->constellation->id;
|
||||
$rows[$i]['regionId'] = $staticData->constellation->region->id;
|
||||
$rows[$i]['trueSec'] = $staticData->trueSec;
|
||||
|
||||
/**
|
||||
* enrich dynamic jump data with static system data (from universe DB)
|
||||
* @param array $row
|
||||
* @param string $systemSourceKey
|
||||
* @param string $systemTargetKey
|
||||
*/
|
||||
$enrichJumpData = function(array &$row, string $systemSourceKey, string $systemTargetKey) use (&$jumpData, &$universe) {
|
||||
if(
|
||||
!array_key_exists($row[$systemSourceKey], $jumpData) &&
|
||||
!is_null($staticData = $universe->getSystemData($row[$systemSourceKey]))
|
||||
){
|
||||
$jumpData[$row[$systemSourceKey]] = [
|
||||
'systemId' => (int)$row[$systemSourceKey],
|
||||
'systemName' => $staticData->name,
|
||||
'constellationId' => $staticData->constellation->id,
|
||||
'regionId' => $staticData->constellation->region->id,
|
||||
'trueSec' => $staticData->trueSec,
|
||||
];
|
||||
}
|
||||
|
||||
if( !in_array($row[$systemTargetKey], (array)$jumpData[$row[$systemSourceKey]]['jumpNodes']) ){
|
||||
$jumpData[$row[$systemSourceKey]]['jumpNodes'][] = (int)$row[$systemTargetKey];
|
||||
}
|
||||
};
|
||||
|
||||
for($i = 0; $i < count($rows); $i++){
|
||||
$enrichJumpData($rows[$i], 'systemSourceId', 'systemTargetId');
|
||||
$enrichJumpData($rows[$i], 'systemTargetId', 'systemSourceId');
|
||||
}
|
||||
|
||||
// update jump data for this instance
|
||||
$this->updateJumpData($rows);
|
||||
$this->updateJumpData($jumpData);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -259,7 +269,7 @@ class Route extends Controller\AccessController {
|
||||
if( !is_array($this->jumpArray[$systemId]) ){
|
||||
$this->jumpArray[$systemId] = [];
|
||||
}
|
||||
$this->jumpArray[$systemId] = array_merge(array_map('intval', explode(':', $row['jumpNodes'])), $this->jumpArray[$systemId]);
|
||||
$this->jumpArray[$systemId] = array_merge($row['jumpNodes'], $this->jumpArray[$systemId]);
|
||||
|
||||
// add systemName to end (if not already there)
|
||||
if(end($this->jumpArray[$systemId]) != $systemName){
|
||||
@@ -580,7 +590,7 @@ class Route extends Controller\AccessController {
|
||||
'connections' => $connections
|
||||
];
|
||||
|
||||
$result = $this->getF3()->ccpClient->getRouteData($systemFromId, $systemToId, $options);
|
||||
$result = $this->getF3()->ccpClient()->getRouteData($systemFromId, $systemToId, $options);
|
||||
|
||||
// format result ------------------------------------------------------------------------------------------
|
||||
|
||||
|
||||
@@ -135,7 +135,7 @@ class System extends Controller\AccessController {
|
||||
];
|
||||
|
||||
foreach($postData['systemData'] as $systemData){
|
||||
$response = $f3->ccpClient->setWaypoint($systemData['systemId'], $accessToken, $options);
|
||||
$response = $f3->ccpClient()->setWaypoint($systemData['systemId'], $accessToken, $options);
|
||||
|
||||
if(empty($response)){
|
||||
$return->systemData[] = $systemData;
|
||||
|
||||
@@ -41,24 +41,24 @@ class User extends Controller\Controller{
|
||||
|
||||
/**
|
||||
* login a valid character
|
||||
* @param Model\CharacterModel $characterModel
|
||||
* @param Model\CharacterModel $character
|
||||
* @return bool
|
||||
* @throws Exception
|
||||
*/
|
||||
protected function loginByCharacter(Model\CharacterModel &$characterModel){
|
||||
protected function loginByCharacter(Model\CharacterModel &$character) : bool {
|
||||
$login = false;
|
||||
|
||||
if($user = $characterModel->getUser()){
|
||||
if($user = $character->getUser()){
|
||||
// check if character belongs to current user
|
||||
// -> If there is already a logged in user! (e.g. multi character use)
|
||||
$currentUser = $this->getUser();
|
||||
$timezone = $this->getF3()->get('getTimeZone')();
|
||||
|
||||
$sessionCharacters = [
|
||||
[
|
||||
'ID' => $characterModel->_id,
|
||||
'NAME' => $characterModel->name,
|
||||
'TIME' => (new \DateTime())->getTimestamp(),
|
||||
'UPDATE_RETRY' => 0
|
||||
'ID' => $character->_id,
|
||||
'NAME' => $character->name,
|
||||
'TIME' => (new \DateTime('now', $timezone))->getTimestamp()
|
||||
]
|
||||
];
|
||||
|
||||
@@ -74,29 +74,29 @@ class User extends Controller\Controller{
|
||||
]);
|
||||
}else{
|
||||
// user has NOT changed -----------------------------------------------------------
|
||||
$sessionCharacters = $characterModel::mergeSessionCharacterData($sessionCharacters);
|
||||
$sessionCharacters = $character::mergeSessionCharacterData($sessionCharacters);
|
||||
}
|
||||
|
||||
$this->getF3()->set(self::SESSION_KEY_CHARACTERS, $sessionCharacters);
|
||||
|
||||
// save user login information --------------------------------------------------------
|
||||
$characterModel->roleId = $characterModel->requestRole();
|
||||
$characterModel->touch('lastLogin');
|
||||
$characterModel->save();
|
||||
$character->roleId = $character->requestRole();
|
||||
$character->touch('lastLogin');
|
||||
$character->save();
|
||||
|
||||
// write login log --------------------------------------------------------------------
|
||||
self::getLogger('LOGIN')->write(
|
||||
self::getLogger('CHARACTER_LOGIN')->write(
|
||||
sprintf(self::LOG_LOGGED_IN,
|
||||
$user->_id,
|
||||
$user->name,
|
||||
$characterModel->_id,
|
||||
$characterModel->name
|
||||
$character->_id,
|
||||
$character->name
|
||||
)
|
||||
);
|
||||
|
||||
// set temp character data ------------------------------------------------------------
|
||||
// -> pass character data over for next http request (reroute())
|
||||
$this->setTempCharacterData($characterModel->_id);
|
||||
$this->setTempCharacterData($character->_id);
|
||||
|
||||
$login = true;
|
||||
}
|
||||
@@ -112,31 +112,30 @@ class User extends Controller\Controller{
|
||||
*/
|
||||
public function getCookieCharacter(\Base $f3){
|
||||
$data = $f3->get('POST');
|
||||
$cookieName = (string)$data['cookie'];
|
||||
|
||||
$return = (object) [];
|
||||
$return->error = [];
|
||||
|
||||
if( !empty($data['cookie']) ){
|
||||
if( !empty($cookieData = $this->getCookieByName($data['cookie']) )){
|
||||
// cookie data is valid -> validate data against DB (security check!)
|
||||
// -> add characters WITHOUT permission to log in too!
|
||||
if( !empty($characters = $this->getCookieCharacters(array_slice($cookieData, 0, 1, true), false)) ){
|
||||
// character is valid and allowed to login
|
||||
$return->character = reset($characters)->getData();
|
||||
// get Session status for character
|
||||
if($activeCharacter = $this->getCharacter()){
|
||||
if($activeUser = $activeCharacter->getUser()){
|
||||
if($sessionCharacterData = $activeUser->findSessionCharacterData($return->character->id)){
|
||||
$return->character->hasActiveSession = true;
|
||||
}
|
||||
if( !empty($cookieData = $this->getCookieByName($cookieName) )){
|
||||
// cookie data is valid -> validate data against DB (security check!)
|
||||
// -> add characters WITHOUT permission to log in too!
|
||||
if( !empty($characters = $this->getCookieCharacters(array_slice($cookieData, 0, 1, true), false)) ){
|
||||
// character is valid and allowed to login
|
||||
$return->character = reset($characters)->getData();
|
||||
// get Session status for character
|
||||
if($activeCharacter = $this->getCharacter()){
|
||||
if($activeUser = $activeCharacter->getUser()){
|
||||
if($sessionCharacterData = $activeUser->findSessionCharacterData($return->character->id)){
|
||||
$return->character->hasActiveSession = true;
|
||||
}
|
||||
}
|
||||
}else{
|
||||
$characterError = (object) [];
|
||||
$characterError->type = 'warning';
|
||||
$characterError->message = 'This can happen through "invalid cookies(SSO)", "login restrictions", "ESI problems".';
|
||||
$return->error[] = $characterError;
|
||||
}
|
||||
}else{
|
||||
$characterError = (object) [];
|
||||
$characterError->type = 'warning';
|
||||
$characterError->message = 'This can happen through "invalid cookies(SSO)", "login restrictions", "ESI problems".';
|
||||
$return->error[] = $characterError;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -221,7 +220,7 @@ class User extends Controller\Controller{
|
||||
if( $targetId = (int)$data['targetId']){
|
||||
$activeCharacter = $this->getCharacter();
|
||||
|
||||
$response = $f3->ccpClient->openWindow($targetId, $activeCharacter->getAccessToken());
|
||||
$response = $f3->ccpClient()->openWindow($targetId, $activeCharacter->getAccessToken());
|
||||
|
||||
if(empty($response)){
|
||||
$return->targetId = $targetId;
|
||||
@@ -260,7 +259,7 @@ class User extends Controller\Controller{
|
||||
$formData = $data['formData'];
|
||||
|
||||
try{
|
||||
if($activeCharacter = $this->getCharacter(0)){
|
||||
if($activeCharacter = $this->getCharacter()){
|
||||
$user = $activeCharacter->getUser();
|
||||
|
||||
// captcha is send -> check captcha -------------------------------------------
|
||||
@@ -329,7 +328,7 @@ class User extends Controller\Controller{
|
||||
|
||||
// character config -----------------------------------------------------------
|
||||
if(isset($formData['character'])){
|
||||
$activeCharacter->logLocation = (int)$formData['logLocation'];
|
||||
$activeCharacter->copyfrom($formData, ['logLocation', 'selectLocation']);
|
||||
|
||||
$activeCharacter->save();
|
||||
}
|
||||
@@ -371,7 +370,7 @@ class User extends Controller\Controller{
|
||||
!empty($data['captcha']) &&
|
||||
$data['captcha'] === $captcha
|
||||
){
|
||||
$activeCharacter = $this->getCharacter(0);
|
||||
$activeCharacter = $this->getCharacter();
|
||||
$user = $activeCharacter->getUser();
|
||||
|
||||
if($user){
|
||||
|
||||
@@ -1,17 +1,18 @@
|
||||
<?php
|
||||
<?php /** @noinspection PhpUndefinedMethodInspection */
|
||||
|
||||
/**
|
||||
* Created by PhpStorm.
|
||||
* User: Exodus
|
||||
* Date: 23.01.2016
|
||||
* Time: 17:18
|
||||
*
|
||||
* Handles access to EVE-Online "ESI API" and "SSO" auth functions
|
||||
* Handles access to EVE-Online "ESI API" and "SSO" oAuth 2.0 functions
|
||||
* - Add your API credentials in "environment.ini"
|
||||
* - Check "PATHFINDER.API" in "pathfinder.ini" for correct API URLs
|
||||
* Hint: \Web::instance()->request automatically caches responses by their response "Cache-Control" header!
|
||||
*/
|
||||
|
||||
namespace Controller\Ccp;
|
||||
|
||||
use Controller;
|
||||
use Controller\Api as Api;
|
||||
use Model;
|
||||
@@ -24,11 +25,6 @@ class Sso extends Api\User{
|
||||
*/
|
||||
const SSO_TIMEOUT = 4;
|
||||
|
||||
/**
|
||||
* @var int expire time (seconds) for an valid "accessToken"
|
||||
*/
|
||||
const ACCESS_KEY_EXPIRE_TIME = 20 * 60;
|
||||
|
||||
// SSO specific session keys
|
||||
const SESSION_KEY_SSO = 'SESSION.SSO';
|
||||
const SESSION_KEY_SSO_ERROR = 'SESSION.SSO.ERROR';
|
||||
@@ -45,8 +41,7 @@ class Sso extends Api\User{
|
||||
const ERROR_CHARACTER_DATA = 'Failed to load characterData from ESI';
|
||||
const ERROR_CHARACTER_FORBIDDEN = 'Character "%s" is not authorized to log in. Reason: %s';
|
||||
const ERROR_SERVICE_TIMEOUT = 'CCP SSO service timeout (%ss). Try again later';
|
||||
const ERROR_COOKIE_LOGIN = 'Login from Cookie failed. Please retry by CCP SSO';
|
||||
|
||||
const ERROR_COOKIE_LOGIN = 'Login from Cookie failed (data not found). Please retry by CCP SSO';
|
||||
|
||||
/**
|
||||
* redirect user to CCP SSO page and request authorization
|
||||
@@ -72,7 +67,7 @@ class Sso extends Api\User{
|
||||
|
||||
if(
|
||||
isset($params['characterId']) &&
|
||||
( $activeCharacter = $this->getCharacter(0) )
|
||||
( $activeCharacter = $this->getCharacter() )
|
||||
){
|
||||
// authentication restricted to a characterId -----------------------------------------------
|
||||
// restrict login to this characterId e.g. for character switch on map page
|
||||
@@ -104,9 +99,7 @@ class Sso extends Api\User{
|
||||
$character->hasUserCharacter() &&
|
||||
($character->isAuthorized() === 'OK')
|
||||
){
|
||||
$loginCheck = $this->loginByCharacter($character);
|
||||
|
||||
if($loginCheck){
|
||||
if($this->loginByCharacter($character)){
|
||||
// set "login" cookie
|
||||
$this->setLoginCookie($character);
|
||||
|
||||
@@ -132,7 +125,7 @@ class Sso extends Api\User{
|
||||
* @param array $scopes
|
||||
* @param string $rootAlias
|
||||
*/
|
||||
private function rerouteAuthorization(\Base $f3, $scopes = [], $rootAlias = 'login'){
|
||||
private function rerouteAuthorization(\Base $f3, array $scopes = [], string $rootAlias = 'login'){
|
||||
if( !empty( Controller\Controller::getEnvironmentData('CCP_SSO_CLIENT_ID') ) ){
|
||||
// used for "state" check between request and callback
|
||||
$state = bin2hex( openssl_random_pseudo_bytes(12) );
|
||||
@@ -146,7 +139,9 @@ class Sso extends Api\User{
|
||||
'state' => $state
|
||||
];
|
||||
|
||||
$ssoAuthUrl = self::getAuthorizationEndpoint() . '?' . http_build_query($urlParams, '', '&', PHP_QUERY_RFC3986 );
|
||||
$ssoAuthUrl = $f3->ssoClient()->getUrl();
|
||||
$ssoAuthUrl .= $f3->ssoClient()->getAuthorizationEndpointURI();
|
||||
$ssoAuthUrl .= '?' . http_build_query($urlParams, '', '&', PHP_QUERY_RFC3986 );
|
||||
|
||||
$f3->status(302);
|
||||
$f3->reroute($ssoAuthUrl);
|
||||
@@ -190,28 +185,26 @@ class Sso extends Api\User{
|
||||
|
||||
$accessData = $this->getSsoAccessData($getParams['code']);
|
||||
|
||||
if(
|
||||
isset($accessData->accessToken) &&
|
||||
isset($accessData->refreshToken)
|
||||
){
|
||||
if(isset($accessData->accessToken, $accessData->esiAccessTokenExpires, $accessData->refreshToken)){
|
||||
// login succeeded -> get basic character data for current login
|
||||
$verificationCharacterData = $this->verifyCharacterData($accessData->accessToken);
|
||||
|
||||
if( !is_null($verificationCharacterData)){
|
||||
if( !empty($verificationCharacterData) ){
|
||||
|
||||
// check if login is restricted to a characterID
|
||||
|
||||
// verification available data. Data is needed for "ownerHash" check
|
||||
|
||||
// get character data from ESI
|
||||
$characterData = $this->getCharacterData((int)$verificationCharacterData->CharacterID);
|
||||
$characterData = $this->getCharacterData((int)$verificationCharacterData['characterId']);
|
||||
|
||||
if( isset($characterData->character) ){
|
||||
// add "ownerHash" and SSO tokens
|
||||
$characterData->character['ownerHash'] = $verificationCharacterData->CharacterOwnerHash;
|
||||
$characterData->character['crestAccessToken'] = $accessData->accessToken;
|
||||
$characterData->character['crestRefreshToken'] = $accessData->refreshToken;
|
||||
$characterData->character['esiScopes'] = Lib\Util::convertScopesString($verificationCharacterData->Scopes);
|
||||
$characterData->character['ownerHash'] = $verificationCharacterData['characterOwnerHash'];
|
||||
$characterData->character['esiAccessToken'] = $accessData->accessToken;
|
||||
$characterData->character['esiAccessTokenExpires'] = $accessData->esiAccessTokenExpires;
|
||||
$characterData->character['esiRefreshToken'] = $accessData->refreshToken;
|
||||
$characterData->character['esiScopes'] = $verificationCharacterData['scopes'];
|
||||
|
||||
// add/update static character data
|
||||
$characterModel = $this->updateCharacter($characterData);
|
||||
@@ -253,9 +246,7 @@ class Sso extends Api\User{
|
||||
$characterModel = $userCharactersModel->getCharacter();
|
||||
|
||||
// login by character
|
||||
$loginCheck = $this->loginByCharacter($characterModel);
|
||||
|
||||
if($loginCheck){
|
||||
if($this->loginByCharacter($characterModel)){
|
||||
// set "login" cookie
|
||||
$this->setLoginCookie($characterModel);
|
||||
|
||||
@@ -306,7 +297,7 @@ class Sso extends Api\User{
|
||||
*/
|
||||
public function login(\Base $f3){
|
||||
$data = (array)$f3->get('GET');
|
||||
$cookieName = empty($data['cookie']) ? '' : $data['cookie'];
|
||||
$cookieName = (string)$data['cookie'];
|
||||
$character = null;
|
||||
|
||||
if( !empty($cookieName) ){
|
||||
@@ -319,17 +310,19 @@ class Sso extends Api\User{
|
||||
}
|
||||
}
|
||||
|
||||
if( is_object($character)){
|
||||
if(is_object($character)){
|
||||
// login by character
|
||||
$loginCheck = $this->loginByCharacter($character);
|
||||
if($loginCheck){
|
||||
if($this->loginByCharacter($character)){
|
||||
// route to "map"
|
||||
$f3->reroute(['map', ['*' => '']]);
|
||||
}else{
|
||||
$f3->set(self::SESSION_KEY_SSO_ERROR, sprintf(self::ERROR_LOGIN_FAILED, $character->name));
|
||||
}
|
||||
}else{
|
||||
$f3->set(self::SESSION_KEY_SSO_ERROR, self::ERROR_COOKIE_LOGIN);
|
||||
}
|
||||
|
||||
// on error -> route back to login form
|
||||
$f3->set(self::SESSION_KEY_SSO_ERROR, self::ERROR_COOKIE_LOGIN);
|
||||
$f3->reroute(['login']);
|
||||
}
|
||||
|
||||
@@ -341,7 +334,7 @@ class Sso extends Api\User{
|
||||
* @param bool $authCode
|
||||
* @return null|\stdClass
|
||||
*/
|
||||
public function getSsoAccessData($authCode){
|
||||
protected function getSsoAccessData($authCode){
|
||||
$accessData = null;
|
||||
|
||||
if( !empty($authCode) ){
|
||||
@@ -357,10 +350,10 @@ class Sso extends Api\User{
|
||||
|
||||
/**
|
||||
* verify authorization code, and get an "access_token" data
|
||||
* @param $authCode
|
||||
* @param string $authCode
|
||||
* @return \stdClass
|
||||
*/
|
||||
protected function verifyAuthorizationCode($authCode){
|
||||
protected function verifyAuthorizationCode(string $authCode){
|
||||
$requestParams = [
|
||||
'grant_type' => 'authorization_code',
|
||||
'code' => $authCode
|
||||
@@ -372,10 +365,10 @@ class Sso extends Api\User{
|
||||
/**
|
||||
* get new "access_token" by an existing "refresh_token"
|
||||
* -> if "access_token" is expired, this function gets a fresh one
|
||||
* @param $refreshToken
|
||||
* @param string $refreshToken
|
||||
* @return \stdClass
|
||||
*/
|
||||
public function refreshAccessToken($refreshToken){
|
||||
public function refreshAccessToken(string $refreshToken){
|
||||
$requestParams = [
|
||||
'grant_type' => 'refresh_token',
|
||||
'refresh_token' => $refreshToken
|
||||
@@ -388,61 +381,42 @@ class Sso extends Api\User{
|
||||
* request an "access_token" AND "refresh_token" data
|
||||
* -> this can either be done by sending a valid "authorization code"
|
||||
* OR by providing a valid "refresh_token"
|
||||
* @param $requestParams
|
||||
* @param array $requestParams
|
||||
* @return \stdClass
|
||||
*/
|
||||
protected function requestAccessData($requestParams){
|
||||
$verifyAuthCodeUrl = self::getVerifyAuthorizationCodeEndpoint();
|
||||
$verifyAuthCodeUrlParts = parse_url($verifyAuthCodeUrl);
|
||||
|
||||
protected function requestAccessData(array $requestParams) : \stdClass {
|
||||
$accessData = (object) [];
|
||||
$accessData->accessToken = null;
|
||||
$accessData->refreshToken = null;
|
||||
$accessData->esiAccessTokenExpires = 0;
|
||||
|
||||
if($verifyAuthCodeUrlParts){
|
||||
$contentType = 'application/x-www-form-urlencoded';
|
||||
$requestOptions = [
|
||||
'timeout' => self::SSO_TIMEOUT,
|
||||
'method' => 'POST',
|
||||
'user_agent' => $this->getUserAgent(),
|
||||
'header' => [
|
||||
'Authorization: Basic ' . $this->getAuthorizationHeader(),
|
||||
'Content-Type: ' . $contentType,
|
||||
'Host: ' . $verifyAuthCodeUrlParts['host']
|
||||
]
|
||||
];
|
||||
$authCodeRequestData = $this->getF3()->ssoClient()->getAccessData($this->getAuthorizationData(), $requestParams);
|
||||
|
||||
// content (parameters to send with)
|
||||
$requestOptions['content'] = http_build_query($requestParams);
|
||||
if( !empty($authCodeRequestData) ){
|
||||
if( !empty($authCodeRequestData['accessToken']) ){
|
||||
// accessToken is required for endpoints that require Auth
|
||||
$accessData->accessToken = $authCodeRequestData['accessToken'];
|
||||
}
|
||||
|
||||
$apiResponse = Lib\Web::instance()->request($verifyAuthCodeUrl, $requestOptions);
|
||||
if( !empty($authCodeRequestData['expiresIn']) ){
|
||||
// expire time for accessToken
|
||||
try{
|
||||
$timezone = $this->getF3()->get('getTimeZone')();
|
||||
$accessTokenExpires = new \DateTime('now', $timezone);
|
||||
$accessTokenExpires->add(new \DateInterval('PT' . (int)$authCodeRequestData['expiresIn'] . 'S'));
|
||||
|
||||
if($apiResponse['body']){
|
||||
$authCodeRequestData = json_decode($apiResponse['body'], true);
|
||||
|
||||
if( !empty($authCodeRequestData) ){
|
||||
if( isset($authCodeRequestData['access_token']) ){
|
||||
// this token is required for endpoints that require Auth
|
||||
$accessData->accessToken = $authCodeRequestData['access_token'];
|
||||
}
|
||||
|
||||
if(isset($authCodeRequestData['refresh_token'])){
|
||||
// this token is used to refresh/get a new access_token when expires
|
||||
$accessData->refreshToken = $authCodeRequestData['refresh_token'];
|
||||
}
|
||||
$accessData->esiAccessTokenExpires = $accessTokenExpires->format('Y-m-d H:i:s');
|
||||
}catch(\Exception $e){
|
||||
$this->getF3()->error(500, $e->getMessage(), $e->getTrace());
|
||||
}
|
||||
}else{
|
||||
self::getSSOLogger()->write(
|
||||
sprintf(
|
||||
self::ERROR_ACCESS_TOKEN,
|
||||
print_r($requestParams, true)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
if( !empty($authCodeRequestData['refreshToken']) ){
|
||||
// this token is used to refresh/get a new access_token when expires
|
||||
$accessData->refreshToken = $authCodeRequestData['refreshToken'];
|
||||
}
|
||||
}else{
|
||||
self::getSSOLogger()->write(
|
||||
sprintf(self::ERROR_CCP_SSO_URL, __METHOD__)
|
||||
);
|
||||
self::getSSOLogger()->write(sprintf(self::ERROR_ACCESS_TOKEN, print_r($requestParams, true)));
|
||||
}
|
||||
|
||||
return $accessData;
|
||||
@@ -452,34 +426,17 @@ class Sso extends Api\User{
|
||||
* verify character data by "access_token"
|
||||
* -> get some basic information (like character id)
|
||||
* -> if more character information is required, use ESI "characters" endpoints request instead
|
||||
* @param $accessToken
|
||||
* @return mixed|null
|
||||
* @param string $accessToken
|
||||
* @return array
|
||||
*/
|
||||
public function verifyCharacterData($accessToken){
|
||||
$verifyUserUrl = self::getVerifyUserEndpoint();
|
||||
$verifyUrlParts = parse_url($verifyUserUrl);
|
||||
$characterData = null;
|
||||
public function verifyCharacterData(string $accessToken) : array {
|
||||
$characterData = $this->getF3()->ssoClient()->getVerifyCharacterData($accessToken);
|
||||
|
||||
if($verifyUrlParts){
|
||||
$requestOptions = [
|
||||
'timeout' => self::SSO_TIMEOUT,
|
||||
'method' => 'GET',
|
||||
'user_agent' => $this->getUserAgent(),
|
||||
'header' => [
|
||||
'Authorization: Bearer ' . $accessToken,
|
||||
'Host: ' . $verifyUrlParts['host']
|
||||
]
|
||||
];
|
||||
|
||||
$apiResponse = Lib\Web::instance()->request($verifyUserUrl, $requestOptions);
|
||||
|
||||
if($apiResponse['body']){
|
||||
$characterData = json_decode($apiResponse['body']);
|
||||
}else{
|
||||
self::getSSOLogger()->write(sprintf(self::ERROR_VERIFY_CHARACTER, __METHOD__));
|
||||
}
|
||||
if( !empty($characterData) ){
|
||||
// convert string with scopes to array
|
||||
$characterData['scopes'] = Lib\Util::convertScopesString($characterData['scopes']);
|
||||
}else{
|
||||
self::getSSOLogger()->write(sprintf(self::ERROR_CCP_SSO_URL, __METHOD__));
|
||||
self::getSSOLogger()->write(sprintf(self::ERROR_VERIFY_CHARACTER, __METHOD__));
|
||||
}
|
||||
|
||||
return $characterData;
|
||||
@@ -495,7 +452,7 @@ class Sso extends Api\User{
|
||||
$characterData = (object) [];
|
||||
|
||||
if($characterId){
|
||||
$characterDataBasic = $this->getF3()->ccpClient->getCharacterData($characterId);
|
||||
$characterDataBasic = $this->getF3()->ccpClient()->getCharacterData($characterId);
|
||||
|
||||
if( !empty($characterDataBasic) ){
|
||||
// remove some "unwanted" data -> not relevant for Pathfinder
|
||||
@@ -549,7 +506,7 @@ class Sso extends Api\User{
|
||||
$character = Model\BasicModel::getNew('CharacterModel');
|
||||
$character->getById((int)$characterData->character['id'], 0);
|
||||
$character->copyfrom($characterData->character, [
|
||||
'id', 'name', 'ownerHash', 'crestAccessToken', 'crestRefreshToken', 'esiScopes', 'securityStatus'
|
||||
'id', 'name', 'ownerHash', 'esiAccessToken', 'esiAccessTokenExpires', 'esiRefreshToken', 'esiScopes', 'securityStatus'
|
||||
]);
|
||||
|
||||
$character->corporationId = $characterData->corporation;
|
||||
@@ -561,15 +518,16 @@ class Sso extends Api\User{
|
||||
}
|
||||
|
||||
/**
|
||||
* get "Authorization:" Header data
|
||||
* get data for HTTP "Authorization:" Header
|
||||
* -> This header is required for any Auth-required endpoints!
|
||||
* @return string
|
||||
* @return array
|
||||
*/
|
||||
protected function getAuthorizationHeader(){
|
||||
return base64_encode(
|
||||
Controller\Controller::getEnvironmentData('CCP_SSO_CLIENT_ID') . ':'
|
||||
. Controller\Controller::getEnvironmentData('CCP_SSO_SECRET_KEY')
|
||||
);
|
||||
protected function getAuthorizationData() : array {
|
||||
return [
|
||||
Controller\Controller::getEnvironmentData('CCP_SSO_CLIENT_ID'),
|
||||
Controller\Controller::getEnvironmentData('CCP_SSO_SECRET_KEY'),
|
||||
'basic'
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -577,7 +535,7 @@ class Sso extends Api\User{
|
||||
* -> throw error if url is broken/missing
|
||||
* @return string
|
||||
*/
|
||||
static function getSsoUrlRoot(){
|
||||
static function getSsoUrlRoot() : string {
|
||||
$url = '';
|
||||
if( \Audit::instance()->url(self::getEnvironmentData('CCP_SSO_URL')) ){
|
||||
$url = self::getEnvironmentData('CCP_SSO_URL');
|
||||
@@ -590,23 +548,11 @@ class Sso extends Api\User{
|
||||
return $url;
|
||||
}
|
||||
|
||||
static function getAuthorizationEndpoint(){
|
||||
return self::getSsoUrlRoot() . '/oauth/authorize';
|
||||
}
|
||||
|
||||
static function getVerifyAuthorizationCodeEndpoint(){
|
||||
return self::getSsoUrlRoot() . '/oauth/token';
|
||||
}
|
||||
|
||||
static function getVerifyUserEndpoint(){
|
||||
return self::getSsoUrlRoot() . '/oauth/verify';
|
||||
}
|
||||
|
||||
/**
|
||||
* get logger for SSO logging
|
||||
* @return \Log
|
||||
*/
|
||||
static function getSSOLogger(){
|
||||
static function getSSOLogger() : \Log {
|
||||
return parent::getLogger('SSO');
|
||||
}
|
||||
}
|
||||
@@ -22,7 +22,7 @@ class Universe extends Controller {
|
||||
$regionsWhitelist = [
|
||||
10000002 // The Forge (13 constellations -> 93 systems)
|
||||
];
|
||||
$regionIds = $f3->ccpClient->getUniverseRegions();
|
||||
$regionIds = $f3->ccpClient()->getUniverseRegions();
|
||||
$regionIds = array_intersect ($regionsWhitelist, $regionIds);
|
||||
|
||||
$region = Model\Universe\BasicUniverseModel::getNew('RegionModel');
|
||||
@@ -43,7 +43,7 @@ class Universe extends Controller {
|
||||
$constellationsWhitelist = [
|
||||
20000014 // Mal (11 systems)
|
||||
];
|
||||
$constellationIds = $f3->ccpClient->getUniverseConstellations();
|
||||
$constellationIds = $f3->ccpClient()->getUniverseConstellations();
|
||||
$constellationIds = array_intersect ($constellationsWhitelist, $constellationIds);
|
||||
$constellation = Model\Universe\BasicUniverseModel::getNew('ConstellationModel');
|
||||
foreach($constellationIds as $constellationId){
|
||||
@@ -92,7 +92,7 @@ class Universe extends Controller {
|
||||
*/
|
||||
protected function setupCategories(array $categoriesWhitelist = []){
|
||||
$return = [];
|
||||
$categoryIds = $this->getF3()->ccpClient->getUniverseCategories();
|
||||
$categoryIds = $this->getF3()->ccpClient()->getUniverseCategories();
|
||||
$categoryIds = array_intersect ($categoriesWhitelist, $categoryIds);
|
||||
foreach($categoryIds as $categoryId){
|
||||
$return[$categoryId] = $this->setupCategory($categoryId);
|
||||
@@ -112,7 +112,7 @@ class Universe extends Controller {
|
||||
*/
|
||||
protected function setupGroups(array $groupsWhitelist = []){
|
||||
$return = [];
|
||||
$groupIds = $this->getF3()->ccpClient->getUniverseGroups();
|
||||
$groupIds = $this->getF3()->ccpClient()->getUniverseGroups();
|
||||
$groupIds = array_intersect ($groupsWhitelist, $groupIds);
|
||||
/**
|
||||
* @var $group Model\Universe\GroupModel
|
||||
@@ -288,13 +288,13 @@ class Universe extends Controller {
|
||||
$f3 = \Base::instance();
|
||||
$universeNameData = [];
|
||||
if( !empty($categories) && !empty($search)){
|
||||
$universeIds = $f3->ccpClient->search($categories, $search, $strict);
|
||||
$universeIds = $f3->ccpClient()->search($categories, $search, $strict);
|
||||
if(isset($universeIds['error'])){
|
||||
// ESI error
|
||||
$universeNameData = $universeIds;
|
||||
}elseif( !empty($universeIds) ){
|
||||
$universeIds = Util::arrayFlattenByValue($universeIds);
|
||||
$universeNameData = $f3->ccpClient->getUniverseNamesData($universeIds);
|
||||
$universeNameData = $f3->ccpClient()->getUniverseNamesData($universeIds);
|
||||
}
|
||||
}
|
||||
return $universeNameData;
|
||||
|
||||
@@ -10,6 +10,7 @@ namespace Controller;
|
||||
|
||||
use Controller\Api as Api;
|
||||
use Exception\PathfinderException;
|
||||
use lib\api\CcpClient;
|
||||
use lib\Config;
|
||||
use lib\Resource;
|
||||
use lib\Monolog;
|
||||
@@ -126,31 +127,30 @@ class Controller {
|
||||
protected function initSession(\Base $f3){
|
||||
$session = null;
|
||||
|
||||
/**
|
||||
* callback() for suspect sessions
|
||||
* @param $session
|
||||
* @param $sid
|
||||
* @return bool
|
||||
*/
|
||||
$onSuspect = function($session, $sid){
|
||||
self::getLogger('SESSION_SUSPECT')->write( sprintf(
|
||||
self::ERROR_SESSION_SUSPECT,
|
||||
$sid,
|
||||
$session->ip(),
|
||||
$session->agent()
|
||||
));
|
||||
// .. continue with default onSuspect() handler
|
||||
// -> destroy session
|
||||
return false;
|
||||
};
|
||||
|
||||
if(
|
||||
$f3->get('SESSION_CACHE') === 'mysql' &&
|
||||
$this->getDB('PF') instanceof DB\SQL
|
||||
){
|
||||
|
||||
if(!headers_sent() && session_status()!=PHP_SESSION_ACTIVE){
|
||||
$session = new DB\SQL\Session($this->getDB('PF'), 'sessions', true, $onSuspect);
|
||||
/**
|
||||
* callback() for suspect sessions
|
||||
* @param $session
|
||||
* @param $sid
|
||||
* @return bool
|
||||
*/
|
||||
$onSuspect = function($session, $sid){
|
||||
self::getLogger('SESSION_SUSPECT')->write( sprintf(
|
||||
self::ERROR_SESSION_SUSPECT,
|
||||
$sid,
|
||||
$session->ip(),
|
||||
$session->agent()
|
||||
));
|
||||
// .. continue with default onSuspect() handler
|
||||
// -> destroy session
|
||||
return false;
|
||||
};
|
||||
|
||||
new DB\SQL\MySQL\Session($this->getDB('PF'), 'sessions', true, $onSuspect);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -212,7 +212,7 @@ class Controller {
|
||||
$data[$name] = $value;
|
||||
}
|
||||
}
|
||||
}elseif( isset($cookieData[$cookieName]) ){
|
||||
}elseif(isset($cookieData[$cookieName])){
|
||||
// look for a single cookie
|
||||
$data[$cookieName] = $cookieData[$cookieName];
|
||||
}
|
||||
@@ -539,36 +539,90 @@ class Controller {
|
||||
/**
|
||||
* get EVE server status from ESI
|
||||
* @param \Base $f3
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function getEveServerStatus(\Base $f3){
|
||||
$esiStatusVersion = 'latest';
|
||||
$cacheKey = 'eve_server_status';
|
||||
|
||||
if( !$f3->exists($cacheKey, $return) ){
|
||||
$return = (object) [];
|
||||
$return->error = [];
|
||||
$return->status = [
|
||||
'serverName' => strtoupper( self::getEnvironmentData('CCP_ESI_DATASOURCE') ),
|
||||
'serviceStatus' => 'offline'
|
||||
];
|
||||
|
||||
$response = $f3->ccpClient->getServerStatus();
|
||||
/**
|
||||
* @var $client CcpClient
|
||||
*/
|
||||
if($client = $f3->ccpClient()){
|
||||
$return->server = [
|
||||
'name' => strtoupper(self::getEnvironmentData('CCP_ESI_DATASOURCE')),
|
||||
'status' => 'offline',
|
||||
'statusColor' => 'red',
|
||||
];
|
||||
$return->api = [
|
||||
'name' => 'ESI API',
|
||||
'status' => 'offline',
|
||||
'statusColor' => 'red',
|
||||
'url' => $client->getUrl(),
|
||||
'timeout' => $client->getTimeout(),
|
||||
'connectTimeout' => $client->getConnectTimeout(),
|
||||
'readTimeout' => $client->getReadTimeout(),
|
||||
'proxy' => ($proxy = $client->getProxy()) ? : 'false',
|
||||
'verify' => $client->getVerify(),
|
||||
'debug' => $client->getDebugRequests(),
|
||||
'dataSource' => $client->getDataSource(),
|
||||
'statusVersion' => $esiStatusVersion,
|
||||
'routes' => []
|
||||
];
|
||||
|
||||
if( !empty($response) ){
|
||||
// calculate time diff since last server restart
|
||||
$timezone = $f3->get('getTimeZone')();
|
||||
$dateNow = new \DateTime('now', $timezone);
|
||||
$dateServerStart = new \DateTime($response['startTime']);
|
||||
$interval = $dateNow->diff($dateServerStart);
|
||||
$startTimestampFormat = $interval->format('%hh %im');
|
||||
if($interval->days > 0){
|
||||
$startTimestampFormat = $interval->days . 'd ' . $startTimestampFormat;
|
||||
$serverStatus = $client->getServerStatus();
|
||||
if( !isset($serverStatus['error']) ){
|
||||
$statusData = $serverStatus['status'];
|
||||
// calculate time diff since last server restart
|
||||
$timezone = $f3->get('getTimeZone')();
|
||||
$dateNow = new \DateTime('now', $timezone);
|
||||
$dateServerStart = new \DateTime($statusData['startTime']);
|
||||
$interval = $dateNow->diff($dateServerStart);
|
||||
$startTimestampFormat = $interval->format('%hh %im');
|
||||
if($interval->days > 0){
|
||||
$startTimestampFormat = $interval->days . 'd ' . $startTimestampFormat;
|
||||
}
|
||||
|
||||
$statusData['name'] = $return->server['name'];
|
||||
$statusData['status'] = 'online';
|
||||
$statusData['statusColor'] = 'green';
|
||||
$statusData['startTime'] = $startTimestampFormat;
|
||||
$return->server = $statusData;
|
||||
}else{
|
||||
$return->error[] = (new PathfinderException($serverStatus['error'], 500))->getError();
|
||||
}
|
||||
|
||||
$response['serverName'] = strtoupper( self::getEnvironmentData('CCP_ESI_DATASOURCE') );
|
||||
$response['serviceStatus'] = 'online';
|
||||
$response['startTime'] = $startTimestampFormat;
|
||||
$return->status = $response;
|
||||
$apiStatus = $client->getStatusForRoutes('latest');
|
||||
if( !isset($apiStatus['error']) ){
|
||||
// find top status
|
||||
$status = 'OK';
|
||||
$color = 'green';
|
||||
foreach($apiStatus['status'] as $statusData){
|
||||
if('red' == $statusData['status']){
|
||||
$status = 'unstable';
|
||||
$color = $statusData['status'];
|
||||
break;
|
||||
}
|
||||
if('yellow' == $statusData['status']){
|
||||
$status = 'degraded';
|
||||
$color = $statusData['status'];
|
||||
}
|
||||
}
|
||||
|
||||
$f3->set($cacheKey, $return, 60);
|
||||
$return->api['status'] = $status;
|
||||
$return->api['statusColor'] = $color;
|
||||
$return->api['routes'] = $apiStatus['status'];
|
||||
}else{
|
||||
$return->error[] = (new PathfinderException($apiStatus['error'], 500))->getError();
|
||||
}
|
||||
|
||||
if(empty($return->error)){
|
||||
$f3->set($cacheKey, $return, 60);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -795,9 +849,8 @@ class Controller {
|
||||
* @param string $authType
|
||||
* @return array
|
||||
*/
|
||||
static function getScopesByAuthType($authType = ''){
|
||||
static function getScopesByAuthType(string $authType = '') : array {
|
||||
$scopes = array_filter((array)self::getEnvironmentData('CCP_ESI_SCOPES'));
|
||||
|
||||
switch($authType){
|
||||
case 'admin':
|
||||
$scopesAdmin = array_filter((array)self::getEnvironmentData('CCP_ESI_SCOPES_ADMIN'));
|
||||
@@ -896,9 +949,9 @@ class Controller {
|
||||
* get a Logger object by Hive key
|
||||
* -> set in pathfinder.ini
|
||||
* @param string $type
|
||||
* @return \Log|null
|
||||
* @return \Log
|
||||
*/
|
||||
static function getLogger($type){
|
||||
static function getLogger($type = 'DEBUG') : \Log {
|
||||
return LogController::getLogger($type);
|
||||
}
|
||||
|
||||
|
||||
@@ -162,9 +162,9 @@ class LogController extends \Prefab {
|
||||
/**
|
||||
* get Logger instance
|
||||
* @param string $type
|
||||
* @return \Log|null
|
||||
* @return \Log
|
||||
*/
|
||||
public static function getLogger($type){
|
||||
public static function getLogger(string $type) : \Log {
|
||||
$logFiles = Config::getPathfinderData('logfiles');
|
||||
|
||||
$logFileName = empty($logFiles[$type]) ? 'error' : $logFiles[$type];
|
||||
|
||||
@@ -167,9 +167,6 @@ class Setup extends Controller {
|
||||
// js view (file)
|
||||
$f3->set('tplJsView', 'setup');
|
||||
|
||||
// set render functions (called within template)
|
||||
$f3->set('cacheType', $this->getCacheType($f3));
|
||||
|
||||
// simple counter (called within template)
|
||||
$counter = [];
|
||||
$f3->set('tplCounter', function(string $action = 'increment', string $type = 'default', $val = 0) use (&$counter){
|
||||
@@ -187,19 +184,6 @@ class Setup extends Controller {
|
||||
echo \Template::instance()->render( Config::getPathfinderData('view.index') );
|
||||
}
|
||||
|
||||
/**
|
||||
* get Cache backend type for F3
|
||||
* @param \Base $f3
|
||||
* @return string
|
||||
*/
|
||||
protected function getCacheType(\Base &$f3) : string {
|
||||
$cacheType = $f3->get('CACHE');
|
||||
if(strpos($cacheType, 'redis') !== false){
|
||||
$cacheType = 'redis';
|
||||
}
|
||||
return $cacheType;
|
||||
}
|
||||
|
||||
/**
|
||||
* main setup route handler
|
||||
* works as dispatcher for setup functions
|
||||
@@ -229,47 +213,63 @@ class Setup extends Controller {
|
||||
case 'exportTable':
|
||||
$this->exportTable($params['model']);
|
||||
break;
|
||||
case 'clearCache':
|
||||
$this->clearCache($f3);
|
||||
case 'clearFiles':
|
||||
$this->clearFiles((string)$params['path']);
|
||||
break;
|
||||
case 'flushRedisDb':
|
||||
$this->flushRedisDb((string)$params['host'], (int)$params['port'], (int)$params['db']);
|
||||
break;
|
||||
case 'invalidateCookies':
|
||||
$this->invalidateCookies($f3);
|
||||
break;
|
||||
}
|
||||
|
||||
// set template data ----------------------------------------------------------------
|
||||
// set environment information
|
||||
$f3->set('environmentInformation', $this->getEnvironmentInformation($f3));
|
||||
// ============================================================================================================
|
||||
// Template data
|
||||
// ============================================================================================================
|
||||
|
||||
// set server information
|
||||
// Server -----------------------------------------------------------------------------------------------------
|
||||
// Server information
|
||||
$f3->set('serverInformation', $this->getServerInformation($f3));
|
||||
|
||||
// set requirement check information
|
||||
$f3->set('checkRequirements', $this->checkRequirements($f3));
|
||||
// Pathfinder directory config
|
||||
$f3->set('directoryConfig', $this->getDirectoryConfig($f3));
|
||||
|
||||
// set php config check information
|
||||
$f3->set('checkPHPConfig', $this->checkPHPConfig($f3));
|
||||
|
||||
// set system config check information
|
||||
// Server environment variables
|
||||
$f3->set('checkSystemConfig', $this->checkSystemConfig($f3));
|
||||
|
||||
// set map default config
|
||||
// Environment ------------------------------------------------------------------------------------------------
|
||||
// Server requirement
|
||||
$f3->set('checkRequirements', $this->checkRequirements($f3));
|
||||
|
||||
// PHP config
|
||||
$f3->set('checkPHPConfig', $this->checkPHPConfig($f3));
|
||||
|
||||
// Settings ---------------------------------------------------------------------------------------------------
|
||||
// Pathfinder environment config
|
||||
$f3->set('environmentInformation', $this->getEnvironmentInformation($f3));
|
||||
|
||||
// Pathfinder map default config
|
||||
$f3->set('mapsDefaultConfig', $this->getMapsDefaultConfig($f3));
|
||||
|
||||
// set database connection information
|
||||
// Database ---------------------------------------------------------------------------------------------------
|
||||
// Database config
|
||||
$f3->set('checkDatabase', $this->checkDatabase($f3, $fixColumns));
|
||||
|
||||
// set socket information
|
||||
// Redis ------------------------------------------------------------------------------------------------------
|
||||
// Redis information
|
||||
$f3->set('checkRedisInformation', $this->checkRedisInformation($f3));
|
||||
|
||||
// Socket -----------------------------------------------------------------------------------------------------
|
||||
// WebSocket information
|
||||
$f3->set('socketInformation', $this->getSocketInformation());
|
||||
|
||||
// set index information
|
||||
// Administration ---------------------------------------------------------------------------------------------
|
||||
// Index information
|
||||
$f3->set('indexInformation', $this->getIndexData($f3));
|
||||
|
||||
// set cache size
|
||||
$f3->set('cacheSize', $this->getCacheData($f3));
|
||||
|
||||
// set Redis config check information
|
||||
$f3->set('checkRedisConfig', $this->checkRedisConfig($f3));
|
||||
// Filesystem (cache) size
|
||||
$f3->set('checkDirSize', $this->checkDirSize($f3));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -290,6 +290,9 @@ class Setup extends Controller {
|
||||
'database' => [
|
||||
'icon' => 'fa-database'
|
||||
],
|
||||
'cache' => [
|
||||
'icon' => 'fa-hdd'
|
||||
],
|
||||
'socket' => [
|
||||
'icon' => 'fa-exchange-alt'
|
||||
],
|
||||
@@ -306,7 +309,7 @@ class Setup extends Controller {
|
||||
* @param \Base $f3
|
||||
* @return array
|
||||
*/
|
||||
protected function getEnvironmentInformation(\Base $f3){
|
||||
protected function getEnvironmentInformation(\Base $f3) : array {
|
||||
$environmentData = [];
|
||||
// exclude some sensitive data (e.g. database, passwords)
|
||||
$excludeVars = [
|
||||
@@ -346,7 +349,7 @@ class Setup extends Controller {
|
||||
* @param \Base $f3
|
||||
* @return array
|
||||
*/
|
||||
protected function getServerInformation(\Base $f3){
|
||||
protected function getServerInformation(\Base $f3) : array {
|
||||
$serverInfo = [
|
||||
'time' => [
|
||||
'label' => 'Time',
|
||||
@@ -389,15 +392,90 @@ class Setup extends Controller {
|
||||
return $serverInfo;
|
||||
}
|
||||
|
||||
/**
|
||||
* get information for used directories
|
||||
* @param \Base $f3
|
||||
* @return array
|
||||
*/
|
||||
protected function getDirectoryConfig(\Base $f3) : array {
|
||||
$directoryData = [
|
||||
'TEMP' => [
|
||||
'label' => 'TEMP',
|
||||
'value' => $f3->get('TEMP'),
|
||||
'check' => true,
|
||||
'tooltip' => 'Temporary folder for pre compiled templates.',
|
||||
'chmod' => Util::filesystemInfo($f3->get('TEMP'))['chmod']
|
||||
],
|
||||
'CACHE' => [
|
||||
'label' => 'CACHE',
|
||||
'value' => $f3->get('CACHE'),
|
||||
'check' => true,
|
||||
'tooltip' => 'Cache backend. Support for Redis, Memcache, APC, WinCache, XCache and a filesystem-based (default) cache.',
|
||||
'chmod' => ((Config::parseDSN($f3->get('CACHE'), $confCache)) && $confCache['type'] == 'folder') ?
|
||||
Util::filesystemInfo((string)$confCache['folder'])['chmod'] : ''
|
||||
],
|
||||
'API_CACHE' => [
|
||||
'label' => 'API_CACHE',
|
||||
'value' => $f3->get('API_CACHE'),
|
||||
'check' => true,
|
||||
'tooltip' => 'Cache backend for API related cache data. Support for Redis and a filesystem-based (default) cache.',
|
||||
'chmod' => ((Config::parseDSN($f3->get('API_CACHE'), $confCacheApi)) && $confCacheApi['type'] == 'folder') ?
|
||||
Util::filesystemInfo((string)$confCacheApi['folder'])['chmod'] : ''
|
||||
],
|
||||
'LOGS' => [
|
||||
'label' => 'LOGS',
|
||||
'value' => $f3->get('LOGS'),
|
||||
'check' => true,
|
||||
'tooltip' => 'Folder for pathfinder logs (e.g. cronjob-, error-logs, ...).',
|
||||
'chmod' => Util::filesystemInfo($f3->get('LOGS'))['chmod']
|
||||
],
|
||||
'UI' => [
|
||||
'label' => 'UI',
|
||||
'value' => $f3->get('UI'),
|
||||
'check' => true,
|
||||
'tooltip' => 'Folder for public accessible resources (templates, js, css, images,..).',
|
||||
'chmod' => Util::filesystemInfo($f3->get('UI'))['chmod']
|
||||
],
|
||||
'AUTOLOAD' => [
|
||||
'label' => 'AUTOLOAD',
|
||||
'value' => $f3->get('AUTOLOAD'),
|
||||
'check' => true,
|
||||
'tooltip' => 'Autoload folder for PHP files.',
|
||||
'chmod' => Util::filesystemInfo($f3->get('AUTOLOAD'))['chmod']
|
||||
],
|
||||
'FAVICON' => [
|
||||
'label' => 'FAVICON',
|
||||
'value' => $f3->get('FAVICON'),
|
||||
'check' => true,
|
||||
'tooltip' => 'Folder for Favicons.',
|
||||
'chmod' => Util::filesystemInfo($f3->get('FAVICON'))['chmod']
|
||||
],
|
||||
'HISTORY' => [
|
||||
'label' => 'HISTORY [optional]',
|
||||
'value' => Config::getPathfinderData('history.log'),
|
||||
'check' => true,
|
||||
'tooltip' => 'Folder for log history files. (e.g. change logs for maps).',
|
||||
'chmod' => Util::filesystemInfo(Config::getPathfinderData('history.log'))['chmod']
|
||||
],
|
||||
'CONFIG' => [
|
||||
'label' => 'CONFIG PATH [optional]',
|
||||
'value' => implode(' ', (array)$f3->get('CONF')),
|
||||
'check' => true,
|
||||
'tooltip' => 'Folder for custom *.ini files. (e.g. when overwriting of default values in app/*.ini)'
|
||||
]
|
||||
];
|
||||
|
||||
return $directoryData;
|
||||
}
|
||||
|
||||
/**
|
||||
* check all required backend requirements
|
||||
* (Fat Free Framework)
|
||||
* @param \Base $f3
|
||||
* @return array
|
||||
*/
|
||||
protected function checkRequirements(\Base $f3){
|
||||
protected function checkRequirements(\Base $f3) : array {
|
||||
|
||||
// server type ------------------------------------------------------------------
|
||||
$serverData = self::getServerData(0);
|
||||
|
||||
$checkRequirements = [
|
||||
@@ -516,7 +594,7 @@ class Setup extends Controller {
|
||||
$modNotFoundMsg = 'Module status can not be identified. '
|
||||
. 'This can happen if PHP runs as \'FastCGI\'. Please check manual! ';
|
||||
|
||||
// mod_rewrite check ------------------------------------------------------------
|
||||
// mod_rewrite check --------------------------------------------------------------------------------------
|
||||
$modRewriteCheck = false;
|
||||
$modRewriteVersion = 'disabled';
|
||||
$modRewriteTooltip = false;
|
||||
@@ -539,7 +617,7 @@ class Setup extends Controller {
|
||||
'tooltip' => $modRewriteTooltip
|
||||
];
|
||||
|
||||
// mod_headers check ------------------------------------------------------------
|
||||
// mod_headers check --------------------------------------------------------------------------------------
|
||||
$modHeadersCheck = false;
|
||||
$modHeadersVersion = 'disabled';
|
||||
$modHeadersTooltip = false;
|
||||
@@ -572,6 +650,11 @@ class Setup extends Controller {
|
||||
* @return array
|
||||
*/
|
||||
protected function checkPHPConfig(\Base $f3): array {
|
||||
$memoryLimit = (int)ini_get('memory_limit');
|
||||
$maxInputVars = (int)ini_get('max_input_vars');
|
||||
$maxExecutionTime = (int)ini_get('max_execution_time'); // 0 == infinite
|
||||
$htmlErrors = (int)ini_get('html_errors');
|
||||
|
||||
$phpConfig = [
|
||||
'exec' => [
|
||||
'label' => 'exec()',
|
||||
@@ -583,29 +666,29 @@ class Setup extends Controller {
|
||||
'memoryLimit' => [
|
||||
'label' => 'memory_limit',
|
||||
'required' => $f3->get('REQUIREMENTS.PHP.MEMORY_LIMIT'),
|
||||
'version' => ini_get('memory_limit'),
|
||||
'check' => ini_get('memory_limit') >= $f3->get('REQUIREMENTS.PHP.MEMORY_LIMIT'),
|
||||
'version' => $memoryLimit,
|
||||
'check' => $memoryLimit >= $f3->get('REQUIREMENTS.PHP.MEMORY_LIMIT'),
|
||||
'tooltip' => 'PHP default = 64MB.'
|
||||
],
|
||||
'maxInputVars' => [
|
||||
'label' => 'max_input_vars',
|
||||
'required' => $f3->get('REQUIREMENTS.PHP.MAX_INPUT_VARS'),
|
||||
'version' => ini_get('max_input_vars'),
|
||||
'check' => ini_get('max_input_vars') >= $f3->get('REQUIREMENTS.PHP.MAX_INPUT_VARS'),
|
||||
'version' => $maxInputVars,
|
||||
'check' => $maxInputVars >= $f3->get('REQUIREMENTS.PHP.MAX_INPUT_VARS'),
|
||||
'tooltip' => 'PHP default = 1000. Increase it in order to import larger maps.'
|
||||
],
|
||||
'maxExecutionTime' => [
|
||||
'label' => 'max_execution_time',
|
||||
'required' => $f3->get('REQUIREMENTS.PHP.MAX_EXECUTION_TIME'),
|
||||
'version' => ini_get('max_execution_time'),
|
||||
'check' => ini_get('max_execution_time') >= $f3->get('REQUIREMENTS.PHP.MAX_EXECUTION_TIME'),
|
||||
'version' => $maxExecutionTime,
|
||||
'check' => !$maxExecutionTime || $maxExecutionTime >= $f3->get('REQUIREMENTS.PHP.MAX_EXECUTION_TIME'),
|
||||
'tooltip' => 'PHP default = 30. Max execution time for PHP scripts.'
|
||||
],
|
||||
'htmlErrors' => [
|
||||
'label' => 'html_errors',
|
||||
'required' => $f3->get('REQUIREMENTS.PHP.HTML_ERRORS'),
|
||||
'version' => (int)ini_get('html_errors'),
|
||||
'check' => (bool)ini_get('html_errors') == (bool)$f3->get('REQUIREMENTS.PHP.HTML_ERRORS'),
|
||||
'version' => $htmlErrors,
|
||||
'check' => (bool)$htmlErrors == (bool)$f3->get('REQUIREMENTS.PHP.HTML_ERRORS'),
|
||||
'tooltip' => 'Formatted HTML StackTrace on error.'
|
||||
],
|
||||
[
|
||||
@@ -640,70 +723,209 @@ class Setup extends Controller {
|
||||
* @param \Base $f3
|
||||
* @return array
|
||||
*/
|
||||
protected function checkRedisConfig(\Base $f3): array {
|
||||
protected function checkRedisInformation(\Base $f3): array {
|
||||
$redisConfig = [];
|
||||
if($this->getCacheType($f3) === 'redis'){
|
||||
// we need to access the "protected" member $ref from F3´s Cache class
|
||||
// to get access to the underlying Redis() class
|
||||
$ref = new \ReflectionObject($cache = \Cache::instance());
|
||||
$prop = $ref->getProperty('ref');
|
||||
$prop->setAccessible(true);
|
||||
|
||||
if(
|
||||
extension_loaded('redis') &&
|
||||
class_exists('\Redis')
|
||||
){
|
||||
// collection of DSN specific $conf array (host, port, db,..)
|
||||
$dsnData = [];
|
||||
|
||||
/**
|
||||
* @var $redis \Redis
|
||||
* get client information for a Redis client
|
||||
* @param \Redis $client
|
||||
* @param array $conf
|
||||
* @return array
|
||||
*/
|
||||
$redis = $prop->getValue($cache);
|
||||
$getClientInfo = function(\Redis $client, array $conf) : array {
|
||||
$redisInfo = [
|
||||
'dsn' => [
|
||||
'label' => 'DNS',
|
||||
'value' => $conf['host'] . ':' . $conf['port']
|
||||
],
|
||||
'connected' => [
|
||||
'label' => 'status',
|
||||
'value' => $client->isConnected()
|
||||
]
|
||||
];
|
||||
|
||||
$redisServerInfo = (array)$redis->info('SERVER');
|
||||
$redisMemoryInfo = (array)$redis->info('MEMORY');
|
||||
$redisStatsInfo = (array)$redis->info('STATS');
|
||||
return $redisInfo;
|
||||
};
|
||||
|
||||
$redisConfig = [
|
||||
'redisVersion' => [
|
||||
'label' => 'redis_version',
|
||||
'required' => number_format((float)$f3->get('REQUIREMENTS.REDIS.VERSION'), 1, '.', ''),
|
||||
'version' => $redisServerInfo['redis_version'],
|
||||
'check' => version_compare( $redisServerInfo['redis_version'], $f3->get('REQUIREMENTS.REDIS.VERSION'), '>='),
|
||||
'tooltip' => 'Redis server version'
|
||||
],
|
||||
'maxMemory' => [
|
||||
'label' => 'maxmemory',
|
||||
'required' => $this->convertBytes($f3->get('REQUIREMENTS.REDIS.MAX_MEMORY')),
|
||||
'version' => $this->convertBytes($redisMemoryInfo['maxmemory']),
|
||||
'check' => $redisMemoryInfo['maxmemory'] >= $f3->get('REQUIREMENTS.REDIS.MAX_MEMORY'),
|
||||
'tooltip' => 'Max memory limit for Redis'
|
||||
],
|
||||
'usedMemory' => [
|
||||
'label' => 'used_memory',
|
||||
'version' => $this->convertBytes($redisMemoryInfo['used_memory']),
|
||||
'check' => $redisMemoryInfo['used_memory'] < $redisMemoryInfo['maxmemory'],
|
||||
'tooltip' => 'Current memory used by Redis'
|
||||
],
|
||||
'usedMemoryPeak' => [
|
||||
'label' => 'used_memory_peak',
|
||||
'version' => $this->convertBytes($redisMemoryInfo['used_memory_peak']),
|
||||
'check' => $redisMemoryInfo['used_memory_peak'] <= $redisMemoryInfo['maxmemory'],
|
||||
'tooltip' => 'Peak memory used by Redis'
|
||||
],
|
||||
'maxmemoryPolicy' => [
|
||||
'label' => 'maxmemory_policy',
|
||||
'required' => $f3->get('REQUIREMENTS.REDIS.MAXMEMORY_POLICY'),
|
||||
'version' => $redisMemoryInfo['maxmemory_policy'],
|
||||
'check' => $redisMemoryInfo['maxmemory_policy'] == $f3->get('REQUIREMENTS.REDIS.MAXMEMORY_POLICY'),
|
||||
'tooltip' => 'How Redis behaves if \'maxmemory\' limit reached'
|
||||
],
|
||||
'evictedKeys' => [
|
||||
'label' => 'evicted_keys',
|
||||
'version' => $redisStatsInfo['evicted_keys'],
|
||||
'check' => !(bool)$redisStatsInfo['evicted_keys'],
|
||||
'tooltip' => 'Number of evicted keys due to maxmemory limit'
|
||||
],
|
||||
'dbSize' . $redis->getDbNum() => [
|
||||
'label' => 'Size DB (' . $redis->getDbNum() . ')',
|
||||
'version' => $redis->dbSize(),
|
||||
'check' => $redis->dbSize() > 0,
|
||||
'tooltip' => 'Keys found in DB (' . $redis->getDbNum() . ') [Cache DB]'
|
||||
]
|
||||
/**
|
||||
* get status information for a Redis client
|
||||
* @param \Redis $client
|
||||
* @return array
|
||||
*/
|
||||
$getClientStats = function(\Redis $client) use ($f3) : array {
|
||||
$redisStats = [];
|
||||
|
||||
if($client->isConnected()){
|
||||
$redisServerInfo = (array)$client->info('SERVER');
|
||||
$redisMemoryInfo = (array)$client->info('MEMORY');
|
||||
$redisStatsInfo = (array)$client->info('STATS');
|
||||
|
||||
$redisStats = [
|
||||
'redisVersion' => [
|
||||
'label' => 'redis_version',
|
||||
'required' => number_format((float)$f3->get('REQUIREMENTS.REDIS.VERSION'), 1, '.', ''),
|
||||
'version' => $redisServerInfo['redis_version'],
|
||||
'check' => version_compare( $redisServerInfo['redis_version'], $f3->get('REQUIREMENTS.REDIS.VERSION'), '>='),
|
||||
'tooltip' => 'Redis server version'
|
||||
],
|
||||
'maxMemory' => [
|
||||
'label' => 'maxmemory',
|
||||
'required' => $this->convertBytes($f3->get('REQUIREMENTS.REDIS.MAX_MEMORY')),
|
||||
'version' => $this->convertBytes($redisMemoryInfo['maxmemory']),
|
||||
'check' => $redisMemoryInfo['maxmemory'] >= $f3->get('REQUIREMENTS.REDIS.MAX_MEMORY'),
|
||||
'tooltip' => 'Max memory limit for Redis'
|
||||
],
|
||||
'usedMemory' => [
|
||||
'label' => 'used_memory',
|
||||
'version' => $this->convertBytes($redisMemoryInfo['used_memory']),
|
||||
'check' => $redisMemoryInfo['used_memory'] < $redisMemoryInfo['maxmemory'],
|
||||
'tooltip' => 'Current memory used by Redis'
|
||||
],
|
||||
'usedMemoryPeak' => [
|
||||
'label' => 'used_memory_peak',
|
||||
'version' => $this->convertBytes($redisMemoryInfo['used_memory_peak']),
|
||||
'check' => $redisMemoryInfo['used_memory_peak'] <= $redisMemoryInfo['maxmemory'],
|
||||
'tooltip' => 'Peak memory used by Redis'
|
||||
],
|
||||
'maxmemoryPolicy' => [
|
||||
'label' => 'maxmemory_policy',
|
||||
'required' => $f3->get('REQUIREMENTS.REDIS.MAXMEMORY_POLICY'),
|
||||
'version' => $redisMemoryInfo['maxmemory_policy'],
|
||||
'check' => $redisMemoryInfo['maxmemory_policy'] == $f3->get('REQUIREMENTS.REDIS.MAXMEMORY_POLICY'),
|
||||
'tooltip' => 'How Redis behaves if \'maxmemory\' limit reached'
|
||||
],
|
||||
'evictedKeys' => [
|
||||
'label' => 'evicted_keys',
|
||||
'version' => $redisStatsInfo['evicted_keys'],
|
||||
'check' => !(bool)$redisStatsInfo['evicted_keys'],
|
||||
'tooltip' => 'Number of evicted keys due to maxmemory limit'
|
||||
],
|
||||
[
|
||||
'label' => 'Databases'
|
||||
]
|
||||
];
|
||||
}
|
||||
|
||||
return $redisStats;
|
||||
};
|
||||
|
||||
/**
|
||||
* get database status for current selected db
|
||||
* @param \Redis $client
|
||||
* @param string $tag
|
||||
* @return array
|
||||
*/
|
||||
$getDatabaseStatus = function(\Redis $client, string $tag) : array {
|
||||
$redisDatabases = [];
|
||||
if($client->isConnected()){
|
||||
$dbNum = $client->getDbNum();
|
||||
$dbSize = $client->dbSize();
|
||||
$redisDatabases = [
|
||||
'db_' . $dbNum => [
|
||||
'label' => '<i class="fas fa-fw fa-database"></i> db(' . $dbNum . ') : ' . $tag,
|
||||
'version' => $dbSize . ' keys',
|
||||
'check' => $dbSize > 0,
|
||||
'tooltip' => 'Keys in db(' . $dbNum . ')',
|
||||
'task' => [
|
||||
[
|
||||
'action' => http_build_query([
|
||||
'action' => 'flushRedisDb',
|
||||
'host' => $client->getHost(),
|
||||
'port' => $client->getPort(),
|
||||
'db' => $dbNum
|
||||
]) . '#pf-setup-cache',
|
||||
'label' => 'Flush',
|
||||
'icon' => 'fa-trash',
|
||||
'btn' => 'btn-danger' . (($dbSize > 0) ? '' : ' disabled')
|
||||
]
|
||||
]
|
||||
]
|
||||
];
|
||||
}
|
||||
|
||||
return $redisDatabases;
|
||||
};
|
||||
|
||||
/**
|
||||
* build (modify) $redisConfig with DNS $conf data
|
||||
* @param array $conf
|
||||
*/
|
||||
$buildRedisConfig = function(array $conf) use (&$redisConfig, $getClientInfo, $getClientStats, $getDatabaseStatus){
|
||||
if($conf['type'] == 'redis'){
|
||||
// is Redis -> group all DNS by host:port
|
||||
$client = new \Redis();
|
||||
|
||||
try{
|
||||
$client->connect($conf['host'], $conf['port'], 0.3);
|
||||
if(isset($conf['db'])) {
|
||||
$client->select($conf['db']);
|
||||
}
|
||||
|
||||
$conf['db'] = $client->getDbNum();
|
||||
}catch(\RedisException $e){
|
||||
// connection failed
|
||||
}
|
||||
|
||||
if(!array_key_exists($uid = $conf['host'] . ':' . $conf['port'], $redisConfig)){
|
||||
$redisConfig[$uid] = $getClientInfo($client, $conf);
|
||||
$redisConfig[$uid]['status'] = $getClientStats($client) + $getDatabaseStatus($client, $conf['tag']);
|
||||
}elseif(!array_key_exists($uidDb = 'db_' . $conf['db'], $redisConfig[$uid]['status'])){
|
||||
$redisConfig[$uid]['status'] += $getDatabaseStatus($client, $conf['tag']);
|
||||
}else{
|
||||
$redisConfig[$uid]['status'][$uidDb]['label'] .= '; ' . $conf['tag'];
|
||||
}
|
||||
|
||||
$client->close();
|
||||
}
|
||||
};
|
||||
|
||||
// potential Redis caches ---------------------------------------------------------------------------------
|
||||
$redisCaches = [
|
||||
'CACHE' => $f3->get('CACHE'),
|
||||
'API_CACHE' => $f3->get('API_CACHE')
|
||||
];
|
||||
|
||||
foreach($redisCaches as $tag => $dsn){
|
||||
if(Config::parseDSN($dsn, $conf)){
|
||||
$conf['tag'] = $tag;
|
||||
$dsnData[] = $conf;
|
||||
}
|
||||
}
|
||||
|
||||
// if Session handler is also Redis -> add this as well ---------------------------------------------------
|
||||
// -> the DSN format is not the same, convert URL format into DSN
|
||||
if(
|
||||
strtolower(session_module_name()) == 'redis' &&
|
||||
($parts = parse_url(strtolower(session_save_path())))
|
||||
){
|
||||
// parse URL parameters
|
||||
parse_str((string)$parts['query'], $params);
|
||||
|
||||
$conf = [
|
||||
'type' => 'redis',
|
||||
'host' => $parts['host'],
|
||||
'port' => $parts['port'],
|
||||
'db' => !empty($params['database']) ? (int)$params['database'] : 0,
|
||||
'tag' => 'SESSION'
|
||||
];
|
||||
$dsnData[] = $conf;
|
||||
}
|
||||
|
||||
// sort all $dsnData by 'db' number -----------------------------------------------------------------------
|
||||
usort($dsnData, function($a, $b){
|
||||
return $a['db'] <=> $b['db'];
|
||||
});
|
||||
|
||||
foreach($dsnData as $conf){
|
||||
$buildRedisConfig($conf);
|
||||
}
|
||||
}
|
||||
|
||||
return $redisConfig;
|
||||
@@ -989,14 +1211,14 @@ class Setup extends Controller {
|
||||
$changedIndex = false;
|
||||
$addConstraints = [];
|
||||
|
||||
// set (new) column information -------------------------------------------------------
|
||||
// set (new) column information -----------------------------------------------------------
|
||||
$requiredTables[$requiredTableName]['fieldConf'][$columnName]['exists'] = true;
|
||||
$requiredTables[$requiredTableName]['fieldConf'][$columnName]['currentType'] = $currentColType;
|
||||
$requiredTables[$requiredTableName]['fieldConf'][$columnName]['currentNullable'] = $hasNullable;
|
||||
$requiredTables[$requiredTableName]['fieldConf'][$columnName]['currentIndex'] = $hasIndex;
|
||||
$requiredTables[$requiredTableName]['fieldConf'][$columnName]['currentUnique'] = $hasUnique;
|
||||
|
||||
// check constraint -------------------------------------------------------------------
|
||||
// check constraint -----------------------------------------------------------------------
|
||||
if(isset($fieldConf['constraint'])){
|
||||
// add or update constraints
|
||||
foreach((array)$fieldConf['constraint'] as $constraintData){
|
||||
@@ -1022,7 +1244,7 @@ class Setup extends Controller {
|
||||
}
|
||||
}
|
||||
|
||||
// check type changed -----------------------------------------------------------------
|
||||
// check type changed ---------------------------------------------------------------------
|
||||
if(
|
||||
$fieldConf['type'] !== 'JSON' &&
|
||||
!$schema->isCompatible($fieldConf['type'], $currentColType)
|
||||
@@ -1033,14 +1255,14 @@ class Setup extends Controller {
|
||||
$tableStatusCheckCount++;
|
||||
}
|
||||
|
||||
// check if column nullable changed ---------------------------------------------------
|
||||
// check if column nullable changed -------------------------------------------------------
|
||||
if( $currentNullable != $fieldConf['nullable']){
|
||||
$changedNullable = true;
|
||||
$columnStatusCheck = false;
|
||||
$tableStatusCheckCount++;
|
||||
}
|
||||
|
||||
// check if column index changed ------------------------------------------------------
|
||||
// check if column index changed ----------------------------------------------------------
|
||||
$indexUpdate = false;
|
||||
$indexKey = (bool)$hasIndex;
|
||||
$indexUnique = (bool)$hasUnique;
|
||||
@@ -1054,7 +1276,7 @@ class Setup extends Controller {
|
||||
$indexKey = (bool)$fieldConf['index'];
|
||||
}
|
||||
|
||||
// check if column unique changed -----------------------------------------------------
|
||||
// check if column unique changed ---------------------------------------------------------
|
||||
if($currentColIndexData['unique'] != $fieldConf['unique']){
|
||||
$changedUnique = true;
|
||||
$columnStatusCheck = false;
|
||||
@@ -1064,7 +1286,7 @@ class Setup extends Controller {
|
||||
$indexUnique = (bool)$fieldConf['unique'];
|
||||
}
|
||||
|
||||
// build table with changed columns ---------------------------------------------------
|
||||
// build table with changed columns -------------------------------------------------------
|
||||
if(!$columnStatusCheck || !$foreignKeyStatusCheck){
|
||||
|
||||
if(!$columnStatusCheck ){
|
||||
@@ -1106,7 +1328,7 @@ class Setup extends Controller {
|
||||
}
|
||||
}
|
||||
|
||||
// set (new) column information -------------------------------------------------------
|
||||
// set (new) column information -----------------------------------------------------------
|
||||
$requiredTables[$requiredTableName]['fieldConf'][$columnName]['changedType'] = $changedType;
|
||||
$requiredTables[$requiredTableName]['fieldConf'][$columnName]['changedNullable'] = $changedNullable;
|
||||
$requiredTables[$requiredTableName]['fieldConf'][$columnName]['changedUnique'] = $changedUnique;
|
||||
@@ -1186,12 +1408,13 @@ class Setup extends Controller {
|
||||
return $this->databases;
|
||||
}
|
||||
|
||||
/** check MySQL params
|
||||
/**
|
||||
* check MySQL params
|
||||
* @param \Base $f3
|
||||
* @param $db
|
||||
* @param SQL $db
|
||||
* @return array
|
||||
*/
|
||||
protected function checkDBConfig(\Base $f3, $db){
|
||||
protected function checkDBConfig(\Base $f3, SQL $db){
|
||||
|
||||
// some db like "Maria DB" have some strange version strings....
|
||||
$dbVersionString = $db->version();
|
||||
@@ -1214,19 +1437,13 @@ class Setup extends Controller {
|
||||
]
|
||||
];
|
||||
|
||||
// get specific MySQL config Value
|
||||
$getDBConfigValue = function($db, $param){
|
||||
$result = $db->exec([
|
||||
//"USE " . $db->name(),
|
||||
"SHOW VARIABLES LIKE '" . strtolower($param) . "'"
|
||||
]);
|
||||
$tmpResult = reset($result);
|
||||
return !empty($result)? end($tmpResult) : 'unknown';
|
||||
};
|
||||
|
||||
$mySQLConfigParams = $f3->get('REQUIREMENTS.MYSQL.VARS');
|
||||
$mySQLConfigParams = (array)$f3->get('REQUIREMENTS.MYSQL.VARS');
|
||||
foreach($mySQLConfigParams as $param => $requiredValue){
|
||||
$value = $getDBConfigValue($db, $param);
|
||||
// get current MySQL config value for $param
|
||||
$result = $db->exec("SHOW VARIABLES LIKE '" . strtolower($param) . "'");
|
||||
$tmpResult = reset($result);
|
||||
$value = !empty($result)? end($tmpResult) : 'unknown';
|
||||
|
||||
$dbConfig[] = [
|
||||
'label' => strtolower($param),
|
||||
'required' => $requiredValue,
|
||||
@@ -1360,7 +1577,7 @@ class Setup extends Controller {
|
||||
[
|
||||
'action' => 'clearIndex',
|
||||
'label' => 'Clear',
|
||||
'icon' => 'fa-times',
|
||||
'icon' => 'fa-trash',
|
||||
'btn' => 'btn-danger'
|
||||
],[
|
||||
'action' => 'buildIndex',
|
||||
@@ -1473,39 +1690,96 @@ class Setup extends Controller {
|
||||
}
|
||||
|
||||
/**
|
||||
* get cache folder size as string
|
||||
* get cache folder size
|
||||
* @param \Base $f3
|
||||
* @return array
|
||||
*/
|
||||
protected function getCacheData(\Base $f3){
|
||||
protected function checkDirSize(\Base $f3) : array {
|
||||
// limit shown cache size. Reduce page load on big cache. In Bytes
|
||||
$maxBytes = 10 * 1024 * 1024; // 10MB
|
||||
$dirTemp = (string)$f3->get('TEMP');
|
||||
$cacheDsn = (string)$f3->get('CACHE');
|
||||
Config::parseDSN($cacheDsn, $conf);
|
||||
// if 'CACHE' is e.g. redis=... -> show default dir for cache
|
||||
$dirCache = $conf['type'] == 'folder' ? $conf['folder'] : $dirTemp . 'cache/';
|
||||
|
||||
// get all cache -----------------------------------------------------------------------------------------
|
||||
$cacheFilesAll = Search::getFilesByMTime( $f3->get('TEMP') );
|
||||
$dirAll = [
|
||||
'TEMP' => [
|
||||
'label' => 'Temp dir',
|
||||
'path' => $dirTemp
|
||||
],
|
||||
'CACHE' => [
|
||||
'label' => 'Cache dir',
|
||||
'path' => $dirCache
|
||||
]
|
||||
];
|
||||
|
||||
$maxHitAll = false;
|
||||
$bytesAll = 0;
|
||||
foreach($cacheFilesAll as $filename => $file) {
|
||||
$bytesAll += $file->getSize();
|
||||
}
|
||||
|
||||
// get data cache -----------------------------------------------------------------------------------------
|
||||
$cacheFilesData = Search::getFilesByMTime( $f3->get('TEMP') . 'cache/' );
|
||||
$bytesData = 0;
|
||||
foreach($cacheFilesData as $filename => $file) {
|
||||
$bytesData += $file->getSize();
|
||||
foreach($dirAll as $key => $dirData){
|
||||
$maxHit = false;
|
||||
$bytes = 0;
|
||||
$files = Search::getFilesByMTime($dirData['path']);
|
||||
foreach($files as $filename => $file) {
|
||||
$bytes += $file->getSize();
|
||||
if($bytes > $maxBytes){
|
||||
$maxHit = $maxHitAll = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
$bytesAll += $bytes;
|
||||
|
||||
$dirAll[$key]['size'] = ($maxHit ? '>' : '') . $this->convertBytes($bytes);
|
||||
$dirAll[$key]['task'] = [
|
||||
[
|
||||
'action' => http_build_query([
|
||||
'action' => 'clearFiles',
|
||||
'path' => $dirData['path']
|
||||
]),
|
||||
'label' => 'Delete files',
|
||||
'icon' => 'fa-trash',
|
||||
'btn' => 'btn-danger' . (($bytes > 0) ? '' : ' disabled')
|
||||
]
|
||||
];
|
||||
}
|
||||
|
||||
return [
|
||||
'all' => $this->convertBytes($bytesAll),
|
||||
'data' => $this->convertBytes($bytesData),
|
||||
'template' => $this->convertBytes($bytesAll - $bytesData)
|
||||
'sizeAll' => ($maxHitAll ? '>' : '') . $this->convertBytes($bytesAll),
|
||||
'dirAll' => $dirAll
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* clear all cached files
|
||||
* @param \Base $f3
|
||||
* clear directory
|
||||
* @param string $path
|
||||
*/
|
||||
protected function clearCache(\Base $f3){
|
||||
$f3->clear('CACHE');
|
||||
protected function clearFiles(string $path){
|
||||
$files = Search::getFilesByMTime($path);
|
||||
foreach($files as $filename => $file){
|
||||
/**
|
||||
* @var $file \SplFileInfo
|
||||
*/
|
||||
if($file->isFile()){
|
||||
if($file->isWritable()){
|
||||
unlink($file->getRealPath());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* clear all key in a specific Redis database
|
||||
* @param string $host
|
||||
* @param int $port
|
||||
* @param int $db
|
||||
*/
|
||||
protected function flushRedisDb(string $host, int $port, int $db = 0){
|
||||
$client = new \Redis();
|
||||
$client->connect($host, $port, 0.3);
|
||||
$client->select($db);
|
||||
$client->flushDB();
|
||||
$client->close();
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -104,13 +104,13 @@ class CcpSystemsUpdate extends AbstractCron {
|
||||
|
||||
// get current jump data --------------------------------------------------------------------------------------
|
||||
$time_start = microtime(true);
|
||||
$jumpData = $f3->ccpClient->getUniverseJumps();
|
||||
$jumpData = $f3->ccpClient()->getUniverseJumps();
|
||||
$time_end = microtime(true);
|
||||
$execTimeGetJumpData = $time_end - $time_start;
|
||||
|
||||
// get current kill data --------------------------------------------------------------------------------------
|
||||
$time_start = microtime(true);
|
||||
$killData = $f3->ccpClient->getUniverseKills();
|
||||
$killData = $f3->ccpClient()->getUniverseKills();
|
||||
$time_end = microtime(true);
|
||||
$execTimeGetKillData = $time_end - $time_start;
|
||||
|
||||
|
||||
@@ -67,8 +67,7 @@ class CharacterUpdate extends AbstractCron {
|
||||
if(is_object($characterLog->characterId)){
|
||||
// force characterLog as "updated" even if no changes were made
|
||||
$characterLog->characterId->updateLog([
|
||||
'markUpdated' => true,
|
||||
'suppressHTTPErrors' => true
|
||||
'markUpdated' => true
|
||||
]);
|
||||
}else{
|
||||
// character_log does not have a character assigned -> delete
|
||||
|
||||
125
app/main/cron/maphistory.php
Normal file
125
app/main/cron/maphistory.php
Normal file
@@ -0,0 +1,125 @@
|
||||
<?php
|
||||
/**
|
||||
* Created by PhpStorm.
|
||||
* User: Exodus 4D
|
||||
* Date: 22.12.2018
|
||||
* Time: 15:48
|
||||
*/
|
||||
|
||||
namespace Cron;
|
||||
|
||||
use data\mapper\SortingIterator;
|
||||
use data\file\FileHandler;
|
||||
use data\filesystem\Search;
|
||||
|
||||
class MapHistory extends AbstractCron {
|
||||
|
||||
const LOG_TEXT = '%s [%4s] log files, [%4s] not writable, [%4s] read error, [%4s] write error, [%4s] rename error, [%4s] delete error, exec (%.3Fs)';
|
||||
|
||||
/**
|
||||
* default log file size limit before truncate, bytes (1MB)
|
||||
*/
|
||||
const LOG_SIZE_THRESHOLD = 1024 * 1024;
|
||||
|
||||
/**
|
||||
* default count of log files that will be truncated
|
||||
*/
|
||||
const LOG_COUNT = 3;
|
||||
|
||||
/**
|
||||
* default line limit after truncate
|
||||
*/
|
||||
const LOG_LINES = 1000;
|
||||
|
||||
/**
|
||||
* get max log size threshold before truncate
|
||||
* @param \Base $f3
|
||||
* @return int
|
||||
*/
|
||||
protected function getMaxLogSize(\Base $f3) : int {
|
||||
$logSize = (int)$f3->get('PATHFINDER.HISTORY.LOG_SIZE_THRESHOLD');
|
||||
return ($logSize >= 0) ? ($logSize * 1024 * 1024) : self::LOG_SIZE_THRESHOLD;
|
||||
}
|
||||
|
||||
/**
|
||||
* get max log entries (lines) after truncate
|
||||
* @param \Base $f3
|
||||
* @return int
|
||||
*/
|
||||
protected function getMaxLogLines(\Base $f3) : int {
|
||||
$logLines = (int)$f3->get('PATHFINDER.HISTORY.LOG_LINES');
|
||||
return ($logLines >= 0) ? $logLines : self::LOG_LINES;
|
||||
}
|
||||
|
||||
/**
|
||||
* truncate map history log files and keep size small
|
||||
* >> php index.php "/cron/truncateMapHistoryLogFiles"
|
||||
* @param \Base $f3
|
||||
*/
|
||||
function truncateFiles(\Base $f3){
|
||||
$timeStart = microtime(true);
|
||||
|
||||
$largeFiles = 0;
|
||||
$notWritableFiles = 0;
|
||||
$readErrors = 0;
|
||||
$writeErrors = 0;
|
||||
$renameErrors = 0;
|
||||
$deleteErrors = 0;
|
||||
|
||||
if($f3->exists('PATHFINDER.HISTORY.LOG', $dir)){
|
||||
$fileHandler = FileHandler::instance();
|
||||
|
||||
$dir = $f3->fixslashes('./' . $dir . 'map/');
|
||||
$files = Search::getFilesBySize($dir, $this->getMaxLogSize($f3));
|
||||
|
||||
// sort by file size
|
||||
$files = new SortingIterator($files, function( \SplFileInfo $a, \SplFileInfo $b){
|
||||
return $b->getSize() - $a->getSize();
|
||||
});
|
||||
|
||||
// limit files count for truncate
|
||||
$files = new \LimitIterator($files, 0, self::LOG_COUNT);
|
||||
|
||||
foreach($files as $filename => $file){
|
||||
/**
|
||||
* @var $file \SplFileInfo
|
||||
*/
|
||||
if($file->isFile()){
|
||||
$largeFiles++;
|
||||
if($file->isWritable()){
|
||||
// read newest logs from large files (reverse order) -> new log entries were appended...
|
||||
$rowsData = $fileHandler->readFileReverse($file->getRealPath(), 0, self::LOG_LINES);
|
||||
if(!empty($rowsData)){
|
||||
// create temp file...
|
||||
$temp = tempnam(sys_get_temp_dir(), 'map_');
|
||||
// write newest logs into temp file...
|
||||
$fileSizeNew = file_put_contents($temp, implode(PHP_EOL, array_reverse($rowsData)) . PHP_EOL, LOCK_EX);
|
||||
if($fileSizeNew){
|
||||
// move temp file from PHP temp dir into Pathfinders history log dir...
|
||||
// ... overwrite old log file with new file
|
||||
if(rename($temp, $file->getRealPath())){
|
||||
// map history logs should be writable non cronjob user too
|
||||
@chmod($file->getRealPath(), 0666);
|
||||
}else{
|
||||
$renameErrors++;
|
||||
}
|
||||
}else{
|
||||
$writeErrors++;
|
||||
}
|
||||
}else{
|
||||
$readErrors++;
|
||||
}
|
||||
}else{
|
||||
$notWritableFiles++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$execTime = microtime(true) - $timeStart;
|
||||
|
||||
// Log ------------------------
|
||||
$log = new \Log('cron_' . __FUNCTION__ . '.log');
|
||||
$log->write(sprintf(self::LOG_TEXT, __FUNCTION__, $largeFiles, $notWritableFiles, $readErrors, $writeErrors, $renameErrors, $deleteErrors, $execTime));
|
||||
}
|
||||
}
|
||||
@@ -34,7 +34,7 @@ class Universe extends AbstractCron {
|
||||
}
|
||||
|
||||
/**
|
||||
* format Byte §size for output
|
||||
* format Byte $size for output
|
||||
* @param int $size
|
||||
* @return string
|
||||
*/
|
||||
@@ -177,7 +177,7 @@ class Universe extends AbstractCron {
|
||||
switch($type){
|
||||
case 'system':
|
||||
// load systems + dependencies (planets, star, types,...)
|
||||
$ids = $f3->ccpClient->getUniverseSystems();
|
||||
$ids = $f3->ccpClient()->getUniverseSystems();
|
||||
$modelClass = 'SystemModel';
|
||||
$setupModel = function(Model\Universe\SystemModel &$model, int $id){
|
||||
$model->loadById($id);
|
||||
@@ -186,7 +186,7 @@ class Universe extends AbstractCron {
|
||||
break;
|
||||
case 'stargate':
|
||||
// load all stargates. Systems must be present first!
|
||||
$ids = $f3->ccpClient->getUniverseSystems();
|
||||
$ids = $f3->ccpClient()->getUniverseSystems();
|
||||
$modelClass = 'SystemModel';
|
||||
$setupModel = function(Model\Universe\SystemModel &$model, int $id){
|
||||
$model->loadById($id);
|
||||
@@ -195,7 +195,7 @@ class Universe extends AbstractCron {
|
||||
break;
|
||||
case 'index_system':
|
||||
// setup system index, Systems must be present first!
|
||||
$ids = $f3->ccpClient->getUniverseSystems();
|
||||
$ids = $f3->ccpClient()->getUniverseSystems();
|
||||
$modelClass = 'SystemModel';
|
||||
$setupModel = function(Model\Universe\SystemModel &$model, int $id){
|
||||
$model->getById($id); // no loadById() here! would take "forever" when system not exists and build up first...
|
||||
|
||||
@@ -21,20 +21,19 @@ class FileHandler extends \Prefab {
|
||||
const Log_File_LIMIT_MAX = 100;
|
||||
|
||||
/**
|
||||
* parse local log file from end to first line
|
||||
* -> Each row is a JSON object
|
||||
* parse file from end to first line
|
||||
* @param string $sourceFile
|
||||
* @param int $offset
|
||||
* @param int $limit
|
||||
* @param null|callable $formatter
|
||||
* @param \Closure|null $rowParser
|
||||
* @return array
|
||||
*/
|
||||
public static function readLogFile(
|
||||
public function readFileReverse(
|
||||
string $sourceFile,
|
||||
int $offset = self::LOG_FILE_OFFSET,
|
||||
int $limit = self::LOG_FILE_LIMIT,
|
||||
$formatter = null
|
||||
): array {
|
||||
\Closure $rowParser = null
|
||||
) : array {
|
||||
$data = [];
|
||||
|
||||
if(is_file($sourceFile)){
|
||||
@@ -43,11 +42,11 @@ class FileHandler extends \Prefab {
|
||||
$file->setFlags(\SplFileObject::DROP_NEW_LINE | \SplFileObject::READ_AHEAD | \SplFileObject::SKIP_EMPTY);
|
||||
|
||||
foreach( new \LimitIterator($file, 0, $limit) as $i => $rowData){
|
||||
if( !empty($rowDataObj = (array)json_decode($rowData, true)) ){
|
||||
if(is_callable($formatter)){
|
||||
$formatter($rowDataObj);
|
||||
}
|
||||
$data[] = $rowDataObj;
|
||||
if(is_callable($rowParser)){
|
||||
// custom parser for row data -> manipulate $data by ref
|
||||
$rowParser($rowData, $data);
|
||||
}else{
|
||||
$data[] = $rowData;
|
||||
}
|
||||
}
|
||||
}else{
|
||||
|
||||
@@ -12,58 +12,79 @@ namespace data\filesystem;
|
||||
class Search {
|
||||
|
||||
/**
|
||||
* max file count that should be deleted in this session
|
||||
* max file count that can be returned
|
||||
*/
|
||||
const DEFAULT_FILE_LIMIT = 1000;
|
||||
|
||||
/**
|
||||
* timestamp (seconds) filter files by mTime()
|
||||
* -> default = "no filter"
|
||||
* @var int
|
||||
*/
|
||||
static $filterTime = 0;
|
||||
|
||||
/**
|
||||
* recursive file filter by mTime
|
||||
* @param string $dir
|
||||
* @param int $mTime
|
||||
* @param null $mTime
|
||||
* @param int $limit
|
||||
* @return array|\LimitIterator
|
||||
* @return \Traversable
|
||||
*/
|
||||
static function getFilesByMTime(string $dir, $mTime = null, $limit = self::DEFAULT_FILE_LIMIT){
|
||||
$files = [];
|
||||
static function getFilesByMTime(string $dir, $mTime = null, $limit = self::DEFAULT_FILE_LIMIT) : \Traversable {
|
||||
$mTime = is_null($mTime) ? time() : (int)$mTime;
|
||||
|
||||
if(is_dir($dir)){
|
||||
if(is_null($mTime)){
|
||||
self::$filterTime = time();
|
||||
}else{
|
||||
self::$filterTime = (int)$mTime;
|
||||
$filterCallback = function($current, $key, $iterator) use ($mTime) {
|
||||
/**
|
||||
* @var $current \RecursiveDirectoryIterator
|
||||
*/
|
||||
if (
|
||||
!$current->isFile() || // allow recursion
|
||||
(
|
||||
strpos($current->getFilename(), '.') !== 0 && // skip e.g. ".gitignore"
|
||||
$current->getMTime() < $mTime // filter last modification date
|
||||
)
|
||||
){
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
$directory = new \RecursiveDirectoryIterator( $dir, \FilesystemIterator::SKIP_DOTS );
|
||||
$files = new \RecursiveCallbackFilterIterator($directory, function ($current, $key, $iterator) {
|
||||
|
||||
// Check for last modification date
|
||||
/**
|
||||
* @var $current \RecursiveDirectoryIterator
|
||||
*/
|
||||
if (
|
||||
!$current->isFile() || // allow recursion
|
||||
(
|
||||
strpos($current->getFilename(), '.') !== 0 && // skip e.g. ".gitignore"
|
||||
$current->getMTime() < self::$filterTime // check last modification date
|
||||
)
|
||||
){
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
||||
// limit max files
|
||||
$files = new \LimitIterator($files, 0, $limit);
|
||||
}
|
||||
|
||||
return $files;
|
||||
return self::getFilesByCallback($dir, $filterCallback, $limit);
|
||||
}
|
||||
|
||||
/**
|
||||
* recursive file filter by size
|
||||
* @param string $dir
|
||||
* @param int $size
|
||||
* @param int $limit
|
||||
* @return \Traversable
|
||||
*/
|
||||
static function getFilesBySize(string $dir, int $size = 0, int $limit = self::DEFAULT_FILE_LIMIT) : \Traversable {
|
||||
|
||||
$filterCallback = function($current, $key, $iterator) use ($size) {
|
||||
/**
|
||||
* @var $current \RecursiveDirectoryIterator
|
||||
*/
|
||||
if (
|
||||
!$current->isFile() || // allow recursion
|
||||
(
|
||||
strpos($current->getFilename(), '.') !== 0 && // skip e.g. ".gitignore"
|
||||
$current->getSize() > $size // filter file size
|
||||
)
|
||||
){
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
return self::getFilesByCallback($dir, $filterCallback, $limit);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $dir
|
||||
* @param \Closure $filterCallback
|
||||
* @param int $limit
|
||||
* @return \Traversable
|
||||
*/
|
||||
private static function getFilesByCallback(string $dir, \Closure $filterCallback, int $limit = self::DEFAULT_FILE_LIMIT) : \Traversable {
|
||||
$files = new \ArrayIterator();
|
||||
if(is_dir($dir)){
|
||||
$directory = new \RecursiveDirectoryIterator( $dir, \FilesystemIterator::SKIP_DOTS );
|
||||
$files = new \RecursiveCallbackFilterIterator($directory, $filterCallback);
|
||||
}
|
||||
return new \LimitIterator($files, 0, $limit);
|
||||
}
|
||||
}
|
||||
20
app/main/data/mapper/sortingiterator.php
Normal file
20
app/main/data/mapper/sortingiterator.php
Normal file
@@ -0,0 +1,20 @@
|
||||
<?php
|
||||
/**
|
||||
* Created by PhpStorm.
|
||||
* User: Exodus 4D
|
||||
* Date: 24.12.2018
|
||||
* Time: 00:55
|
||||
*/
|
||||
|
||||
namespace data\mapper;
|
||||
|
||||
|
||||
class SortingIterator extends \ArrayIterator {
|
||||
|
||||
public function __construct(\Traversable $iterator, callable $callback){
|
||||
parent::__construct(iterator_to_array($iterator));
|
||||
|
||||
// sort by custom function
|
||||
$this->uasort($callback);
|
||||
}
|
||||
}
|
||||
@@ -7,6 +7,8 @@
|
||||
*/
|
||||
|
||||
namespace DB;
|
||||
|
||||
|
||||
use controller\LogController;
|
||||
use lib\Config;
|
||||
|
||||
@@ -29,7 +31,7 @@ class Database extends \Prefab {
|
||||
* @param string $dbKey
|
||||
* @return SQL|null
|
||||
*/
|
||||
public function connectToServer(string $dbKey = 'PF'){
|
||||
public function connectToServer(string $dbKey = 'PF') : ?SQL {
|
||||
$dbConfig = Config::getDatabaseConfig($dbKey);
|
||||
$dbConfig['DNS'] = str_replace(';dbname=', '', $dbConfig['DNS'] );
|
||||
$dbConfig['NAME'] = '';
|
||||
@@ -42,7 +44,7 @@ class Database extends \Prefab {
|
||||
* @param string $dbKey
|
||||
* @return SQL|null
|
||||
*/
|
||||
public function createDB(string $dbKey = 'PF'){
|
||||
public function createDB(string $dbKey = 'PF') : ?SQL {
|
||||
$db = null;
|
||||
$dbConfig = Config::getDatabaseConfig($dbKey);
|
||||
// remove database from $dsn (we want to crate it)
|
||||
@@ -51,6 +53,9 @@ class Database extends \Prefab {
|
||||
$dbConfig['NAME'] = '';
|
||||
$dbConfig['DNS'] = str_replace(';dbname=', '', $dbConfig['DNS'] );
|
||||
|
||||
/**
|
||||
* @var $db SQL|null
|
||||
*/
|
||||
$db = call_user_func_array([$this, 'connect'], $dbConfig);
|
||||
|
||||
if(!is_null($db)){
|
||||
@@ -83,12 +88,15 @@ class Database extends \Prefab {
|
||||
* @param string $dbKey
|
||||
* @return SQL|null
|
||||
*/
|
||||
public function getDB(string $dbKey = 'PF'){
|
||||
public function getDB(string $dbKey = 'PF') : ?SQL {
|
||||
$f3 = \Base::instance();
|
||||
// "Hive" Key for DB object cache
|
||||
$dbHiveKey = $this->getDbHiveKey($dbKey);
|
||||
if( !$f3->exists($dbHiveKey, $db) ){
|
||||
$dbConfig = Config::getDatabaseConfig($dbKey);
|
||||
/**
|
||||
* @var $db SQL|null
|
||||
*/
|
||||
$db = call_user_func_array([$this, 'connect'], $dbConfig);
|
||||
if(!is_null($db)){
|
||||
self::prepareDBConnection($db);
|
||||
@@ -104,7 +112,7 @@ class Database extends \Prefab {
|
||||
* @param $dbKey
|
||||
* @return string
|
||||
*/
|
||||
protected function getDbHiveKey($dbKey){
|
||||
protected function getDbHiveKey(string $dbKey) : string {
|
||||
return 'DB_' . $dbKey;
|
||||
}
|
||||
|
||||
@@ -117,7 +125,7 @@ class Database extends \Prefab {
|
||||
* @param string $alias
|
||||
* @return SQL|null
|
||||
*/
|
||||
protected function connect($dns, $name, $user, $password, $alias){
|
||||
protected function connect(string $dns, string $name, string $user, string $password, string $alias) : ?SQL {
|
||||
$db = null;
|
||||
$f3 = \Base::instance();
|
||||
|
||||
@@ -155,18 +163,18 @@ class Database extends \Prefab {
|
||||
* @param string $dbKey
|
||||
* @return array|bool
|
||||
*/
|
||||
public function getTables($dbKey = 'PF'){
|
||||
public function getTables(string $dbKey = 'PF'){
|
||||
$schema = new SQL\Schema( $this->getDB($dbKey) );
|
||||
return $schema->getTables();
|
||||
}
|
||||
|
||||
/**
|
||||
* checks whether a table exists on a DB or not
|
||||
* @param $table
|
||||
* @param string $table
|
||||
* @param string $dbKey
|
||||
* @return bool
|
||||
*/
|
||||
public function tableExists($table, $dbKey = 'PF'){
|
||||
public function tableExists(string $table, string $dbKey = 'PF') : bool {
|
||||
$tableNames = $this->getTables($dbKey);
|
||||
return in_array($table, $tableNames);
|
||||
}
|
||||
@@ -174,11 +182,11 @@ class Database extends \Prefab {
|
||||
/**
|
||||
* get current row (data) count for an existing table
|
||||
* -> returns 0 if table not exists or empty
|
||||
* @param $table
|
||||
* @param string $table
|
||||
* @param string $dbKey
|
||||
* @return int
|
||||
*/
|
||||
public function getRowCount($table, $dbKey = 'PF') {
|
||||
public function getRowCount(string $table, string $dbKey = 'PF') : int {
|
||||
$count = 0;
|
||||
if( $this->tableExists($table, $dbKey) ){
|
||||
$db = $this->getDB($dbKey);
|
||||
@@ -193,7 +201,7 @@ class Database extends \Prefab {
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function isSilent() : bool{
|
||||
public function isSilent() : bool {
|
||||
return $this->silent;
|
||||
}
|
||||
|
||||
@@ -251,11 +259,15 @@ class Database extends \Prefab {
|
||||
*/
|
||||
public static function prepareDBConnection(SQL &$db){
|
||||
// set DB timezone to UTC +00:00 (eve server time)
|
||||
$db->exec('SET @@session.time_zone = "+00:00";');
|
||||
|
||||
// set default storage engine
|
||||
$db->exec('SET @@session.default_storage_engine = "' .
|
||||
self::getRequiredMySqlVariables('DEFAULT_STORAGE_ENGINE') . '"');
|
||||
$db->exec([
|
||||
'SET @@session.time_zone = :time_zone',
|
||||
'SET @@session.default_storage_engine = :storage_engine'
|
||||
], [
|
||||
[':time_zone' => '+00:00'],
|
||||
[':storage_engine' => self::getRequiredMySqlVariables('DEFAULT_STORAGE_ENGINE')]
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -277,7 +289,7 @@ class Database extends \Prefab {
|
||||
* @param string $key
|
||||
* @return string|null
|
||||
*/
|
||||
public static function getRequiredMySqlVariables(string $key){
|
||||
public static function getRequiredMySqlVariables(string $key) : ?string {
|
||||
\Base::instance()->exists('REQUIREMENTS[MYSQL][VARS][' . $key . ']', $data);
|
||||
return $data;
|
||||
}
|
||||
@@ -286,7 +298,7 @@ class Database extends \Prefab {
|
||||
* get logger for DB logging
|
||||
* @return \Log
|
||||
*/
|
||||
static function getLogger(){
|
||||
static function getLogger() : \Log {
|
||||
return LogController::getLogger('ERROR');
|
||||
}
|
||||
}
|
||||
39
app/main/db/sql/mysql/session.php
Normal file
39
app/main/db/sql/mysql/session.php
Normal file
@@ -0,0 +1,39 @@
|
||||
<?php
|
||||
/**
|
||||
* Created by PhpStorm.
|
||||
* User: Exodus 4D
|
||||
* Date: 02.12.2018
|
||||
* Time: 15:40
|
||||
*/
|
||||
|
||||
namespace DB\SQL\MySQL;
|
||||
|
||||
|
||||
class Session extends \DB\SQL\Session {
|
||||
|
||||
|
||||
function __construct(\DB\SQL $db, string $table = 'sessions', bool $force = true, $onsuspect = null, $key = null){
|
||||
if($force){
|
||||
// create sessions table
|
||||
// -> We use this "custom" SQl rather than the default in parent::__construct()
|
||||
// because of the defaults 'data' column type TEXT
|
||||
$dbName = $db->name();
|
||||
|
||||
$sql = "CREATE TABLE IF NOT EXISTS ";
|
||||
$sql .= $dbName ? $db->quotekey($dbName,FALSE) . "." : "";
|
||||
$sql .= $db->quotekey($table,FALSE) . " (";
|
||||
$sql .= $db->quotekey('session_id') . " VARCHAR(255),";
|
||||
$sql .= $db->quotekey('data') . " MEDIUMTEXT,";
|
||||
$sql .= $db->quotekey('ip') . " VARCHAR(45),";
|
||||
$sql .= $db->quotekey('agent') . " VARCHAR(300),";
|
||||
$sql .= $db->quotekey('stamp') . " INT(11),";
|
||||
$sql .= "PRIMARY KEY (" . $db->quotekey('session_id') . ")";
|
||||
$sql .= ");";
|
||||
|
||||
$db->exec($sql);
|
||||
}
|
||||
|
||||
// $force = false for parent constructor -> skip default create SQL
|
||||
parent::__construct($db, $table, false, $onsuspect, $key);
|
||||
}
|
||||
}
|
||||
337
app/main/lib/api/AbstractClient.php
Normal file
337
app/main/lib/api/AbstractClient.php
Normal file
@@ -0,0 +1,337 @@
|
||||
<?php
|
||||
/**
|
||||
* Created by PhpStorm.
|
||||
* User: Exodus 4D
|
||||
* Date: 26.12.2018
|
||||
* Time: 17:41
|
||||
*/
|
||||
|
||||
namespace lib\api;
|
||||
|
||||
use Cache\Adapter\Filesystem\FilesystemCachePool;
|
||||
use Cache\Adapter\PHPArray\ArrayCachePool;
|
||||
use Cache\Adapter\Redis\RedisCachePool;
|
||||
use Cache\Namespaced\NamespacedCachePool;
|
||||
use League\Flysystem\Adapter\Local;
|
||||
use League\Flysystem\Filesystem;
|
||||
use lib\Config;
|
||||
use lib\Util;
|
||||
use lib\logging;
|
||||
use controller\LogController;
|
||||
use Exodus4D\ESI\Client\ApiInterface;
|
||||
use Psr\Cache\CacheItemPoolInterface;
|
||||
use Psr\Http\Message\RequestInterface;
|
||||
|
||||
/**
|
||||
* Class AbstractClient
|
||||
* @package lib\api
|
||||
*
|
||||
* @method ApiInterface getUrl()
|
||||
* @method ApiInterface getTimeout()
|
||||
* @method ApiInterface getConnectTimeout()
|
||||
* @method ApiInterface getReadTimeout()
|
||||
* @method ApiInterface getProxy()
|
||||
* @method ApiInterface getVerify()
|
||||
* @method ApiInterface getDebugRequests()
|
||||
* @method ApiInterface getDataSource()
|
||||
*/
|
||||
abstract class AbstractClient extends \Prefab {
|
||||
|
||||
const ERROR_CLIENT_INVALID = "HTTP API client not found → Check installed Composer packages";
|
||||
|
||||
/**
|
||||
* @var string|null
|
||||
*/
|
||||
const CLIENT_NAME = null;
|
||||
|
||||
/**
|
||||
* @var string|null
|
||||
*/
|
||||
protected $clientName = null;
|
||||
|
||||
/**
|
||||
* @var ApiInterface|null
|
||||
*/
|
||||
protected $client = null;
|
||||
|
||||
/**
|
||||
* PSR-6 compatible CachePool instance
|
||||
* -> can be Redis, Filesystem or Array cachePool
|
||||
* -> used by e.g. GuzzleCacheMiddleware
|
||||
* @var CacheItemPoolInterface|null
|
||||
*/
|
||||
protected $cachePool = null;
|
||||
|
||||
/**
|
||||
* @param \Base $f3
|
||||
* @return ApiInterface|null
|
||||
*/
|
||||
abstract protected function getClient(\Base $f3) : ?ApiInterface;
|
||||
|
||||
/**
|
||||
* get userAgent
|
||||
* @return string
|
||||
*/
|
||||
protected function getUserAgent() : string {
|
||||
$userAgent = '';
|
||||
$userAgent .= Config::getPathfinderData('name');
|
||||
$userAgent .= ' - ' . Config::getPathfinderData('version');
|
||||
$userAgent .= ' | ' . Config::getPathfinderData('contact');
|
||||
$userAgent .= ' (' . $_SERVER['SERVER_NAME'] . ')';
|
||||
return $userAgent;
|
||||
}
|
||||
|
||||
/**
|
||||
* returns a new Log object used within the Api for logging
|
||||
* @return \Closure
|
||||
*/
|
||||
protected function newLog() : \Closure {
|
||||
return function(string $action, string $level = 'warning') : logging\LogInterface {
|
||||
$log = new logging\ApiLog($action, $level);
|
||||
$log->addHandler('stream', 'json', $this->getStreamConfig($action));
|
||||
return $log;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* returns a new instance of PSR-6 compatible CacheItemPoolInterface
|
||||
* -> this Cache backend will be used across Guzzle Middleware
|
||||
* e.g. GuzzleCacheMiddleware
|
||||
* @see http://www.php-cache.com
|
||||
* @param \Base $f3
|
||||
* @return \Closure
|
||||
*/
|
||||
protected function getCachePool(\Base $f3) : \Closure {
|
||||
// determine cachePool options
|
||||
$poolConfig = $this->getCachePoolConfig($f3);
|
||||
|
||||
return function() use ($poolConfig) : ?CacheItemPoolInterface {
|
||||
// an active CachePool should be re-used
|
||||
// -> no need for e.g. a new Redis->connect()
|
||||
// and/or re-init when it is used the next time
|
||||
if(!is_null($this->cachePool)){
|
||||
return $this->cachePool;
|
||||
}
|
||||
|
||||
// Redis is preferred option (best performance) -----------------------------------------------------------
|
||||
if(
|
||||
$poolConfig['type'] == 'redis' &&
|
||||
extension_loaded('redis') &&
|
||||
class_exists('\Redis') &&
|
||||
class_exists(RedisCachePool::class)
|
||||
){
|
||||
$client = new \Redis();
|
||||
if(
|
||||
$client->connect(
|
||||
$poolConfig['host'],
|
||||
$poolConfig['port'],
|
||||
Config::REDIS_OPT_TIMEOUT,
|
||||
null,
|
||||
Config::REDIS_OPT_RETRY_INTERVAL,
|
||||
Config::REDIS_OPT_READ_TIMEOUT
|
||||
)
|
||||
){
|
||||
if(isset($poolConfig['db'])){
|
||||
$client->select($poolConfig['db']);
|
||||
}
|
||||
$poolRedis = new RedisCachePool($client);
|
||||
|
||||
// RedisCachePool supports "Hierarchy" store slots
|
||||
// -> "Hierarchy" support is required to use it in a NamespacedCachePool
|
||||
// This helps to separate keys by a namespace
|
||||
// @see http://www.php-cache.com/en/latest/
|
||||
$this->cachePool = new NamespacedCachePool($poolRedis, static::CLIENT_NAME);
|
||||
}
|
||||
}
|
||||
|
||||
// Filesystem is second option and fallback for failed Redis pool -----------------------------------------
|
||||
if(
|
||||
is_null($this->cachePool) &&
|
||||
in_array($poolConfig['type'], ['redis', 'folder']) &&
|
||||
class_exists(FilesystemCachePool::class)
|
||||
){
|
||||
$filesystemAdapter = new Local('./');
|
||||
$filesystem = new Filesystem($filesystemAdapter);
|
||||
|
||||
$poolFilesystem = new FilesystemCachePool($filesystem);
|
||||
$poolFilesystem->setFolder($poolConfig['folder']);
|
||||
$this->cachePool = $poolFilesystem;
|
||||
}
|
||||
|
||||
// Array cache pool fallback (not persistent) -------------------------------------------------------------
|
||||
if(
|
||||
is_null($this->cachePool) &&
|
||||
in_array($poolConfig['type'], ['redis', 'folder', 'array']) &&
|
||||
class_exists(ArrayCachePool::class)
|
||||
){
|
||||
$this->cachePool = new ArrayCachePool(2000);
|
||||
}
|
||||
|
||||
return $this->cachePool;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* get cachePool config from [D]ata [S]ource [N]ame string
|
||||
* @param \Base $f3
|
||||
* @return array
|
||||
*/
|
||||
protected function getCachePoolConfig(\Base $f3) : array {
|
||||
$dsn = (string)$f3->get('API_CACHE');
|
||||
|
||||
// fallback
|
||||
$conf = ['type' => 'array'];
|
||||
|
||||
if(!empty($folder = (string)$f3->get('TEMP'))){
|
||||
// filesystem (better than 'array' cache)
|
||||
$conf = [
|
||||
'type' => 'folder',
|
||||
'folder' => $folder . 'cache/'
|
||||
];
|
||||
}
|
||||
|
||||
// redis or filesystem -> overwrites $conf
|
||||
Config::parseDSN($dsn, $conf);
|
||||
|
||||
return $conf;
|
||||
}
|
||||
|
||||
/**
|
||||
* return callback function that expects a $request and checks
|
||||
* whether it should be logged (in case of errors)
|
||||
* @param \Base $f3
|
||||
* @return \Closure
|
||||
*/
|
||||
protected function isLoggable(\Base $f3) : \Closure {
|
||||
return function(RequestInterface $request) use ($f3) : bool {
|
||||
// we need the timestamp for $request that should be checked
|
||||
// -> we assume $request was "recently" send. -> current server time is used for check
|
||||
$requestTime = $f3->get('getDateTime')();
|
||||
// ... "interpolate" time to short interval
|
||||
// -> this might help to re-use sequential calls of this method
|
||||
Util::roundToInterval($requestTime);
|
||||
// check if request was send within ESI downTime range
|
||||
// -> errors during downTime should not be logged
|
||||
$inDowntimeRange = Config::inDownTimeRange($requestTime);
|
||||
|
||||
return !$inDowntimeRange;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* get Logger
|
||||
* @param string $ype
|
||||
* @return \Log
|
||||
*/
|
||||
protected function getLogger(string $ype = 'ERROR') : \Log {
|
||||
return LogController::getLogger($ype);
|
||||
}
|
||||
/**
|
||||
* get error msg for missing $this->client class
|
||||
* @param string $class
|
||||
* @return string
|
||||
*/
|
||||
protected function getMissingClassError(string $class) : string {
|
||||
return sprintf(Config::ERROR_CLASS_NOT_EXISTS_COMPOSER, $class);
|
||||
}
|
||||
|
||||
/**
|
||||
* get error msg for undefined method in $this->client class
|
||||
* @param string $class
|
||||
* @param string $method
|
||||
* @return string
|
||||
*/
|
||||
protected function getMissingMethodError(string $class, string $method) : string {
|
||||
return sprintf(Config::ERROR_METHOD_NOT_EXISTS_COMPOSER, $method, $class);
|
||||
}
|
||||
|
||||
/**
|
||||
* get config for stream logging
|
||||
* @param string $logFileName
|
||||
* @param bool $abs
|
||||
* @return \stdClass
|
||||
*/
|
||||
protected function getStreamConfig(string $logFileName, bool $abs = false) : \stdClass {
|
||||
$f3 = \Base::instance();
|
||||
|
||||
$config = (object) [];
|
||||
$config->stream = '';
|
||||
if( $f3->exists('LOGS', $dir) ){
|
||||
$config->stream .= $abs ? $f3->get('ROOT') . '/' : './';
|
||||
$config->stream .= $dir . $logFileName . '.log';
|
||||
$config->stream = $f3->fixslashes($config->stream);
|
||||
}
|
||||
return $config;
|
||||
}
|
||||
|
||||
/**
|
||||
* call request API data
|
||||
* @param string $name
|
||||
* @param array $arguments
|
||||
* @return array|mixed
|
||||
*/
|
||||
public function __call(string $name, array $arguments = []){
|
||||
$return = [];
|
||||
if(is_object($this->client)){
|
||||
if( method_exists($this->client, $name) ){
|
||||
$return = call_user_func_array([$this->client, $name], $arguments);
|
||||
}else{
|
||||
$errorMsg = $this->getMissingMethodError(get_class($this->client), $name);
|
||||
$this->getLogger('ERROR')->write($errorMsg);
|
||||
\Base::instance()->error(501, $errorMsg);
|
||||
}
|
||||
}else{
|
||||
\Base::instance()->error(501, self::ERROR_CLIENT_INVALID);
|
||||
}
|
||||
|
||||
return $return;
|
||||
}
|
||||
|
||||
/**
|
||||
* init web client on __invoke()
|
||||
* -> no need to init client on __construct()
|
||||
* maybe it is nerer used...
|
||||
* @return AbstractClient
|
||||
*/
|
||||
function __invoke() : self {
|
||||
$f3 = \Base::instance();
|
||||
|
||||
if(
|
||||
!($this->client instanceof ApiInterface) &&
|
||||
($this->getClient($f3) instanceof ApiInterface)
|
||||
){
|
||||
// web client not initialized
|
||||
$client = $this->getClient($f3);
|
||||
$client->setTimeout(5);
|
||||
$client->setConnectTimeout(5);
|
||||
$client->setUserAgent($this->getUserAgent());
|
||||
$client->setDecodeContent('gzip, deflate');
|
||||
|
||||
$client->setDebugLevel($f3->get('DEBUG'));
|
||||
$client->setNewLog($this->newLog());
|
||||
$client->setIsLoggable($this->isLoggable($f3));
|
||||
|
||||
$client->setLogStats(true); // add cURL stats (e.g. transferTime) to logged requests
|
||||
$client->setLogCache(true); // add cache info (e.g. from cached) to logged requests
|
||||
//$client->setLogAllStatus(true); // log all requests regardless of response HTTP status code
|
||||
$client->setLogFile('esi_requests');//
|
||||
|
||||
$client->setRetryLogFile('esi_retry_requests');
|
||||
|
||||
$client->setCacheDebug(true);
|
||||
$client->setCachePool($this->getCachePool($f3));
|
||||
|
||||
// use local proxy server for debugging requests
|
||||
//$client->setProxy('127.0.0.1:8888');
|
||||
|
||||
// disable SSL certificate verification -> allow proxy to decode(view) request
|
||||
$client->setVerify(false);
|
||||
|
||||
//$client->setDebugRequests(true);
|
||||
|
||||
$this->client = $client;
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
46
app/main/lib/api/CcpClient.php
Normal file
46
app/main/lib/api/CcpClient.php
Normal file
@@ -0,0 +1,46 @@
|
||||
<?php
|
||||
/**
|
||||
* Created by PhpStorm.
|
||||
* User: Exodus 4D
|
||||
* Date: 26.12.2018
|
||||
* Time: 17:43
|
||||
*/
|
||||
|
||||
namespace lib\api;
|
||||
|
||||
use lib\Config;
|
||||
use Exodus4D\ESI\Client\ESI as Client;
|
||||
use Exodus4D\ESI\Client\ApiInterface;
|
||||
use Exodus4D\ESI\Client\EsiInterface;
|
||||
|
||||
|
||||
/**
|
||||
* Class CcpClient
|
||||
* @package lib\api
|
||||
*
|
||||
* @method EsiInterface getServerStatus()
|
||||
* @method EsiInterface getStatusForRoutes(string $version)
|
||||
*/
|
||||
class CcpClient extends AbstractClient {
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
const CLIENT_NAME = 'ccpClient';
|
||||
|
||||
/**
|
||||
* @param \Base $f3
|
||||
* @return ApiInterface|null
|
||||
*/
|
||||
protected function getClient(\Base $f3) : ?ApiInterface {
|
||||
$client = null;
|
||||
if(class_exists(Client::class)){
|
||||
$client = new Client(Config::getEnvironmentData('CCP_ESI_URL'));
|
||||
$client->setDataSource(Config::getEnvironmentData('CCP_ESI_DATASOURCE'));
|
||||
}else{
|
||||
$this->getLogger()->write($this->getMissingClassError(Client::class));
|
||||
}
|
||||
|
||||
return $client;
|
||||
}
|
||||
}
|
||||
36
app/main/lib/api/GitHubClient.php
Normal file
36
app/main/lib/api/GitHubClient.php
Normal file
@@ -0,0 +1,36 @@
|
||||
<?php
|
||||
/**
|
||||
* Created by PhpStorm.
|
||||
* User: Exodus 4D
|
||||
* Date: 29.01.2019
|
||||
* Time: 22:23
|
||||
*/
|
||||
|
||||
namespace lib\api;
|
||||
|
||||
use lib\Config;
|
||||
use Exodus4D\ESI\Client\Github as Client;
|
||||
use Exodus4D\ESI\Client\ApiInterface;
|
||||
|
||||
class GitHubClient extends AbstractClient {
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
const CLIENT_NAME = 'gitHubClient';
|
||||
|
||||
/**
|
||||
* @param \Base $f3
|
||||
* @return ApiInterface|null
|
||||
*/
|
||||
protected function getClient(\Base $f3) : ?ApiInterface {
|
||||
$client = null;
|
||||
if(class_exists(Client::class)){
|
||||
$client = new Client(Config::getPathfinderData('api.git_hub'));
|
||||
}else{
|
||||
$this->getLogger()->write($this->getMissingClassError(Client::class));
|
||||
}
|
||||
|
||||
return $client;
|
||||
}
|
||||
}
|
||||
36
app/main/lib/api/SsoClient.php
Normal file
36
app/main/lib/api/SsoClient.php
Normal file
@@ -0,0 +1,36 @@
|
||||
<?php
|
||||
/**
|
||||
* Created by PhpStorm.
|
||||
* User: Exodus 4D
|
||||
* Date: 26.12.2018
|
||||
* Time: 17:39
|
||||
*/
|
||||
|
||||
namespace lib\api;
|
||||
|
||||
use lib\Config;
|
||||
use Exodus4D\ESI\Client\SSO as Client;
|
||||
use Exodus4D\ESI\Client\ApiInterface;
|
||||
|
||||
class SsoClient extends AbstractClient {
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
const CLIENT_NAME = 'ssoClient';
|
||||
|
||||
/**
|
||||
* @param \Base $f3
|
||||
* @return ApiInterface|null
|
||||
*/
|
||||
protected function getClient(\Base $f3) : ?ApiInterface {
|
||||
$client = null;
|
||||
if(class_exists(Client::class)){
|
||||
$client = new Client(Config::getEnvironmentData('CCP_SSO_URL'));
|
||||
}else{
|
||||
$this->getLogger()->write($this->getMissingClassError(Client::class));
|
||||
}
|
||||
|
||||
return $client;
|
||||
}
|
||||
}
|
||||
@@ -1,90 +0,0 @@
|
||||
<?php
|
||||
/**
|
||||
* Created by PhpStorm.
|
||||
* User: Exodus4D
|
||||
* Date: 26.03.2017
|
||||
* Time: 19:17
|
||||
*/
|
||||
|
||||
namespace lib;
|
||||
|
||||
use controller\LogController;
|
||||
use \Exodus4D\ESI\ESI as ApiClient;
|
||||
|
||||
class CcpClient extends \Prefab {
|
||||
|
||||
private $apiClient;
|
||||
|
||||
public function __construct(\Base $f3){
|
||||
$this->apiClient = $this->getClient($f3);
|
||||
$f3->set('ccpClient', $this);
|
||||
}
|
||||
|
||||
/**
|
||||
* get ApiClient instance
|
||||
* @param \Base $f3
|
||||
* @return ApiClient|null
|
||||
*/
|
||||
protected function getClient(\Base $f3){
|
||||
$client = null;
|
||||
|
||||
if(class_exists(ApiClient::class)){
|
||||
$client = new ApiClient();
|
||||
$client->setUrl( Config::getEnvironmentData('CCP_ESI_URL') );
|
||||
$client->setDatasource( Config::getEnvironmentData('CCP_ESI_DATASOURCE') );
|
||||
$client->setUserAgent($this->getUserAgent());
|
||||
$client->setDebugLevel($f3->get('DEBUG'));
|
||||
//$client->setDebugLogRequests(true);
|
||||
}else{
|
||||
LogController::getLogger('ERROR')->write(sprintf(Config::ERROR_CLASS_NOT_EXISTS_COMPOSER, ApiClient::class));
|
||||
}
|
||||
|
||||
return $client;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
protected function getUserAgent(){
|
||||
$userAgent = '';
|
||||
$userAgent .= Config::getPathfinderData('name');
|
||||
$userAgent .= ' - ' . Config::getPathfinderData('version');
|
||||
$userAgent .= ' | ' . Config::getPathfinderData('contact');
|
||||
$userAgent .= ' (' . $_SERVER['SERVER_NAME'] . ')';
|
||||
|
||||
return $userAgent;
|
||||
}
|
||||
|
||||
/**
|
||||
* get error msg for undefined method in ApiClient() class
|
||||
* @param $method
|
||||
* @return string
|
||||
*/
|
||||
protected function getMissingMethodError($method){
|
||||
return "Method '" . $method . "()' not found in class '" . get_class($this->apiClient) . "'. -> Check installed Composer package version.'";
|
||||
}
|
||||
|
||||
/**
|
||||
* call request API data
|
||||
* @param $name
|
||||
* @param $arguments
|
||||
* @return array|mixed
|
||||
*/
|
||||
public function __call($name, $arguments){
|
||||
$return = [];
|
||||
|
||||
if(is_object($this->apiClient)){
|
||||
if( method_exists($this->apiClient, $name) ){
|
||||
$return = call_user_func_array([$this->apiClient, $name], $arguments);
|
||||
}else{
|
||||
LogController::getLogger('ERROR')->write($this->getMissingMethodError($name));
|
||||
\Base::instance()->error(501, $this->getMissingMethodError($name));
|
||||
}
|
||||
}else{
|
||||
LogController::getLogger('ERROR')->write(sprintf(Config::ERROR_CLASS_NOT_EXISTS_COMPOSER, ApiClient::class));
|
||||
\Base::instance()->error(501, sprintf(Config::ERROR_CLASS_NOT_EXISTS_COMPOSER, ApiClient::class));
|
||||
}
|
||||
|
||||
return $return;
|
||||
}
|
||||
}
|
||||
@@ -8,8 +8,10 @@
|
||||
|
||||
namespace lib;
|
||||
|
||||
use controller\LogController;
|
||||
use Exception;
|
||||
|
||||
use lib\api\CcpClient;
|
||||
use lib\api\GitHubClient;
|
||||
use lib\api\SsoClient;
|
||||
|
||||
class Config extends \Prefab {
|
||||
|
||||
@@ -20,8 +22,41 @@ class Config extends \Prefab {
|
||||
const CACHE_KEY_SOCKET_VALID = 'CACHED_SOCKET_VALID';
|
||||
const CACHE_TTL_SOCKET_VALID = 60;
|
||||
|
||||
const ERROR_CONF_PATHFINDER = 'Config value missing in pathfinder.ini file [%s]';
|
||||
const ERROR_CLASS_NOT_EXISTS_COMPOSER = 'Class "%s" not found. -> Check installed Composer packages';
|
||||
// ================================================================================================================
|
||||
// Redis
|
||||
// ================================================================================================================
|
||||
|
||||
/**
|
||||
* Redis connect timeout (seconds)
|
||||
*/
|
||||
const REDIS_OPT_TIMEOUT = 2;
|
||||
|
||||
/**
|
||||
* Redis read timeout (seconds)
|
||||
*/
|
||||
const REDIS_OPT_READ_TIMEOUT = 10;
|
||||
|
||||
/**
|
||||
* redis retry interval (milliseconds)
|
||||
*/
|
||||
const REDIS_OPT_RETRY_INTERVAL = 200;
|
||||
|
||||
// ================================================================================================================
|
||||
// EVE downtime
|
||||
// ================================================================================================================
|
||||
|
||||
/**
|
||||
* SSO downtime length (estimation), minutes
|
||||
*/
|
||||
const DOWNTIME_LENGTH = 8;
|
||||
|
||||
/**
|
||||
* SSO downtime buffer length extends downtime length, minutes
|
||||
*/
|
||||
const DOWNTIME_BUFFER = 1;
|
||||
|
||||
const ERROR_CLASS_NOT_EXISTS_COMPOSER = 'Class "%s" not found. → Check installed Composer packages';
|
||||
const ERROR_METHOD_NOT_EXISTS_COMPOSER = 'Method "%s()" not found in class "%s". → Check installed Composer packages';
|
||||
|
||||
|
||||
/**
|
||||
@@ -50,10 +85,21 @@ class Config extends \Prefab {
|
||||
// -> overwrites default configuration
|
||||
$this->setHiveVariables($f3);
|
||||
|
||||
// set global function for current DateTimeZone()
|
||||
$f3->set('getTimeZone', function() use ($f3){
|
||||
// set global getter for \DateTimeZone
|
||||
$f3->set('getTimeZone', function() use ($f3) : \DateTimeZone {
|
||||
return new \DateTimeZone( $f3->get('TZ') );
|
||||
});
|
||||
|
||||
// set global getter for new \DateTime
|
||||
$f3->set('getDateTime', function(string $time = 'now', ?\DateTimeZone $timeZone = null) use ($f3) : \DateTime {
|
||||
$timeZone = $timeZone ? : $f3->get('getTimeZone')();
|
||||
return new \DateTime($time, $timeZone);
|
||||
});
|
||||
|
||||
// lazy init Web Api clients
|
||||
$f3->set(SsoClient::CLIENT_NAME, SsoClient::instance());
|
||||
$f3->set(CcpClient::CLIENT_NAME, CcpClient::instance());
|
||||
$f3->set(GitHubClient::CLIENT_NAME, GitHubClient::instance());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -382,13 +428,8 @@ class Config extends \Prefab {
|
||||
*/
|
||||
static function getPathfinderData($key = ''){
|
||||
$hiveKey = self::HIVE_KEY_PATHFINDER . ($key ? '.' . strtoupper($key) : '');
|
||||
$data = null; // make sure it is always defined
|
||||
try{
|
||||
if( !\Base::instance()->exists($hiveKey, $data) ){
|
||||
throw new Exception\ConfigException(sprintf(self::ERROR_CONF_PATHFINDER, $hiveKey));
|
||||
}
|
||||
}catch (Exception\ConfigException $e){
|
||||
LogController::getLogger('ERROR')->write($e->getMessage());
|
||||
if( !\Base::instance()->exists($hiveKey, $data) ){
|
||||
$data = null;
|
||||
}
|
||||
return $data;
|
||||
}
|
||||
@@ -406,15 +447,41 @@ class Config extends \Prefab {
|
||||
return $status;
|
||||
}
|
||||
|
||||
/**
|
||||
* parse [D]ata [S]ource [N]ame string from *.ini into $conf parts
|
||||
* -> $dsn = redis=localhost:6379:2
|
||||
* $conf = ['type' => 'redis', 'host' => 'localhost', 'port' => 6379, 'db' => 2]
|
||||
* -> some $conf values might be NULL if not found in $dsn!
|
||||
* -> some missing values become defaults
|
||||
* @param string $dsn
|
||||
* @param array|null $conf
|
||||
* @return bool
|
||||
*/
|
||||
static function parseDSN(string $dsn, ?array &$conf = []) : bool {
|
||||
// reset reference
|
||||
if($matches = (bool)preg_match('/^(\w+)\h*=\h*(.+)/', strtolower(trim($dsn)), $parts)){
|
||||
$conf['type'] = $parts[1];
|
||||
if($conf['type'] == 'redis'){
|
||||
list($conf['host'], $conf['port'], $conf['db']) = explode(':', $parts[2]) + [1 => 6379, 2 => null];
|
||||
}elseif($conf['type'] == 'folder'){
|
||||
$conf['folder'] = $parts[2];
|
||||
}
|
||||
// int cast numeric values
|
||||
$conf = array_map(function($val){
|
||||
return is_numeric($val) ? intval($val) : $val;
|
||||
}, $conf);
|
||||
}
|
||||
return $matches;
|
||||
}
|
||||
|
||||
/**
|
||||
* check if a given DateTime() is within downTime range: downtime + 10m
|
||||
* -> can be used for prevent logging errors during downTime
|
||||
* @param \DateTime|null $dateCheck
|
||||
* @return bool
|
||||
* @throws Exception\DateException
|
||||
* @throws \Exception
|
||||
*/
|
||||
static function inDownTimeRange(\DateTime $dateCheck = null) : bool {
|
||||
$inRange = false;
|
||||
// default daily downtime 00:00am
|
||||
$downTimeParts = [0, 0];
|
||||
if( !empty($downTime = (string)self::getEnvironmentData('CCP_SSO_DOWNTIME')) ){
|
||||
@@ -425,19 +492,28 @@ class Config extends \Prefab {
|
||||
}
|
||||
}
|
||||
|
||||
// downTime Range is 10m
|
||||
$downtimeInterval = new \DateInterval('PT10M');
|
||||
$timezone = \Base::instance()->get('getTimeZone')();
|
||||
try{
|
||||
// downTime Range is 10m
|
||||
$downtimeLength = self::DOWNTIME_LENGTH + (2 * self::DOWNTIME_BUFFER);
|
||||
$timezone = \Base::instance()->get('getTimeZone')();
|
||||
|
||||
// if set -> use current time
|
||||
$dateCheck = is_null($dateCheck) ? new \DateTime('now', $timezone) : $dateCheck;
|
||||
$dateDowntimeStart = new \DateTime('now', $timezone);
|
||||
$dateDowntimeStart->setTime($downTimeParts[0],$downTimeParts[1]);
|
||||
$dateDowntimeEnd = clone $dateDowntimeStart;
|
||||
$dateDowntimeEnd->add($downtimeInterval);
|
||||
// if not set -> use current time
|
||||
$dateCheck = is_null($dateCheck) ? new \DateTime('now', $timezone) : $dateCheck;
|
||||
$dateDowntimeStart = new \DateTime('now', $timezone);
|
||||
$dateDowntimeStart->setTime($downTimeParts[0],$downTimeParts[1]);
|
||||
$dateDowntimeStart->sub(new \DateInterval('PT' . self::DOWNTIME_BUFFER . 'M'));
|
||||
|
||||
$dateRange = new DateRange($dateDowntimeStart, $dateDowntimeEnd);
|
||||
return $dateRange->inRange($dateCheck);
|
||||
$dateDowntimeEnd = clone $dateDowntimeStart;
|
||||
$dateDowntimeEnd->add(new \DateInterval('PT' . $downtimeLength . 'M'));
|
||||
|
||||
$dateRange = new DateRange($dateDowntimeStart, $dateDowntimeEnd);
|
||||
$inRange = $dateRange->inRange($dateCheck);
|
||||
}catch(\Exception $e){
|
||||
$f3 = \Base::instance();
|
||||
$f3->error(500, $e->getMessage(), $e->getTrace());
|
||||
}
|
||||
|
||||
return $inRange;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -198,7 +198,7 @@ abstract class AbstractLog implements LogInterface {
|
||||
* @param \stdClass|null $handlerParams
|
||||
* @return LogInterface
|
||||
*/
|
||||
public function addHandler(string $handlerKey, string $formatterKey = null, \stdClass $handlerParams = null): LogInterface{
|
||||
public function addHandler(string $handlerKey, string $formatterKey = null, \stdClass $handlerParams = null): LogInterface {
|
||||
if(!$this->hasHandlerKey($handlerKey)){
|
||||
$this->handlerConfig[$handlerKey] = $formatterKey;
|
||||
// add more configuration params for the new handler
|
||||
@@ -214,7 +214,7 @@ abstract class AbstractLog implements LogInterface {
|
||||
* @param string $handlerKey
|
||||
* @return LogInterface
|
||||
*/
|
||||
public function addHandlerGroup(string $handlerKey): LogInterface{
|
||||
public function addHandlerGroup(string $handlerKey) : LogInterface {
|
||||
if(
|
||||
$this->hasHandlerKey($handlerKey) &&
|
||||
!$this->hasHandlerGroupKey($handlerKey)
|
||||
@@ -227,7 +227,7 @@ abstract class AbstractLog implements LogInterface {
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function getHandlerConfig(): array{
|
||||
public function getHandlerConfig() : array{
|
||||
return $this->handlerConfig;
|
||||
}
|
||||
|
||||
@@ -237,7 +237,7 @@ abstract class AbstractLog implements LogInterface {
|
||||
* @return array
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function getHandlerParams(string $handlerKey): array{
|
||||
public function getHandlerParams(string $handlerKey) : array {
|
||||
$params = [];
|
||||
|
||||
if($this->hasHandlerKey($handlerKey)){
|
||||
@@ -267,14 +267,14 @@ abstract class AbstractLog implements LogInterface {
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function getHandlerParamsConfig(): array{
|
||||
public function getHandlerParamsConfig(): array {
|
||||
return $this->handlerParamsConfig;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function getProcessorConfig(): array{
|
||||
public function getProcessorConfig(): array {
|
||||
return $this->processorConfig;
|
||||
}
|
||||
|
||||
@@ -420,6 +420,9 @@ abstract class AbstractLog implements LogInterface {
|
||||
$params = [];
|
||||
if( !empty($conf = $this->handlerParamsConfig['stream']) ){
|
||||
$params[] = $conf->stream;
|
||||
$params[] = Logger::toMonologLevel($this->getLevel()); // min level that is handled;
|
||||
$params[] = true; // bubble
|
||||
$params[] = 0666; // permissions (default 644)
|
||||
}
|
||||
|
||||
return $params;
|
||||
|
||||
40
app/main/lib/logging/ApiLog.php
Normal file
40
app/main/lib/logging/ApiLog.php
Normal file
@@ -0,0 +1,40 @@
|
||||
<?php
|
||||
/**
|
||||
* Created by PhpStorm.
|
||||
* User: Exodus 4D
|
||||
* Date: 01.01.2019
|
||||
* Time: 16:42
|
||||
*/
|
||||
|
||||
namespace lib\logging;
|
||||
|
||||
|
||||
class ApiLog extends AbstractLog {
|
||||
|
||||
/**
|
||||
* List of possible handlers (tested)
|
||||
* -> final handler will be set dynamic for per instance
|
||||
* @var array
|
||||
*/
|
||||
protected $handlerConfig = [
|
||||
//'stream' => 'json'
|
||||
];
|
||||
|
||||
protected $channelType = 'api';
|
||||
|
||||
public function __construct(string $action, string $level){
|
||||
parent::__construct($action);
|
||||
|
||||
$this->setLevel($level);
|
||||
}
|
||||
|
||||
/**
|
||||
* overwrites parent
|
||||
* -> we need unique channelNames for different $actions within same $channelType
|
||||
* -> otherwise logs would be bundled into the first log file handler
|
||||
* @return string
|
||||
*/
|
||||
public function getChannelName(): string{
|
||||
return $this->getChannelType() . '_' . $this->getAction();
|
||||
}
|
||||
}
|
||||
@@ -15,6 +15,8 @@ interface LogInterface {
|
||||
|
||||
public function setLevel(string $level);
|
||||
|
||||
public function setTag(string $tag);
|
||||
|
||||
public function setData(array $data): LogInterface;
|
||||
|
||||
public function setTempData(array $data): LogInterface;
|
||||
@@ -61,4 +63,5 @@ interface LogInterface {
|
||||
|
||||
public function removeHandlerGroup(string $handlerKey);
|
||||
|
||||
public function buffer();
|
||||
}
|
||||
@@ -72,7 +72,7 @@ class RallyLog extends AbstractCharacterLog{
|
||||
}
|
||||
|
||||
// add human readable changes to string ---------------------------------------------------
|
||||
$data['formatted'] =$this->formatData($data);
|
||||
$data['formatted'] = $this->formatData($data);
|
||||
|
||||
return $data;
|
||||
}
|
||||
@@ -89,10 +89,10 @@ class RallyLog extends AbstractCharacterLog{
|
||||
!empty($data['channel'])
|
||||
){
|
||||
$replace = [
|
||||
'{objName}' => $data['object']['objName'],
|
||||
'{objId}' => $data['object']['objId'],
|
||||
'{objName}' => $data['object']['objName'],
|
||||
'{objId}' => $data['object']['objId'],
|
||||
'{channelName}' => $data['channel']['channelName'],
|
||||
'{channelId}' => $data['channel']['channelId']
|
||||
'{channelId}' => $data['channel']['channelId']
|
||||
];
|
||||
$string = str_replace(array_keys($replace), array_values($replace), $this->getMessage());
|
||||
}
|
||||
|
||||
@@ -130,4 +130,54 @@ class Util {
|
||||
sort($scopes);
|
||||
return md5(serialize($scopes));
|
||||
}
|
||||
|
||||
/**
|
||||
* get some information about a $source file/dir
|
||||
* @param string|null $source
|
||||
* @return array
|
||||
*/
|
||||
static function filesystemInfo(?string $source) : array {
|
||||
$info = [];
|
||||
if(is_dir($source)){
|
||||
$info['isDir'] = true;
|
||||
}elseif(is_file($source)){
|
||||
$info['isFile'] = true;
|
||||
}
|
||||
if(!empty($info)){
|
||||
$info['chmod'] = substr(sprintf('%o', fileperms($source)), -4);
|
||||
}
|
||||
return $info;
|
||||
}
|
||||
|
||||
/**
|
||||
* round DateTime to interval
|
||||
* @param \DateTime $dateTime
|
||||
* @param string $type
|
||||
* @param int $interval
|
||||
* @param string $round
|
||||
*/
|
||||
static function roundToInterval(\DateTime &$dateTime, string $type = 'sec', int $interval = 5, string $round = 'floor'){
|
||||
$hours = $minutes = $seconds = 0;
|
||||
|
||||
$roundInterval = function(string $format, int $interval, string $round) : int {
|
||||
return call_user_func($round, $format / $interval) * $interval;
|
||||
};
|
||||
|
||||
switch($type){
|
||||
case 'hour':
|
||||
$hours = $roundInterval($dateTime->format('H'), $interval, $round);
|
||||
break;
|
||||
case 'min':
|
||||
$hours = $dateTime->format('H');
|
||||
$minutes = $roundInterval($dateTime->format('i'), $interval, $round);
|
||||
break;
|
||||
case 'sec':
|
||||
$hours = $dateTime->format('H');
|
||||
$minutes = $dateTime->format('i');
|
||||
$seconds = $roundInterval($dateTime->format('s'), $interval, $round);
|
||||
break;
|
||||
}
|
||||
|
||||
$dateTime->setTime($hours, $minutes, $seconds);
|
||||
}
|
||||
}
|
||||
@@ -1,247 +0,0 @@
|
||||
<?php
|
||||
/**
|
||||
* Created by PhpStorm.
|
||||
* User: Exodus
|
||||
* Date: 12.03.2016
|
||||
* Time: 12:28
|
||||
*/
|
||||
|
||||
namespace lib;
|
||||
|
||||
use controller\LogController;
|
||||
|
||||
class Web extends \Web {
|
||||
|
||||
const ERROR_STATUS_LOG = 'HTTP %s: \'%s\' | url: %s \'%s\'%s';
|
||||
|
||||
/**
|
||||
* max number of curls calls for a single resource until giving up...
|
||||
* this is because SSO API is not very stable
|
||||
*/
|
||||
const RETRY_COUNT_MAX = 3;
|
||||
|
||||
/**
|
||||
* end of line
|
||||
* @var string
|
||||
*/
|
||||
private $eol = "\r\n";
|
||||
|
||||
/**
|
||||
* get status code from Header data array
|
||||
* @param array $headers
|
||||
* @return int
|
||||
*/
|
||||
protected function getStatusCodeFromHeaders($headers = []){
|
||||
$statusCode = 0;
|
||||
|
||||
if(
|
||||
preg_match(
|
||||
'/HTTP\/1\.\d (\d{3}?)/',
|
||||
implode($this->eol, $headers),
|
||||
$matches
|
||||
)
|
||||
){
|
||||
$statusCode = (int)$matches[1];
|
||||
}
|
||||
return $statusCode;
|
||||
}
|
||||
|
||||
/**
|
||||
* get cache time in seconds from Header data array
|
||||
* @param array $headers
|
||||
* @return int
|
||||
*/
|
||||
protected function getCacheTimeFromHeaders($headers = []){
|
||||
$cacheTime = 0;
|
||||
|
||||
if(
|
||||
preg_match(
|
||||
'/Cache-Control:(.*?)max-age=([0-9]+)/',
|
||||
implode($this->eol, $headers),
|
||||
$matches
|
||||
)
|
||||
){
|
||||
$cacheTime = (int)$matches[2];
|
||||
}elseif(
|
||||
preg_match(
|
||||
'/Access-Control-Max-Age: ([0-9]+)/',
|
||||
implode($this->eol, $headers),
|
||||
$matches
|
||||
)
|
||||
){
|
||||
$cacheTime = (int)$matches[1];
|
||||
}
|
||||
return $cacheTime;
|
||||
}
|
||||
|
||||
/**
|
||||
* get a unique cache kay for a request
|
||||
* @param $url
|
||||
* @param null $options
|
||||
* @return string
|
||||
*/
|
||||
protected function getCacheKey($url, $options = null){
|
||||
$f3 = \Base::instance();
|
||||
|
||||
$headers = isset($options['header']) ? implode($this->eol, (array) $options['header']) : '';
|
||||
|
||||
return $f3->hash(
|
||||
$options['method'] . ' '
|
||||
. $url . ' '
|
||||
. $headers
|
||||
) . 'url';
|
||||
}
|
||||
|
||||
/**
|
||||
* perform curl() request
|
||||
* -> caches response by returned HTTP Cache header data
|
||||
* @param string $url
|
||||
* @param array|null $options
|
||||
* @param array $additionalOptions
|
||||
* @param int $retryCount request counter for failed call
|
||||
* @return array|FALSE|mixed
|
||||
* @throws \Exception\DateException
|
||||
*/
|
||||
public function request($url,array $options = null, $additionalOptions = [], $retryCount = 0){
|
||||
$f3 = \Base::instance();
|
||||
|
||||
if( !$f3->exists( $hash = $this->getCacheKey($url, $options) ) ){
|
||||
// retry same request until request limit is reached
|
||||
$retry = false;
|
||||
|
||||
$result = parent::request($url, $options);
|
||||
$result['timeout'] = false;
|
||||
$statusCode = $this->getStatusCodeFromHeaders( $result['headers'] );
|
||||
|
||||
switch($statusCode){
|
||||
case 100:
|
||||
case 200:
|
||||
// request succeeded -> check if response should be cached
|
||||
$ttl = $this->getCacheTimeFromHeaders( $result['headers'] );
|
||||
|
||||
if(
|
||||
$ttl > 0 &&
|
||||
!empty( json_decode( $result['body'], true ) )
|
||||
){
|
||||
$f3->set($hash, $result, $ttl);
|
||||
}
|
||||
break;
|
||||
case 401:
|
||||
case 415:
|
||||
// unauthorized
|
||||
$errorMsg = $this->getErrorMessageFromJsonResponse(
|
||||
$statusCode,
|
||||
$options['method'],
|
||||
$url,
|
||||
json_decode($result['body'])
|
||||
);
|
||||
|
||||
// if request not within downTime time range -> log error
|
||||
if( !Config::inDownTimeRange() ){
|
||||
LogController::getLogger('ERROR')->write($errorMsg);
|
||||
}
|
||||
break;
|
||||
case 500:
|
||||
case 501:
|
||||
case 502:
|
||||
case 503:
|
||||
case 505:
|
||||
$retry = true;
|
||||
|
||||
if( $retryCount == self::RETRY_COUNT_MAX ){
|
||||
$errorMsg = $this->getErrorMessageFromJsonResponse(
|
||||
$statusCode,
|
||||
$options['method'],
|
||||
$url,
|
||||
json_decode($result['body'])
|
||||
);
|
||||
|
||||
// if request not within downTime time range -> log error
|
||||
if( !Config::inDownTimeRange() ){
|
||||
LogController::getLogger('ERROR')->write($errorMsg);
|
||||
}
|
||||
|
||||
// trigger error
|
||||
if($additionalOptions['suppressHTTPErrors'] !== true){
|
||||
$f3->error($statusCode, $errorMsg);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 504:
|
||||
case 0:
|
||||
$retry = true;
|
||||
|
||||
if( $retryCount == self::RETRY_COUNT_MAX ){
|
||||
// timeout -> response should not be cached
|
||||
$result['timeout'] = true;
|
||||
|
||||
$errorMsg = $this->getErrorMessageFromJsonResponse(
|
||||
504,
|
||||
$options['method'],
|
||||
$url,
|
||||
json_decode($result['body'])
|
||||
);
|
||||
|
||||
// if request not within downTime time range -> log error
|
||||
if( !Config::inDownTimeRange() ){
|
||||
LogController::getLogger('ERROR')->write($errorMsg);
|
||||
}
|
||||
|
||||
if($additionalOptions['suppressHTTPErrors'] !== true){
|
||||
$f3->error(504, $errorMsg);
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
// unknown status
|
||||
$errorMsg = $this->getErrorMessageFromJsonResponse(
|
||||
$statusCode,
|
||||
$options['method'],
|
||||
$url
|
||||
);
|
||||
|
||||
if( !Config::inDownTimeRange() ){
|
||||
LogController::getLogger('ERROR')->write($errorMsg);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if(
|
||||
$retry &&
|
||||
$retryCount < self::RETRY_COUNT_MAX
|
||||
){
|
||||
$retryCount++;
|
||||
$this->request($url, $options, $additionalOptions, $retryCount);
|
||||
}
|
||||
|
||||
}else{
|
||||
$result = $f3->get($hash);
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* get error message from response object
|
||||
* @param int $code
|
||||
* @param string $method
|
||||
* @param string $url
|
||||
* @param null|\stdClass $responseBody
|
||||
* @return string
|
||||
*/
|
||||
protected function getErrorMessageFromJsonResponse($code, $method, $url, $responseBody = null){
|
||||
if( empty($responseBody->message) ){
|
||||
$message = @constant('Base::HTTP_' . $code);
|
||||
}else{
|
||||
$message = $responseBody->message;
|
||||
}
|
||||
|
||||
$body = '';
|
||||
if( !is_null($responseBody) ){
|
||||
$body = ' | body: ' . print_r($responseBody, true);
|
||||
}
|
||||
|
||||
return sprintf(self::ERROR_STATUS_LOG, $code, $message, $method, $url, $body);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -141,7 +141,7 @@ class AllianceModel extends BasicModel {
|
||||
$alliance = parent::getById($id, $ttl, $isActive);
|
||||
if($alliance->isOutdated()){
|
||||
// request alliance data
|
||||
$allianceData = self::getF3()->ccpClient->getAllianceData($id);
|
||||
$allianceData = self::getF3()->ccpClient()->getAllianceData($id);
|
||||
if( !empty($allianceData) ){
|
||||
$alliance->copyfrom($allianceData, ['id', 'name', 'ticker']);
|
||||
$alliance->save();
|
||||
|
||||
@@ -11,7 +11,6 @@ namespace Model;
|
||||
use Controller\Ccp\Sso as Sso;
|
||||
use Controller\Api\User as User;
|
||||
use DB\SQL\Schema;
|
||||
use lib\Util;
|
||||
use lib\Config;
|
||||
use lib\Socket;
|
||||
use Model\Universe;
|
||||
@@ -23,7 +22,9 @@ class CharacterModel extends BasicModel {
|
||||
/**
|
||||
* cache key prefix for getData(); result WITH log data
|
||||
*/
|
||||
const DATA_CACHE_KEY_LOG = 'LOG';
|
||||
const DATA_CACHE_KEY_LOG = 'LOG';
|
||||
|
||||
const LOG_ACCESS = 'charId: [%20s], status: %s, charName: %s';
|
||||
|
||||
/**
|
||||
* character authorization status
|
||||
@@ -75,15 +76,15 @@ class CharacterModel extends BasicModel {
|
||||
'nullable' => false,
|
||||
'default' => ''
|
||||
],
|
||||
'crestAccessToken' => [
|
||||
'esiAccessToken' => [
|
||||
'type' => Schema::DT_VARCHAR256
|
||||
],
|
||||
'crestAccessTokenUpdated' => [
|
||||
'esiAccessTokenExpires' => [
|
||||
'type' => Schema::DT_TIMESTAMP,
|
||||
'default' => Schema::DF_CURRENT_TIMESTAMP,
|
||||
'index' => true
|
||||
],
|
||||
'crestRefreshToken' => [
|
||||
'esiRefreshToken' => [
|
||||
'type' => Schema::DT_VARCHAR256
|
||||
],
|
||||
'esiScopes' => [
|
||||
@@ -142,6 +143,11 @@ class CharacterModel extends BasicModel {
|
||||
'nullable' => false,
|
||||
'default' => 1
|
||||
],
|
||||
'selectLocation' => [
|
||||
'type' => Schema::DT_BOOL,
|
||||
'nullable' => false,
|
||||
'default' => 0
|
||||
],
|
||||
'securityStatus' => [
|
||||
'type' => Schema::DT_FLOAT,
|
||||
'nullable' => false,
|
||||
@@ -186,10 +192,7 @@ class CharacterModel extends BasicModel {
|
||||
$characterData->role = $this->roleId->getData();
|
||||
$characterData->shared = $this->shared;
|
||||
$characterData->logLocation = $this->logLocation;
|
||||
|
||||
if($this->authStatus){
|
||||
$characterData->authStatus = $this->authStatus;
|
||||
}
|
||||
$characterData->selectLocation = $this->selectLocation;
|
||||
|
||||
if($addCharacterLogData){
|
||||
if($logModel = $this->getLog()){
|
||||
@@ -213,6 +216,11 @@ class CharacterModel extends BasicModel {
|
||||
$this->updateCacheData($characterData, $cacheKeyModifier);
|
||||
}
|
||||
|
||||
// temp "authStatus" should not be cached
|
||||
if($this->authStatus){
|
||||
$characterData->authStatus = $this->authStatus;
|
||||
}
|
||||
|
||||
return $characterData;
|
||||
}
|
||||
|
||||
@@ -255,20 +263,6 @@ class CharacterModel extends BasicModel {
|
||||
return $ownerHash;
|
||||
}
|
||||
|
||||
/**
|
||||
* set API accessToken for current session
|
||||
* -> update "tokenUpdated" column on change
|
||||
* -> this is required for expire checking!
|
||||
* @param string $accessToken
|
||||
* @return string
|
||||
*/
|
||||
public function set_crestAccessToken($accessToken){
|
||||
if($this->crestAccessToken !== $accessToken){
|
||||
$this->touch('crestAccessTokenUpdated');
|
||||
}
|
||||
return $accessToken;
|
||||
}
|
||||
|
||||
/**
|
||||
* setter for "kicked" until time
|
||||
* @param $minutes
|
||||
@@ -478,47 +472,54 @@ class CharacterModel extends BasicModel {
|
||||
*/
|
||||
public function getAccessToken(){
|
||||
$accessToken = false;
|
||||
$refreshToken = true;
|
||||
|
||||
$timezone = self::getF3()->get('getTimeZone')();
|
||||
$now = new \DateTime('now', $timezone);
|
||||
|
||||
// check if there is already an "accessToken" for this user
|
||||
// check expire timer for stored "accessToken"
|
||||
if(
|
||||
!empty($this->crestAccessToken) &&
|
||||
!empty($this->crestAccessTokenUpdated)
|
||||
!empty($this->esiAccessToken) &&
|
||||
!empty($this->esiAccessTokenExpires)
|
||||
){
|
||||
$timezone = self::getF3()->get('getTimeZone')();
|
||||
$tokenTime = \DateTime::createFromFormat(
|
||||
$expireTime = \DateTime::createFromFormat(
|
||||
'Y-m-d H:i:s',
|
||||
$this->crestAccessTokenUpdated,
|
||||
$this->esiAccessTokenExpires,
|
||||
$timezone
|
||||
);
|
||||
// add expire time buffer for this "accessToken"
|
||||
// token should be marked as "deprecated" BEFORE it actually expires.
|
||||
$timeBuffer = 2 * 60;
|
||||
$tokenTime->add(new \DateInterval('PT' . (Sso::ACCESS_KEY_EXPIRE_TIME - $timeBuffer) . 'S'));
|
||||
|
||||
$now = new \DateTime('now', $timezone);
|
||||
if($tokenTime->getTimestamp() > $now->getTimestamp()){
|
||||
$accessToken = $this->crestAccessToken;
|
||||
// check if token is not expired
|
||||
if($expireTime->getTimestamp() > $now->getTimestamp()){
|
||||
// token still valid
|
||||
$accessToken = $this->esiAccessToken;
|
||||
|
||||
// check if token should be renewed (close to expire)
|
||||
$timeBuffer = 2 * 60;
|
||||
$expireTime->sub(new \DateInterval('PT' . $timeBuffer . 'S'));
|
||||
|
||||
if($expireTime->getTimestamp() > $now->getTimestamp()){
|
||||
// token NOT close to expire
|
||||
$refreshToken = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// if no "accessToken" was found -> get a fresh one by an existing "refreshToken"
|
||||
// no valid "accessToken" found OR
|
||||
// existing token is close to expire
|
||||
// -> get a fresh one by an existing "refreshToken"
|
||||
// -> in case request for new token fails (e.g. timeout) and old token is still valid -> keep old token
|
||||
if(
|
||||
!$accessToken &&
|
||||
!empty($this->crestRefreshToken)
|
||||
$refreshToken &&
|
||||
!empty($this->esiRefreshToken)
|
||||
){
|
||||
// no accessToken found OR token is deprecated
|
||||
$ssoController = new Sso();
|
||||
$accessData = $ssoController->refreshAccessToken($this->crestRefreshToken);
|
||||
$accessData = $ssoController->refreshAccessToken($this->esiRefreshToken);
|
||||
|
||||
if(
|
||||
isset($accessData->accessToken) &&
|
||||
isset($accessData->refreshToken)
|
||||
){
|
||||
$this->crestAccessToken = $accessData->accessToken;
|
||||
if(isset($accessData->accessToken, $accessData->esiAccessTokenExpires, $accessData->refreshToken)){
|
||||
$this->esiAccessToken = $accessData->accessToken;
|
||||
$this->esiAccessTokenExpires = $accessData->esiAccessTokenExpires;
|
||||
$this->save();
|
||||
|
||||
$accessToken = $this->crestAccessToken;
|
||||
$accessToken = $this->esiAccessToken;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -529,24 +530,59 @@ class CharacterModel extends BasicModel {
|
||||
* check if character is currently kicked
|
||||
* @return bool
|
||||
*/
|
||||
public function isKicked(){
|
||||
public function isKicked() : bool {
|
||||
$kicked = false;
|
||||
if( !is_null($this->kicked) ){
|
||||
$kickedUntil = new \DateTime();
|
||||
$kickedUntil->setTimestamp( (int)strtotime($this->kicked) );
|
||||
$now = new \DateTime();
|
||||
$kicked = ($kickedUntil > $now);
|
||||
try{
|
||||
$kickedUntil = new \DateTime();
|
||||
$kickedUntil->setTimestamp( (int)strtotime($this->kicked) );
|
||||
$now = new \DateTime();
|
||||
$kicked = ($kickedUntil > $now);
|
||||
}catch(\Exception $e){
|
||||
self::getF3()->error(500, $e->getMessage(), $e->getTrace());
|
||||
}
|
||||
}
|
||||
|
||||
return $kicked;
|
||||
}
|
||||
|
||||
/**
|
||||
* checks whether this character is authorized to log in
|
||||
* -> check corp/ally whitelist config (pathfinder.ini)
|
||||
* checks whether this character is currently logged in
|
||||
* @return bool
|
||||
*/
|
||||
public function isAuthorized(){
|
||||
public function checkLoginTimer() : bool {
|
||||
$loginCheck = false;
|
||||
|
||||
if( !$this->dry() && $this->lastLogin ){
|
||||
// get max login time (minutes) from config
|
||||
$maxLoginMinutes = (int)Config::getPathfinderData('timer.logged');
|
||||
if($maxLoginMinutes){
|
||||
$timezone = self::getF3()->get('getTimeZone')();
|
||||
try{
|
||||
$now = new \DateTime('now', $timezone);
|
||||
$logoutTime = new \DateTime($this->lastLogin, $timezone);
|
||||
$logoutTime->add(new \DateInterval('PT' . $maxLoginMinutes . 'M'));
|
||||
if($logoutTime->getTimestamp() > $now->getTimestamp()){
|
||||
$loginCheck = true;
|
||||
}
|
||||
}catch(\Exception $e){
|
||||
self::getF3()->error(500, $e->getMessage(), $e->getTrace());
|
||||
}
|
||||
}else{
|
||||
// no "max login" timer configured -> character still logged in
|
||||
$loginCheck = true;
|
||||
}
|
||||
}
|
||||
|
||||
return $loginCheck;
|
||||
}
|
||||
|
||||
/**
|
||||
* checks whether this character is authorized to log in
|
||||
* -> check corp/ally whitelist config (pathfinder.ini)
|
||||
* @return string
|
||||
*/
|
||||
public function isAuthorized() : string {
|
||||
$authStatus = 'UNKNOWN';
|
||||
|
||||
// check whether character is banned or temp kicked
|
||||
@@ -714,12 +750,12 @@ class CharacterModel extends BasicModel {
|
||||
){
|
||||
// Try to pull data from API
|
||||
if( $accessToken = $this->getAccessToken() ){
|
||||
$onlineData = self::getF3()->ccpClient->getCharacterOnlineData($this->_id, $accessToken, $additionalOptions);
|
||||
$onlineData = self::getF3()->ccpClient()->getCharacterOnlineData($this->_id, $accessToken);
|
||||
|
||||
// check whether character is currently ingame online
|
||||
if(is_bool($onlineData['online'])){
|
||||
if($onlineData['online'] === true){
|
||||
$locationData = self::getF3()->ccpClient->getCharacterLocationData($this->_id, $accessToken, $additionalOptions);
|
||||
$locationData = self::getF3()->ccpClient()->getCharacterLocationData($this->_id, $accessToken);
|
||||
|
||||
if( !empty($locationData['system']['id']) ){
|
||||
// character is currently in-game
|
||||
@@ -763,7 +799,7 @@ class CharacterModel extends BasicModel {
|
||||
// get "more" data for systemId and/or stationId -----------------------------------------
|
||||
if( !empty($lookupUniverseIds) ){
|
||||
// get "more" information for some Ids (e.g. name)
|
||||
$universeData = self::getF3()->ccpClient->getUniverseNamesData($lookupUniverseIds, $additionalOptions);
|
||||
$universeData = self::getF3()->ccpClient()->getUniverseNamesData($lookupUniverseIds);
|
||||
|
||||
if( !empty($universeData) && !isset($universeData['error']) ){
|
||||
// We expect max ONE system AND/OR station data, not an array of e.g. systems
|
||||
@@ -816,7 +852,7 @@ class CharacterModel extends BasicModel {
|
||||
|
||||
// check ship data for changes ------------------------------------------------------------
|
||||
if( !$deleteLog ){
|
||||
$shipData = self::getF3()->ccpClient->getCharacterShipData($this->_id, $accessToken, $additionalOptions);
|
||||
$shipData = self::getF3()->ccpClient()->getCharacterShipData($this->_id, $accessToken);
|
||||
|
||||
// IDs for "shipTypeId" that require more data
|
||||
$lookupShipTypeId = 0;
|
||||
@@ -887,35 +923,8 @@ class CharacterModel extends BasicModel {
|
||||
$deleteLog = true;
|
||||
}
|
||||
|
||||
//in case of failure (invalid API response) increase or reset "retry counter"
|
||||
if( $user = $this->getUser() ){
|
||||
// Session data does not exists in CLI mode (Cronjob)
|
||||
if( $sessionCharacterData = $user->getSessionCharacterData($this->id, false) ){
|
||||
$updateRetry = (int)$sessionCharacterData['UPDATE_RETRY'];
|
||||
$newRetry = $updateRetry;
|
||||
if($invalidResponse){
|
||||
$newRetry++;
|
||||
|
||||
if($newRetry >= 3){
|
||||
// no proper character log data (3 fails in a row))
|
||||
$newRetry = 0;
|
||||
$deleteLog = true;
|
||||
}
|
||||
}else{
|
||||
// reset retry counter
|
||||
$newRetry = 0;
|
||||
}
|
||||
|
||||
if($updateRetry !== $newRetry){
|
||||
// update retry counter
|
||||
$sessionCharacterData['UPDATE_RETRY'] = $newRetry;
|
||||
$sessionCharacters = self::mergeSessionCharacterData([$sessionCharacterData]);
|
||||
self::getF3()->set(User::SESSION_KEY_CHARACTERS, $sessionCharacters);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if($deleteLog){
|
||||
self::log('DELETE LOG!');
|
||||
$this->deleteLog();
|
||||
}
|
||||
|
||||
@@ -945,14 +954,14 @@ class CharacterModel extends BasicModel {
|
||||
// -> the "id" check is just for security and should NEVER fail!
|
||||
$ssoController = new Sso();
|
||||
if(
|
||||
!is_null( $verificationCharacterData = $ssoController->verifyCharacterData($accessToken) ) &&
|
||||
$verificationCharacterData->CharacterID === $this->_id
|
||||
!empty( $verificationCharacterData = $ssoController->verifyCharacterData($accessToken) ) &&
|
||||
$verificationCharacterData['characterId'] === $this->_id
|
||||
){
|
||||
// get character data from API
|
||||
$characterData = $ssoController->getCharacterData($this->_id);
|
||||
if( !empty($characterData->character) ){
|
||||
$characterData->character['ownerHash'] = $verificationCharacterData->CharacterOwnerHash;
|
||||
$characterData->character['esiScopes'] = Util::convertScopesString($verificationCharacterData->Scopes);
|
||||
$characterData->character['ownerHash'] = $verificationCharacterData['characterOwnerHash'];
|
||||
$characterData->character['esiScopes'] = $verificationCharacterData['scopes'];
|
||||
|
||||
$this->copyfrom($characterData->character, ['ownerHash', 'esiScopes', 'securityStatus']);
|
||||
$this->corporationId = $characterData->corporation;
|
||||
@@ -1105,7 +1114,7 @@ class CharacterModel extends BasicModel {
|
||||
}
|
||||
|
||||
// delete auth cookie data ------------------------------------------------------------------------------------
|
||||
if($deleteCookie ){
|
||||
if($deleteCookie){
|
||||
$this->deleteAuthentications();
|
||||
}
|
||||
}
|
||||
@@ -1115,7 +1124,7 @@ class CharacterModel extends BasicModel {
|
||||
* @param array $characterDataBase
|
||||
* @return array
|
||||
*/
|
||||
public static function mergeSessionCharacterData(array $characterDataBase = []){
|
||||
public static function mergeSessionCharacterData(array $characterDataBase = []) : array {
|
||||
$addData = [];
|
||||
// get current session characters to be merged with
|
||||
$characterData = (array)self::getF3()->get(User::SESSION_KEY_CHARACTERS);
|
||||
|
||||
@@ -284,7 +284,7 @@ class CorporationModel extends BasicModel {
|
||||
!empty($accessToken) &&
|
||||
!$this->isNPC
|
||||
){
|
||||
$response = self::getF3()->ccpClient->getCorporationRoles($this->_id, $accessToken);
|
||||
$response = self::getF3()->ccpClient()->getCorporationRoles($this->_id, $accessToken);
|
||||
if( !empty($response['roles']) ){
|
||||
$characterRolesData = (array)$response['roles'];
|
||||
}
|
||||
@@ -351,10 +351,10 @@ class CorporationModel extends BasicModel {
|
||||
$corporation = parent::getById($id, $ttl, $isActive);
|
||||
if($corporation->isOutdated()){
|
||||
// request corporation data
|
||||
$corporationData = self::getF3()->ccpClient->getCorporationData($id);
|
||||
$corporationData = self::getF3()->ccpClient()->getCorporationData($id);
|
||||
if( !empty($corporationData) ){
|
||||
// check for NPC corporation
|
||||
$corporationData['isNPC'] = self::getF3()->ccpClient->isNpcCorporation($id);
|
||||
$corporationData['isNPC'] = self::getF3()->ccpClient()->isNpcCorporation($id);
|
||||
|
||||
$corporation->copyfrom($corporationData, ['id', 'name', 'ticker', 'memberCount', 'isNPC']);
|
||||
$corporation->save();
|
||||
|
||||
@@ -1020,7 +1020,7 @@ class MapModel extends AbstractMapTrackingModel {
|
||||
* get object relevant data for model log channel
|
||||
* @return array
|
||||
*/
|
||||
public function getLogChannelData() : array{
|
||||
public function getLogChannelData() : array {
|
||||
return [
|
||||
'channelId' => $this->_id,
|
||||
'channelName' => $this->name
|
||||
@@ -1030,13 +1030,17 @@ class MapModel extends AbstractMapTrackingModel {
|
||||
* get object relevant data for model log object
|
||||
* @return array
|
||||
*/
|
||||
public function getLogObjectData() : array{
|
||||
public function getLogObjectData() : array {
|
||||
return [
|
||||
'objId' => $this->_id,
|
||||
'objName' => $this->name
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* map log formatter callback
|
||||
* @return \Closure
|
||||
*/
|
||||
protected function getLogFormatter(){
|
||||
return function(&$rowDataObj){
|
||||
unset($rowDataObj['extra']);
|
||||
@@ -1258,9 +1262,18 @@ class MapModel extends AbstractMapTrackingModel {
|
||||
* @param int $limit
|
||||
* @return array
|
||||
*/
|
||||
public function getLogData(int $offset = FileHandler::LOG_FILE_OFFSET, int $limit = FileHandler::LOG_FILE_LIMIT): array {
|
||||
public function getLogData(int $offset = FileHandler::LOG_FILE_OFFSET, int $limit = FileHandler::LOG_FILE_LIMIT) : array {
|
||||
$streamConf = $this->getStreamConfig();
|
||||
return FileHandler::readLogFile($streamConf->stream, $offset, $limit, $this->getLogFormatter());
|
||||
|
||||
$rowFormatter = $this->getLogFormatter();
|
||||
$rowParser = function(string &$rowData, array &$data) use ($rowFormatter){
|
||||
if( !empty($rowDataObj = (array)json_decode($rowData, true)) ){
|
||||
$rowFormatter($rowDataObj);
|
||||
$data[] = $rowDataObj;
|
||||
}
|
||||
};
|
||||
|
||||
return FileHandler::instance()->readFileReverse($streamConf->stream, $offset, $limit, $rowParser);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -10,7 +10,7 @@ namespace Model;
|
||||
|
||||
use DB\SQL\Schema;
|
||||
|
||||
class MapTypeModel extends BasicModel{
|
||||
class MapTypeModel extends BasicModel {
|
||||
|
||||
protected $table = 'map_type';
|
||||
|
||||
|
||||
@@ -665,7 +665,7 @@ class SystemModel extends AbstractMapTrackingModel {
|
||||
public function sendRallyPoke(array $rallyData, CharacterModel $characterModel){
|
||||
// rally log needs at least one handler to be valid
|
||||
$isValidLog = false;
|
||||
$log = new Logging\RallyLog('rallySet', $this->getMap()->getLogChannelData());
|
||||
$log = new logging\RallyLog('rallySet', $this->getMap()->getLogChannelData());
|
||||
|
||||
// Slack poke -----------------------------------------------------------------------------
|
||||
$slackChannelKey = 'slackChannelRally';
|
||||
|
||||
@@ -109,7 +109,7 @@ class CategoryModel extends BasicUniverseModel {
|
||||
* @param array $additionalOptions
|
||||
*/
|
||||
protected function loadData(int $id, string $accessToken = '', array $additionalOptions = []){
|
||||
$data = self::getF3()->ccpClient->getUniverseCategoryData($id);
|
||||
$data = self::getF3()->ccpClient()->getUniverseCategoryData($id);
|
||||
if(!empty($data)){
|
||||
$this->copyfrom($data, ['id', 'name', 'published']);
|
||||
$this->save();
|
||||
@@ -125,7 +125,7 @@ class CategoryModel extends BasicUniverseModel {
|
||||
public function loadGroupsData(int $offset = 0, int $length = 0) : array {
|
||||
$groupIds = [];
|
||||
if( !$this->dry() ){
|
||||
$data = self::getF3()->ccpClient->getUniverseCategoryData($this->_id);
|
||||
$data = self::getF3()->ccpClient()->getUniverseCategoryData($this->_id);
|
||||
if(!empty($data)){
|
||||
array_multisort($data['groups'], SORT_ASC, SORT_NUMERIC);
|
||||
if($length){
|
||||
|
||||
@@ -86,7 +86,7 @@ class ConstellationModel extends BasicUniverseModel {
|
||||
* @param array $additionalOptions
|
||||
*/
|
||||
protected function loadData(int $id, string $accessToken = '', array $additionalOptions = []){
|
||||
$data = self::getF3()->ccpClient->getUniverseConstellationData($id);
|
||||
$data = self::getF3()->ccpClient()->getUniverseConstellationData($id);
|
||||
if(!empty($data)){
|
||||
/**
|
||||
* @var $region RegionModel
|
||||
@@ -105,7 +105,7 @@ class ConstellationModel extends BasicUniverseModel {
|
||||
*/
|
||||
public function loadSystemsData(){
|
||||
if( !$this->dry() ){
|
||||
$data = self::getF3()->ccpClient->getUniverseConstellationData($this->_id);
|
||||
$data = self::getF3()->ccpClient()->getUniverseConstellationData($this->_id);
|
||||
if(!empty($data)){
|
||||
foreach((array)$data['systems'] as $systemId){
|
||||
/**
|
||||
|
||||
@@ -111,7 +111,7 @@ class GroupModel extends BasicUniverseModel {
|
||||
* @param array $additionalOptions
|
||||
*/
|
||||
protected function loadData(int $id, string $accessToken = '', array $additionalOptions = []){
|
||||
$data = self::getF3()->ccpClient->getUniverseGroupData($id);
|
||||
$data = self::getF3()->ccpClient()->getUniverseGroupData($id);
|
||||
if(!empty($data)){
|
||||
/**
|
||||
* @var $category CategoryModel
|
||||
@@ -132,7 +132,7 @@ class GroupModel extends BasicUniverseModel {
|
||||
public function loadTypesData(){
|
||||
$count = 0;
|
||||
if( !$this->dry() ){
|
||||
$data = self::getF3()->ccpClient->getUniverseGroupData($this->_id);
|
||||
$data = self::getF3()->ccpClient()->getUniverseGroupData($this->_id);
|
||||
if(!empty($data)){
|
||||
foreach((array)$data['types'] as $typeId){
|
||||
/**
|
||||
|
||||
@@ -96,7 +96,7 @@ class PlanetModel extends BasicUniverseModel {
|
||||
* @param array $additionalOptions
|
||||
*/
|
||||
protected function loadData(int $id, string $accessToken = '', array $additionalOptions = []){
|
||||
$data = self::getF3()->ccpClient->getUniversePlanetData($id);
|
||||
$data = self::getF3()->ccpClient()->getUniversePlanetData($id);
|
||||
if(!empty($data)){
|
||||
/**
|
||||
* @var $system SystemModel
|
||||
|
||||
@@ -46,7 +46,7 @@ class RegionModel extends BasicUniverseModel {
|
||||
* @param array $additionalOptions
|
||||
*/
|
||||
protected function loadData(int $id, string $accessToken = '', array $additionalOptions = []){
|
||||
$data = self::getF3()->ccpClient->getUniverseRegionData($id);
|
||||
$data = self::getF3()->ccpClient()->getUniverseRegionData($id);
|
||||
if(!empty($data)){
|
||||
$this->copyfrom($data, ['id', 'name', 'description']);
|
||||
$this->save();
|
||||
@@ -58,7 +58,7 @@ class RegionModel extends BasicUniverseModel {
|
||||
*/
|
||||
public function loadConstellationsData(){
|
||||
if( !$this->dry() ){
|
||||
$data = self::getF3()->ccpClient->getUniverseRegionData($this->_id);
|
||||
$data = self::getF3()->ccpClient()->getUniverseRegionData($this->_id);
|
||||
if(!empty($data)){
|
||||
foreach((array)$data['constellations'] as $constellationsId){
|
||||
/**
|
||||
|
||||
@@ -104,7 +104,7 @@ class StargateModel extends BasicUniverseModel {
|
||||
* @param array $additionalOptions
|
||||
*/
|
||||
protected function loadData(int $id, string $accessToken = '', array $additionalOptions = []){
|
||||
$data = self::getF3()->ccpClient->getUniverseStargateData($id);
|
||||
$data = self::getF3()->ccpClient()->getUniverseStargateData($id);
|
||||
|
||||
if(!empty($data)){
|
||||
|
||||
|
||||
@@ -80,7 +80,7 @@ class StarModel extends BasicUniverseModel {
|
||||
* @param array $additionalOptions
|
||||
*/
|
||||
protected function loadData(int $id, string $accessToken = '', array $additionalOptions = []){
|
||||
$data = self::getF3()->ccpClient->getUniverseStarData($id);
|
||||
$data = self::getF3()->ccpClient()->getUniverseStarData($id);
|
||||
if(!empty($data)){
|
||||
/**
|
||||
* @var $type TypeModel
|
||||
|
||||
@@ -77,7 +77,7 @@ class StructureModel extends BasicUniverseModel {
|
||||
* @param array $additionalOptions
|
||||
*/
|
||||
protected function loadData(int $id, string $accessToken = '', array $additionalOptions = []){
|
||||
$data = self::getF3()->ccpClient->getUniverseStructureData($id, $accessToken, $additionalOptions);
|
||||
$data = self::getF3()->ccpClient()->getUniverseStructureData($id, $accessToken);
|
||||
if(!empty($data)){
|
||||
/**
|
||||
* @var $type TypeModel
|
||||
|
||||
@@ -333,7 +333,7 @@ class SystemModel extends BasicUniverseModel {
|
||||
* @param array $additionalOptions
|
||||
*/
|
||||
protected function loadData(int $id, string $accessToken = '', array $additionalOptions = []){
|
||||
$data = self::getF3()->ccpClient->getUniverseSystemData($id);
|
||||
$data = self::getF3()->ccpClient()->getUniverseSystemData($id);
|
||||
|
||||
if(!empty($data)){
|
||||
/**
|
||||
@@ -363,7 +363,7 @@ class SystemModel extends BasicUniverseModel {
|
||||
*/
|
||||
public function loadPlanetsData(){
|
||||
if( !$this->dry() ){
|
||||
$data = self::getF3()->ccpClient->getUniverseSystemData($this->_id);
|
||||
$data = self::getF3()->ccpClient()->getUniverseSystemData($this->_id);
|
||||
if($data['planets']){
|
||||
// planets are optional since ESI v4 (e.g. Abyssal systems)
|
||||
foreach((array)$data['planets'] as $planetData){
|
||||
@@ -384,7 +384,7 @@ class SystemModel extends BasicUniverseModel {
|
||||
*/
|
||||
public function loadStargatesData(){
|
||||
if( !$this->dry() ){
|
||||
$data = self::getF3()->ccpClient->getUniverseSystemData($this->_id);
|
||||
$data = self::getF3()->ccpClient()->getUniverseSystemData($this->_id);
|
||||
if(!empty($data)){
|
||||
foreach((array)$data['stargates'] as $stargateId){
|
||||
/**
|
||||
|
||||
@@ -136,7 +136,7 @@ class TypeModel extends BasicUniverseModel {
|
||||
* @param array $additionalOptions
|
||||
*/
|
||||
protected function loadData(int $id, string $accessToken = '', array $additionalOptions = []){
|
||||
$data = self::getF3()->ccpClient->getUniverseTypesData($id, $additionalOptions);
|
||||
$data = self::getF3()->ccpClient()->getUniverseTypesData($id);
|
||||
if(!empty($data)){
|
||||
$group = $this->rel('groupId');
|
||||
$group->loadById($data['groupId'], $accessToken, $additionalOptions);
|
||||
|
||||
@@ -1,54 +1,116 @@
|
||||
; Main Config
|
||||
; Pathfinder Config
|
||||
|
||||
[PATHFINDER]
|
||||
; Name of installation
|
||||
; This can be changed to any name
|
||||
; This name is used in e.g. emails, user interface
|
||||
; Syntax: String
|
||||
; Default: Pathfinder
|
||||
NAME = Pathfinder
|
||||
; installed version (used for CSS/JS cache busting)
|
||||
VERSION = v1.4.3
|
||||
; contact information [optional]
|
||||
|
||||
; Pathfinder version
|
||||
; Version number should not be changed manually.
|
||||
; Version is used for CSS/JS cache busting and is part of the URL for static resources:
|
||||
; e.g. public/js/vX.X.X/app.js
|
||||
; Syntax: String (current version)
|
||||
; Default: v1.5.0
|
||||
VERSION = v1.5.0
|
||||
|
||||
; Contact information [optional]
|
||||
; Shown on 'licence', 'contact' page.
|
||||
; Syntax: String
|
||||
; Default: https://github.com/exodus4d
|
||||
CONTACT = https://github.com/exodus4d
|
||||
; public contact email [optional]
|
||||
|
||||
; Public contact email [optional]
|
||||
; Syntax: String
|
||||
; Default:
|
||||
EMAIL =
|
||||
; source code [optional]
|
||||
|
||||
; Repository URL [optional]
|
||||
; Used for 'licence', 'contact' page.
|
||||
; Syntax: String
|
||||
; Default: https://github.com/exodus4d/pathfinder
|
||||
REPO = https://github.com/exodus4d/pathfinder
|
||||
|
||||
; show warning on "login" form if /setup route is active
|
||||
; DO NOT disable this warning unless /setup route is protected by e.g. WebAuth
|
||||
; Show warning on 'login' page if /setup route is active
|
||||
; DO NOT disable this warning unless /setup route is protected or commented in routes.ini
|
||||
; Syntax: 0 | 1
|
||||
; Default: 1
|
||||
SHOW_SETUP_WARNING = 1
|
||||
|
||||
; show complete login page
|
||||
; if disabled, some section dont appear (Slideshow, Features, Admin, Install, About) (default: 1)
|
||||
; Show full login page
|
||||
; If disabled, some section don´t appear:
|
||||
; 'Slideshow', 'Features', 'Admin', 'Install', 'About'
|
||||
; Syntax: 0 | 1
|
||||
; Default: 1
|
||||
SHOW_COMPLETE_LOGIN_PAGE = 1
|
||||
|
||||
; REGISTRATION ====================================================================================
|
||||
[PATHFINDER.REGISTRATION]
|
||||
; registration status (for new users) (0=disabled, 1=enabled)
|
||||
; Registration status (for new users)
|
||||
; If disabled, users can no longer register a new account on this installation.
|
||||
; Syntax: 0 | 1
|
||||
; Default: 1
|
||||
STATUS = 1
|
||||
|
||||
[PATHFINDER.LOGIN]
|
||||
; expire time (in days) for login cookies
|
||||
; Expire time for login cookies
|
||||
; Login Cookie information send by clients is re-validated by the server.
|
||||
; The expire time for each cookie is stored in DB. Expired Cookies become invalid.
|
||||
; Syntax: Integer (days)
|
||||
; Default: 30
|
||||
COOKIE_EXPIRE = 30
|
||||
|
||||
; shows "scheduled maintenance" warning to users (default: 0)
|
||||
; Show 'scheduled maintenance' warning
|
||||
; If enabled, active users will see a notification panel.
|
||||
; This can be used to inform users about upcoming maintenance shutdown.
|
||||
; This flag can be enabled "on the fly" (no page reload required to see the notice).
|
||||
; Syntax: 0 | 1
|
||||
; Default: 0
|
||||
MODE_MAINTENANCE = 0
|
||||
|
||||
; restrict login to specific corporations/alliances by id (e.g. 1000166,1000080)
|
||||
; Login restrictions (white lists)
|
||||
; Login/registration can be restricted to specific groups.
|
||||
; Use comma separated strings for CCP Ids (e.g. 1000166,1000080).
|
||||
; If no groups are specified, all characters are allowed.
|
||||
; Syntax: String (comma separated)
|
||||
; Default:
|
||||
CHARACTER =
|
||||
CORPORATION =
|
||||
ALLIANCE =
|
||||
|
||||
; Slack API integration ===========================================================================
|
||||
[PATHFINDER.SLACK]
|
||||
; Global Slack API status, check PATHFINDER.MAP section for individual control (0=disabled, 1=enabled)
|
||||
STATUS = 1
|
||||
[PATHFINDER.CHARACTER]
|
||||
; Auto location select for characters
|
||||
; If enabled, characters can activate the "auto location select" checkbox in their account settings.
|
||||
; If checkbox active, solar systems get auto selected on map based on their current system.
|
||||
; Hint: This can increase server load because of more client requests.
|
||||
; Syntax: 0 | 1
|
||||
; Default: 1
|
||||
AUTO_LOCATION_SELECT = 1
|
||||
|
||||
; Slack API integration ===========================================================================
|
||||
[PATHFINDER.SLACK]
|
||||
; Slack API status
|
||||
; This is a global toggle for all Slack related features.
|
||||
; Check PATHFINDER.MAP section for individual control.
|
||||
; Syntax: 0 | 1
|
||||
; Default: 1
|
||||
STATUS = 1
|
||||
|
||||
; Discord API integration =========================================================================
|
||||
[PATHFINDER.DISCORD]
|
||||
; Global Discord API status, check PATHFINDER.MAP section for individual control (0=disabled, 1=enabled)
|
||||
; Discord API status
|
||||
; This is a global toggle for all Discord related features.
|
||||
; Check PATHFINDER.MAP section for individual control.
|
||||
; Syntax: 0 | 1
|
||||
; Default: 1
|
||||
STATUS = 1
|
||||
|
||||
; View ============================================================================================
|
||||
[PATHFINDER.VIEW]
|
||||
; static page templates
|
||||
; Page templates
|
||||
; Hint: You should not change this.
|
||||
INDEX = templates/view/index.html
|
||||
SETUP = templates/view/setup.html
|
||||
LOGIN = templates/view/login.html
|
||||
@@ -56,33 +118,38 @@ ADMIN = templates/view/admin.html
|
||||
|
||||
; HTTP status pages ===============================================================================
|
||||
[PATHFINDER.STATUS]
|
||||
; error pages
|
||||
; Error page templates
|
||||
; Hint: You should not change this.
|
||||
4XX = templates/status/4xx.html
|
||||
5XX = templates/status/5xx.html
|
||||
|
||||
; MAP =============================================================================================
|
||||
; Map settings for "private", "corporation" and "alliance" maps
|
||||
; LIFETIME (days):
|
||||
; - Map will be deleted after "X" days, by cronjob
|
||||
; MAX_COUNT:
|
||||
; - Users can create/view up to "X" maps of a type
|
||||
; MAX_SHARED:
|
||||
; - Max number of shared entities per map
|
||||
; MAX_SYSTEMS:
|
||||
; - Max number of active systems per map
|
||||
; LOG_ACTIVITY_ENABLED (0: disable, 1: enable):
|
||||
; - Whether user activity statistics can be anabled for a map type
|
||||
; - E.g. create/update/delete of systems/connections/signatures/...
|
||||
; LOG_HISTORY_ENABLED (0: disable, 1: enable):
|
||||
; - Whether map change history should be logged to separat *.log files
|
||||
; - see: [PATHFINDER.HISTORY] config section below
|
||||
; SEND_HISTORY_SLACK_ENABLED (0: disable, 1: enable):
|
||||
; - Send map updates to a Slack channel per map
|
||||
; SEND_RALLY_SLACK_ENABLED (0: disable, 1: enable):
|
||||
; - Send rally point pokes to a Slack channel per map
|
||||
; SEND_RALLY_Mail_ENABLED (0: disable, 1: enable):
|
||||
; - Send rally point pokes by mail
|
||||
; - see: [PATHFINDER.NOTIFICATION] section below
|
||||
; Map settings for 'private', 'corporation' and 'alliance' maps:
|
||||
; LIFETIME (days)
|
||||
; - Map will be deleted after 'X' days, by cronjob
|
||||
; MAX_COUNT
|
||||
; - Users can create/view up to 'X' maps of a type
|
||||
; MAX_SHARED
|
||||
; - Max number of shared entities per map
|
||||
; MAX_SYSTEMS
|
||||
; - Max number of active systems per map
|
||||
; LOG_ACTIVITY_ENABLED (Syntax: 0 | 1)
|
||||
; - Whether user activity statistics can be enabled for a map type
|
||||
; - E.g. create/update/delete of systems/connections/signatures/...
|
||||
; LOG_HISTORY_ENABLED (Syntax: 0 | 1)
|
||||
; - Whether map change history should be logged to separate *.log files
|
||||
; - see: [PATHFINDER.HISTORY] config section below
|
||||
; SEND_HISTORY_SLACK_ENABLED (Syntax: 0 | 1)
|
||||
; - Send map updates to a Slack channel per map
|
||||
; SEND_RALLY_SLACK_ENABLED (Syntax: 0 | 1)
|
||||
; - Send rally point pokes to a Slack channel per map
|
||||
; SEND_HISTORY_DISCORD_ENABLED (Syntax: 0 | 1)
|
||||
; - Send map updates to a Discord channel per map
|
||||
; SEND_RALLY_DISCORD_ENABLED (Syntax: 0 | 1)
|
||||
; - Send rally point pokes to a Discord channel per map
|
||||
; SEND_RALLY_Mail_ENABLED (Syntax: 0 | 1)
|
||||
; - Send rally point pokes by mail
|
||||
; - see: [PATHFINDER.NOTIFICATION] section below
|
||||
[PATHFINDER.MAP.PRIVATE]
|
||||
LIFETIME = 60
|
||||
MAX_COUNT = 3
|
||||
@@ -124,75 +191,152 @@ SEND_RALLY_Mail_ENABLED = 0
|
||||
|
||||
; Route search ====================================================================================
|
||||
[PATHFINDER.ROUTE]
|
||||
; max recursive search depth for routes (default: 9000)
|
||||
; decrease it on performance problems
|
||||
; Search depth for system route search
|
||||
; Recursive search depth for search algorithm.
|
||||
; This is only used in case ESIs /route/ API responds with errors and the custom search algorithm is used.
|
||||
; Hint: Higher values can lead to high CPU load. If to low, routes might not be found even if exist.
|
||||
; Syntax: Integer
|
||||
; Default: 9000
|
||||
SEARCH_DEPTH = 9000
|
||||
; default count of routes that will be checked (initial) when a system is selected (default: 4)
|
||||
|
||||
; Initial count of routes that will be checked when a system becomes active
|
||||
; Syntax: Integer
|
||||
; Default: 4
|
||||
SEARCH_DEFAULT_COUNT = 4
|
||||
; max count of routes that can be selected in "route settings" dialog (default: 6)
|
||||
|
||||
; Max count of routes that can be selected in 'route settings' dialog
|
||||
; Syntax: Integer
|
||||
; Default: 6
|
||||
MAX_DEFAULT_COUNT = 6
|
||||
; max count of routes that will be checked (MAX_COUNT + custom routes ) (default: 8)
|
||||
|
||||
; Max count of routes that will be checked (MAX_DEFAULT_COUNT + custom routes)
|
||||
; Syntax: Integer
|
||||
; Default: 8
|
||||
LIMIT = 8
|
||||
|
||||
; Email notifications =============================================================================
|
||||
; Requires SMTP configuration (see environment.ini)
|
||||
; Set mail address for recipient (e.g. pathfinder.notification@[YOUR_DOMAIN] )
|
||||
[PATHFINDER.NOTIFICATION]
|
||||
; Email address for rally point pokes
|
||||
; Requires SMTP configuration (see environment.ini).
|
||||
; Hint: This only makes sens if the installation is restricted to allied groups only.
|
||||
; This email address is used for all maps on this installation.
|
||||
; Syntax: String
|
||||
; Default:
|
||||
RALLY_SET =
|
||||
|
||||
; TIMER ===========================================================================================
|
||||
; Timer values should NOT be changed unless you know what they affect!
|
||||
; =================================================================================================
|
||||
[PATHFINDER.TIMER]
|
||||
; login time (minutes) (default: 480)
|
||||
; Login time for characters. Users get logged out after X minutes
|
||||
; Hint: Set to 0 disables login time and characters stay logged in until Cookie data expires
|
||||
; Syntax: Integer (minutes)
|
||||
; Default: 480
|
||||
LOGGED = 480
|
||||
; double click timer (milliseconds) (default: 250)
|
||||
|
||||
; Double click timer
|
||||
; Syntax: Integer (milliseconds)
|
||||
; Default: 250
|
||||
DBL_CLICK = 250
|
||||
; time for status change visibility in header (milliseconds) (default: 5000)
|
||||
|
||||
; Time for status change visibility in header
|
||||
; Syntax: Integer (milliseconds)
|
||||
; Default: 5000
|
||||
PROGRAM_STATUS_VISIBLE = 5000
|
||||
|
||||
; main map update ping (ajax) (milliseconds)
|
||||
[PATHFINDER.TIMER.UPDATE_SERVER_MAP]
|
||||
; Map data update interval (ajax long polling)
|
||||
; This is not used for 'WebSocket' configured installations.
|
||||
; Syntax: Integer (milliseconds)
|
||||
; Default: 5000
|
||||
DELAY = 5000
|
||||
|
||||
; Execution limit for map data update request (ajax long polling)
|
||||
; Requests that exceed the limit are logged as 'warning'.
|
||||
; Syntax: Integer (milliseconds)
|
||||
; Default: 200
|
||||
EXECUTION_LIMIT = 200
|
||||
|
||||
; update client map data (milliseconds)
|
||||
[PATHFINDER.TIMER.UPDATE_CLIENT_MAP]
|
||||
; Execution limit for client side (javascript) map data updates
|
||||
; Map data updates that exceed the limit are logged as 'warning'.
|
||||
; Syntax: Integer (milliseconds)
|
||||
; Default: 50
|
||||
EXECUTION_LIMIT = 50
|
||||
|
||||
; map user update ping (ajax) (milliseconds)
|
||||
[PATHFINDER.TIMER.UPDATE_SERVER_USER_DATA]
|
||||
; User data update interval (ajax long polling)
|
||||
; This is not used for 'WebSocket' configured installations.
|
||||
; Syntax: Integer (milliseconds)
|
||||
; Default: 5000
|
||||
DELAY = 5000
|
||||
|
||||
; Execution limit for user data update request (ajax long polling)
|
||||
; Requests that exceed the limit are logged as 'warning'.
|
||||
; Syntax: Integer (milliseconds)
|
||||
; Default: 500
|
||||
EXECUTION_LIMIT = 500
|
||||
|
||||
; update client user data (milliseconds)
|
||||
[PATHFINDER.TIMER.UPDATE_CLIENT_USER_DATA]
|
||||
; Execution limit for client side (javascript) user data updates
|
||||
; User data updates that exceed the limit are logged as 'warning'.
|
||||
; Syntax: Integer (milliseconds)
|
||||
; Default: 50
|
||||
EXECUTION_LIMIT = 50
|
||||
|
||||
; CACHE ===========================================================================================
|
||||
[PATHFINDER.CACHE]
|
||||
; delete character log data if if nothing (ship/system/...) for X seconds (seconds) (default: 3min)
|
||||
; Delete character log data if nothing (ship/system/...) changed for X seconds
|
||||
; Syntax: Integer (seconds)
|
||||
; Default: 180
|
||||
CHARACTER_LOG_INACTIVE = 180
|
||||
; max expire time. Expired cache files will be deleted by cronjob (seconds) (default: 10d)
|
||||
|
||||
; Max expire time for cache files
|
||||
; Files will be deleted by cronjob afterwards.
|
||||
; This setting only affects 'file cache'. Redis installations are not affected by this.
|
||||
; Syntax: Integer (seconds)
|
||||
; Default: 864000 (10d)
|
||||
EXPIRE_MAX = 864000
|
||||
; expire time for EOL (end of life) connections (seconds) (default: 4h + 15min)
|
||||
|
||||
; Expire time for EOL (end of life) connections
|
||||
; EOL connections get auto deleted by cronjob afterwards.
|
||||
; Syntax: Integer (seconds)
|
||||
; Default: 15300 (4h + 15min)
|
||||
EXPIRE_CONNECTIONS_EOL = 15300
|
||||
; expire time for WH connections (seconds) (default: 2d)
|
||||
|
||||
; Expire time for WH connections
|
||||
; WH connections get auto deleted by cronjob afterwards.
|
||||
; This can be overwritten for each map in the UI.
|
||||
; Syntax: Integer (seconds)
|
||||
; Default: 172800 (2d)
|
||||
EXPIRE_CONNECTIONS_WH = 172800
|
||||
; expire time for signatures (inactive systems) (seconds) (default 3d)
|
||||
|
||||
; Expire time for signatures (inactive systems)
|
||||
; Signatures get auto deleted by cronjob afterwards.
|
||||
; This can be overwritten for each map in the UI.
|
||||
; Syntax: Integer (seconds)
|
||||
; Default: 259200 (3d)
|
||||
EXPIRE_SIGNATURES = 259200
|
||||
|
||||
; LOGGING =========================================================================================
|
||||
; Log file configurations
|
||||
; Log files are location in [PATHFINDER]/logs/ dir (see: config.ini)
|
||||
; Syntax: String
|
||||
[PATHFINDER.LOGFILES]
|
||||
; error log
|
||||
; Error log
|
||||
ERROR = error
|
||||
; SSO error log
|
||||
SSO = sso
|
||||
; login information
|
||||
LOGIN = login
|
||||
; session warnings (suspect)
|
||||
; Login info
|
||||
CHARACTER_LOGIN = character_login
|
||||
; Character access
|
||||
CHARACTER_ACCESS = character_access
|
||||
; Session warnings (mysql sessions only)
|
||||
SESSION_SUSPECT = session_suspect
|
||||
; account deleted
|
||||
; Account deleted
|
||||
DELETE_ACCOUNT = account_delete
|
||||
; admin action (e.g. kick, bann) log
|
||||
; Admin action (e.g. kick, ban)
|
||||
ADMIN = admin
|
||||
; TCP socket errors
|
||||
SOCKET_ERROR = socket_error
|
||||
@@ -200,11 +344,26 @@ SOCKET_ERROR = socket_error
|
||||
DEBUG = debug
|
||||
|
||||
[PATHFINDER.HISTORY]
|
||||
; cache time for parsed log files (seconds) (default: 5)
|
||||
; cache time for parsed history log file data
|
||||
; Syntax: Integer (seconds)
|
||||
; Default: 5
|
||||
CACHE = 5
|
||||
; file folder for 'history' logs (e.g. map history) (default: history/)
|
||||
|
||||
; File folder for 'history' logs (e.g. map history)
|
||||
; Syntax: String
|
||||
; Default: history/
|
||||
LOG = history/
|
||||
|
||||
; Max file size for 'history' logs before getting truncated by cronjob
|
||||
; Syntax: Integer (MB)
|
||||
; Default: 2
|
||||
LOG_SIZE_THRESHOLD = 2
|
||||
|
||||
; log entries (lines) after file getting truncated by cronjob
|
||||
; Syntax: Integer
|
||||
; Default: 1000
|
||||
LOG_LINES = 1000
|
||||
|
||||
; ADMIN ===========================================================================================
|
||||
; "SUPER" admins and additional "CORPORATION" admins can be added here
|
||||
;[PATHFINDER.ROLES]
|
||||
|
||||
@@ -9,7 +9,7 @@ APACHE.VERSION = 2.5
|
||||
NGINX.VERSION = 1.9
|
||||
|
||||
[REQUIREMENTS.PHP]
|
||||
VERSION = 7.0
|
||||
VERSION = 7.1
|
||||
|
||||
; 64-bit version of PHP (4 = 32-bit, 8 = 64-bit)
|
||||
PHP_INT_SIZE = 8
|
||||
@@ -69,6 +69,7 @@ CHARACTER_SET_CONNECTION = utf8
|
||||
COLLATION_DATABASE = utf8_general_ci
|
||||
COLLATION_CONNECTION = utf8_general_ci
|
||||
FOREIGN_KEY_CHECKS = ON
|
||||
INNODB_FILE_PER_TABLE = ON
|
||||
|
||||
[REQUIREMENTS.REDIS]
|
||||
VERSION = 3.0
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
"url": "../pathfinder_esi"
|
||||
}],
|
||||
"require": {
|
||||
"php-64bit": ">=7.0",
|
||||
"php-64bit": ">=7.1",
|
||||
"ext-pdo": "*",
|
||||
"ext-openssl": "*",
|
||||
"ext-curl": "*",
|
||||
@@ -33,6 +33,14 @@
|
||||
"websoftwares/monolog-zmq-handler": "0.2.*",
|
||||
"swiftmailer/swiftmailer": "^6.0",
|
||||
"league/html-to-markdown": "4.8.*",
|
||||
"cache/redis-adapter": "1.0.*",
|
||||
"cache/filesystem-adapter": "1.0.*",
|
||||
"cache/array-adapter": "1.0.*",
|
||||
"cache/void-adapter": "1.0.*",
|
||||
"cache/namespaced-cache": "1.0.*",
|
||||
"exodus4d/pathfinder_esi": "dev-develop as 0.0.x-dev"
|
||||
},
|
||||
"suggest": {
|
||||
"ext-redis": "Redis can be used as cache backend."
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
"url": "https://github.com/exodus4d/pathfinder_esi"
|
||||
}],
|
||||
"require": {
|
||||
"php-64bit": ">=7.0",
|
||||
"php-64bit": ">=7.1",
|
||||
"ext-pdo": "*",
|
||||
"ext-openssl": "*",
|
||||
"ext-curl": "*",
|
||||
@@ -33,6 +33,14 @@
|
||||
"websoftwares/monolog-zmq-handler": "0.2.*",
|
||||
"swiftmailer/swiftmailer": "^6.0",
|
||||
"league/html-to-markdown": "4.8.*",
|
||||
"exodus4d/pathfinder_esi": "dev-master#v1.2.5"
|
||||
"cache/redis-adapter": "1.0.*",
|
||||
"cache/filesystem-adapter": "1.0.*",
|
||||
"cache/array-adapter": "1.0.*",
|
||||
"cache/void-adapter": "1.0.*",
|
||||
"cache/namespaced-cache": "1.0.*",
|
||||
"exodus4d/pathfinder_esi": "dev-master#v1.3.0"
|
||||
},
|
||||
"suggest": {
|
||||
"ext-redis": "Redis can be used as cache backend."
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,9 +14,6 @@ $f3->config('app/config.ini', true);
|
||||
// load environment dependent config
|
||||
lib\Config::instance($f3);
|
||||
|
||||
// initiate CCP API Client (ESI)
|
||||
lib\CcpClient::instance($f3);
|
||||
|
||||
// initiate cron-jobs
|
||||
Cron::instance();
|
||||
|
||||
|
||||
@@ -54,7 +54,7 @@ requirejs.config({
|
||||
blueImpGalleryBootstrap: 'lib/bootstrap-image-gallery', // v3.4.2 Bootstrap extension for Blue Imp Gallery - https://blueimp.github.io/Bootstrap-Image-Gallery
|
||||
bootstrapConfirmation: 'lib/bootstrap-confirmation', // v1.0.5 Bootstrap extension for inline confirm dialog - https://github.com/tavicu/bs-confirmation
|
||||
bootstrapToggle: 'lib/bootstrap-toggle.min', // v2.2.0 Bootstrap Toggle (Checkbox) - http://www.bootstraptoggle.com
|
||||
lazyload: 'lib/jquery.lazyload.min', // v1.9.7 LazyLoader images - http://www.appelsiini.net/projects/lazyload
|
||||
lazyload: 'lib/jquery.lazyload.min', // v1.9.7 LazyLoader images - https://appelsiini.net/projects/lazyload/
|
||||
sortable: 'lib/sortable.min', // v1.6.0 Sortable - drag&drop reorder - https://github.com/rubaxa/Sortable
|
||||
|
||||
'summernote.loader': './app/summernote.loader', // v0.8.10 Summernote WYSIWYG editor -https://summernote.org
|
||||
|
||||
@@ -102,7 +102,7 @@ define(['jquery'], ($) => {
|
||||
4: 'M609 - C4',
|
||||
5: 'L614 - C5',
|
||||
6: 'S804 - C6',
|
||||
7: 'F353 - Thera'
|
||||
7: 'F353 - C12 Thera'
|
||||
},
|
||||
6: { // ORE
|
||||
1: 'Ordinary Perimeter Deposit', //*
|
||||
@@ -146,7 +146,7 @@ define(['jquery'], ($) => {
|
||||
4: 'Y683 - C4',
|
||||
5: 'N062 - C5',
|
||||
6: 'R474 - C6',
|
||||
7: 'F135 - Thera'
|
||||
7: 'F135 - C12 Thera'
|
||||
},
|
||||
6: { // ORE
|
||||
1: 'Ordinary Perimeter Deposit', //*
|
||||
@@ -192,7 +192,7 @@ define(['jquery'], ($) => {
|
||||
4: 'T405 - C4',
|
||||
5: 'N770 - C5',
|
||||
6: 'A982 - C6',
|
||||
7: 'F135 - Thera'
|
||||
7: 'F135 - C12 Thera'
|
||||
},
|
||||
6: { // ORE
|
||||
1: 'Ordinary Perimeter Deposit', //*
|
||||
@@ -341,6 +341,14 @@ define(['jquery'], ($) => {
|
||||
1: 'Superior Blood Raider Covert Research Facility' //*
|
||||
}
|
||||
},
|
||||
12: { // Thera wormhole
|
||||
1: { // Combat
|
||||
1: 'Epicenter',
|
||||
2: 'Expedition Command Outpost Wreck',
|
||||
3: 'Planetary Colonization Office Wreck',
|
||||
4: 'Testing Facilities'
|
||||
}
|
||||
},
|
||||
13: { // Shattered Wormholes
|
||||
5: { // Wormhole (some of them are static)
|
||||
1: 'P060 - C1',
|
||||
@@ -378,7 +386,7 @@ define(['jquery'], ($) => {
|
||||
}
|
||||
}, // system type (k-space)
|
||||
2: {
|
||||
10: { // High Sec
|
||||
30: { // High Sec
|
||||
5: { // Wormhole
|
||||
1: 'Z971 - C1',
|
||||
2: 'R943 - C2',
|
||||
@@ -389,10 +397,10 @@ define(['jquery'], ($) => {
|
||||
7: 'A641 - H',
|
||||
8: 'R051 - L',
|
||||
9: 'V283 - 0.0',
|
||||
10: 'T458 - Thera'
|
||||
10: 'T458 - C12 Thera'
|
||||
}
|
||||
},
|
||||
11: { // Low Sec
|
||||
31: { // Low Sec
|
||||
5: { // Wormhole
|
||||
1: 'Z971 - C1',
|
||||
2: 'R943 - C2',
|
||||
@@ -403,10 +411,10 @@ define(['jquery'], ($) => {
|
||||
7: 'B449 - H',
|
||||
8: 'N944 - L',
|
||||
9: 'S199 - 0.0',
|
||||
10: 'M164 - Thera'
|
||||
10: 'M164 - C12 Thera'
|
||||
}
|
||||
},
|
||||
12: { // 0.0
|
||||
32: { // 0.0
|
||||
5: { // Wormhole
|
||||
1: 'Z971 - C1',
|
||||
2: 'R943 - C2',
|
||||
@@ -417,7 +425,7 @@ define(['jquery'], ($) => {
|
||||
7: 'B449 - H',
|
||||
8: 'N944 - L',
|
||||
9: 'S199 - 0.0',
|
||||
10: 'L031 - Thera'
|
||||
10: 'L031 - C12 Thera'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
215
js/app/console.js
Normal file
215
js/app/console.js
Normal file
@@ -0,0 +1,215 @@
|
||||
/**
|
||||
* Console module
|
||||
* -> extends default window.console log object
|
||||
*/
|
||||
|
||||
define([], () => {
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* init custom window.console object
|
||||
* -> extend console obj with custom methods for styling and logging
|
||||
*/
|
||||
let initConsole = () => {
|
||||
|
||||
window.console = (origConsole => {
|
||||
// save orig methods for byPassing args to original methods
|
||||
let log = origConsole.log;
|
||||
let info = origConsole.info;
|
||||
let warn = origConsole.warn;
|
||||
let error = origConsole.error;
|
||||
|
||||
let styles = {
|
||||
'indentDefault': {
|
||||
'padding-left': '3px'
|
||||
},
|
||||
'global': {
|
||||
'font-weight': 500,
|
||||
'font-size': '11px',
|
||||
'line-height': '19px',
|
||||
'font-family': '"Fira Code", "Lucida Console"',
|
||||
},
|
||||
'ok': {
|
||||
'color': '#5cb85c'
|
||||
},
|
||||
'log': {
|
||||
'color': '#adadad'
|
||||
},
|
||||
'info': {
|
||||
'color': '#428bca'
|
||||
},
|
||||
'warn': {
|
||||
'color': '#ffdd9e'
|
||||
},
|
||||
'error': {
|
||||
'color': '#ff8080'
|
||||
},
|
||||
'pf': {
|
||||
'color': '#568a89'
|
||||
},
|
||||
'brand': {
|
||||
'color': '#375959',
|
||||
'line-height': '35px',
|
||||
'font-size': '25px'
|
||||
}
|
||||
};
|
||||
|
||||
let placeholders = {
|
||||
'%s': {
|
||||
'style': ['color: #e93f3b; font-style: italic', 'color: inherit']
|
||||
},
|
||||
'%i': {
|
||||
'style': ['color: #9980ff', 'color: inherit'],
|
||||
},
|
||||
'%d': {
|
||||
'style': ['color: #9980ff', 'color: inherit']
|
||||
},
|
||||
'%f': {
|
||||
'style': ['color: #9980ff', 'color: inherit']
|
||||
},
|
||||
'%o': {
|
||||
'style': ['', '']
|
||||
},
|
||||
'%O': {
|
||||
'style': ['', '']
|
||||
}
|
||||
};
|
||||
|
||||
let findPlaceholders = str => {
|
||||
let exp = new RegExp(Object.keys(placeholders).join('|'), 'g');
|
||||
let matches = str.match(exp);
|
||||
return matches ? matches : [];
|
||||
};
|
||||
|
||||
let addStylePlaceholder = str => {
|
||||
let exp = new RegExp(Object.keys(placeholders).join('|'), 'g');
|
||||
|
||||
return str.replace(exp, function(matched){
|
||||
return '%c' + matched + '%c';
|
||||
});
|
||||
};
|
||||
|
||||
let getStyleByPlaceholder = (placeholder, clear = false) => {
|
||||
let css = '';
|
||||
if(placeholders.hasOwnProperty(placeholder)){
|
||||
css = placeholders[placeholder].style[clear ? 1 : 0];
|
||||
}
|
||||
return css;
|
||||
};
|
||||
|
||||
let getStyleByLogType = (logType, props = []) => {
|
||||
let css = '';
|
||||
if(styles.hasOwnProperty(logType)){
|
||||
css = Object.keys(styles[logType])
|
||||
.filter(prop => props.length ? props.includes(prop) : true)
|
||||
.reduce((css, prop,i, affe) => {
|
||||
css += prop + ':' + styles[logType][prop] + ';';
|
||||
return css;
|
||||
}, '');
|
||||
}
|
||||
return css;
|
||||
};
|
||||
|
||||
let setLineStyleByLogType = (logType, args) => {
|
||||
if(args.length){
|
||||
let lineStyle = getStyleByLogType('global') + getStyleByLogType(logType);
|
||||
lineStyle += ['ok', 'log', 'info', 'pf'].includes(logType) ? getStyleByLogType('indentDefault') : '';
|
||||
let bullet = ['ok', 'log', 'info', 'pf'].includes(logType) ? '●' : '';
|
||||
|
||||
if(typeof args[0] === 'string'){
|
||||
// prepend placeholder to existing message
|
||||
args[0] = '%c' + bullet + ' ' + args[0];
|
||||
}else{
|
||||
// prepend placeholder as new message
|
||||
args.splice(0, 0, '%c' + bullet + ' ' + logType + ':');
|
||||
}
|
||||
// set line style as 2nd argument
|
||||
args.splice(1, 0, lineStyle);
|
||||
}
|
||||
};
|
||||
|
||||
let setMessageStyleByLogType = (logType, args) => {
|
||||
if(typeof args[0] === 'string') {
|
||||
let placeholdersFound = findPlaceholders(args[0]);
|
||||
let placeholderCount = placeholdersFound.length;
|
||||
|
||||
// add c% placeholders around other placeholders
|
||||
args[0] = addStylePlaceholder(args[0]);
|
||||
|
||||
// add style args for c% placeholders
|
||||
let placeholderIndex = 0;
|
||||
let argIndexStart = 1;
|
||||
let argIndexEnd = argIndexStart + placeholderCount;
|
||||
let argIndexOffset = 0;
|
||||
for (let argIndex = argIndexStart; argIndex < argIndexEnd; argIndex++) {
|
||||
args.splice(argIndex + argIndexOffset, 0, getStyleByPlaceholder(placeholdersFound[placeholderIndex]));
|
||||
argIndexOffset += 2;
|
||||
args.splice(argIndex + argIndexOffset, 0, getStyleByPlaceholder(placeholdersFound[placeholderIndex], true) + ';' + getStyleByLogType('global') + getStyleByLogType(logType));
|
||||
placeholderIndex++;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
origConsole.ok = (...args) => {
|
||||
setMessageStyleByLogType('ok', args);
|
||||
setLineStyleByLogType('ok', args);
|
||||
info.apply(origConsole, args);
|
||||
};
|
||||
|
||||
origConsole.info = (...args) => {
|
||||
setMessageStyleByLogType('info', args);
|
||||
setLineStyleByLogType('info', args);
|
||||
info.apply(origConsole, args);
|
||||
};
|
||||
|
||||
origConsole.log = (...args) => {
|
||||
setMessageStyleByLogType('log', args);
|
||||
setLineStyleByLogType('log', args);
|
||||
log.apply(origConsole, args);
|
||||
};
|
||||
|
||||
origConsole.warn = (...args) => {
|
||||
setMessageStyleByLogType('warn', args);
|
||||
setLineStyleByLogType('warn', args);
|
||||
warn.apply(origConsole, args);
|
||||
};
|
||||
|
||||
origConsole.error = (...args) => {
|
||||
setMessageStyleByLogType('error', args);
|
||||
setLineStyleByLogType('error', args);
|
||||
error.apply(origConsole, args);
|
||||
};
|
||||
|
||||
origConsole.pf = (...args) => {
|
||||
setMessageStyleByLogType('pf', args);
|
||||
setLineStyleByLogType('pf', args);
|
||||
info.apply(origConsole, args);
|
||||
};
|
||||
|
||||
origConsole.brand = (...args) => {
|
||||
setMessageStyleByLogType('brand', args);
|
||||
setLineStyleByLogType('brand', args);
|
||||
info.apply(origConsole, args);
|
||||
};
|
||||
|
||||
return origConsole;
|
||||
})(window.console);
|
||||
};
|
||||
|
||||
initConsole();
|
||||
|
||||
/**
|
||||
* show current program version information console
|
||||
* @param version
|
||||
*/
|
||||
let showVersionInfo = (version) => {
|
||||
console.ok('%c PATHFINDER',
|
||||
'color: #477372; font-size: 25px; margin-left: 10px; line-height: 100px; text-shadow: 1px 1px 0 #212C30; ' +
|
||||
'background: url(https://i.imgur.com/1Gw8mjL.png) no-repeat;');
|
||||
console.pf('Release: %s', version);
|
||||
};
|
||||
|
||||
return {
|
||||
showVersionInfo: showVersionInfo
|
||||
};
|
||||
});
|
||||
@@ -202,7 +202,7 @@ define(['jquery'], ($) => {
|
||||
},
|
||||
// system security
|
||||
systemSecurity: {
|
||||
security: {
|
||||
'security': {
|
||||
class: 'pf-system-sec'
|
||||
},
|
||||
'A': {
|
||||
@@ -220,23 +220,26 @@ define(['jquery'], ($) => {
|
||||
'0.0': {
|
||||
class: 'pf-system-sec-nullSec'
|
||||
},
|
||||
'C6': {
|
||||
class: 'pf-system-sec-high'
|
||||
},
|
||||
'C5': {
|
||||
class: 'pf-system-sec-high'
|
||||
},
|
||||
'C4': {
|
||||
class: 'pf-system-sec-mid'
|
||||
},
|
||||
'C3': {
|
||||
class: 'pf-system-sec-mid'
|
||||
'C1': {
|
||||
class: 'pf-system-sec-low'
|
||||
},
|
||||
'C2': {
|
||||
class: 'pf-system-sec-low'
|
||||
},
|
||||
'C1': {
|
||||
class: 'pf-system-sec-low'
|
||||
'C3': {
|
||||
class: 'pf-system-sec-mid'
|
||||
},
|
||||
'C4': {
|
||||
class: 'pf-system-sec-mid'
|
||||
},
|
||||
'C5': {
|
||||
class: 'pf-system-sec-high'
|
||||
},
|
||||
'C6': {
|
||||
class: 'pf-system-sec-high'
|
||||
},
|
||||
'C12': {
|
||||
class: 'pf-system-sec-special'
|
||||
}
|
||||
},
|
||||
// true sec
|
||||
@@ -495,6 +498,36 @@ define(['jquery'], ($) => {
|
||||
6: 'G008 - C6',
|
||||
7: 'Q003 - 0.0',
|
||||
8: 'A009 - C13'
|
||||
},
|
||||
30: { // High Sec
|
||||
1: 'E004 - C1',
|
||||
2: 'L005 - C2',
|
||||
3: 'Z006 - C3',
|
||||
4: 'M001 - C4',
|
||||
5: 'C008 - C5',
|
||||
6: 'G008 - C6',
|
||||
7: 'Q003 - 0.0',
|
||||
8: 'A009 - C13'
|
||||
},
|
||||
31: { // Low Sec
|
||||
1: 'E004 - C1',
|
||||
2: 'L005 - C2',
|
||||
3: 'Z006 - C3',
|
||||
4: 'M001 - C4',
|
||||
5: 'C008 - C5',
|
||||
6: 'G008 - C6',
|
||||
7: 'Q003 - 0.0',
|
||||
8: 'A009 - C13'
|
||||
},
|
||||
32: { // 0.0
|
||||
1: 'E004 - C1',
|
||||
2: 'L005 - C2',
|
||||
3: 'Z006 - C3',
|
||||
4: 'M001 - C4',
|
||||
5: 'C008 - C5',
|
||||
6: 'G008 - C6',
|
||||
7: 'Q003 - 0.0',
|
||||
8: 'A009 - C13'
|
||||
}
|
||||
},
|
||||
// incoming wormholes
|
||||
@@ -505,7 +538,7 @@ define(['jquery'], ($) => {
|
||||
4: 'K162 - H',
|
||||
5: 'K162 - L',
|
||||
6: 'K162 - 0.0',
|
||||
7: 'K162 - Thera'
|
||||
7: 'K162 - C12 Thera'
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
@@ -17,7 +17,8 @@ define([
|
||||
'dialog/notification',
|
||||
'dialog/manual',
|
||||
'dialog/changelog',
|
||||
'dialog/credit'
|
||||
'dialog/credit',
|
||||
'dialog/api_status',
|
||||
], ($, Init, Util, Render, Gallery, bootbox) => {
|
||||
|
||||
'use strict';
|
||||
@@ -70,6 +71,8 @@ define([
|
||||
stickyPanelServerId: 'pf-landing-server-panel', // id for EVE Online server status panel
|
||||
stickyPanelAdminId: 'pf-landing-admin-panel', // id for admin login panel
|
||||
|
||||
apiStatusTriggerClass: 'pf-api-status-trigger', // class for "api status" dialog trigger elements
|
||||
|
||||
// animation
|
||||
animateElementClass: 'pf-animate-on-visible', // class for elements that will be animated to show
|
||||
|
||||
@@ -463,30 +466,40 @@ define([
|
||||
dataType: 'json'
|
||||
}).done(function(responseData, textStatus, request){
|
||||
|
||||
if(responseData.hasOwnProperty('status')){
|
||||
let data = responseData.status;
|
||||
data.stickyPanelServerId = config.stickyPanelServerId;
|
||||
data.stickyPanelClass = config.stickyPanelClass;
|
||||
|
||||
let statusClass = '';
|
||||
switch(data.serviceStatus.toLowerCase()){
|
||||
case 'online': statusClass = 'txt-color-green'; break;
|
||||
case 'vip': statusClass = 'txt-color-orange'; break;
|
||||
case 'offline': statusClass = 'txt-color-redDarker'; break;
|
||||
let data = {
|
||||
stickyPanelServerId: config.stickyPanelServerId,
|
||||
stickyPanelClass: config.stickyPanelClass,
|
||||
apiStatusTriggerClass: config.apiStatusTriggerClass,
|
||||
server: responseData.server,
|
||||
api: responseData.api,
|
||||
statusFormat: () => {
|
||||
return (val, render) => {
|
||||
switch(render(val)){
|
||||
case 'online':
|
||||
case 'green': return 'txt-color-green';
|
||||
case 'vip':
|
||||
case 'yellow': return 'txt-color-orange';
|
||||
case 'offline':
|
||||
case 'red': return 'txt-color-red';
|
||||
default: return '';
|
||||
}
|
||||
};
|
||||
}
|
||||
data.serviceStatus = {
|
||||
eve: data.serviceStatus,
|
||||
style: statusClass
|
||||
};
|
||||
};
|
||||
|
||||
requirejs(['text!templates/ui/server_panel.html', 'mustache'], function(template, Mustache){
|
||||
let content = Mustache.render(template, data);
|
||||
$('#' + config.headerId).prepend(content);
|
||||
$('#' + config.stickyPanelServerId).velocity('transition.slideLeftBigIn', {
|
||||
duration: 240
|
||||
});
|
||||
requirejs(['text!templates/ui/server_panel.html', 'mustache'], function(template, Mustache){
|
||||
let content = Mustache.render(template, data);
|
||||
$('#' + config.headerId).prepend(content);
|
||||
let stickyPanelServer = $('#' + config.stickyPanelServerId);
|
||||
stickyPanelServer.velocity('transition.slideLeftBigIn', {
|
||||
duration: 240
|
||||
});
|
||||
}
|
||||
|
||||
// set observer for api status dialog
|
||||
stickyPanelServer.on('click', '.' + config.apiStatusTriggerClass, function(){
|
||||
$.fn.apiStatusDialog(data.api);
|
||||
});
|
||||
});
|
||||
|
||||
}).fail(handleAjaxErrorResponse);
|
||||
};
|
||||
|
||||
@@ -1495,7 +1495,7 @@ define([
|
||||
hiddenOptions.push('delete_system');
|
||||
}
|
||||
|
||||
let mapElement = component.parents('.' + config.mapClass);
|
||||
let mapElement = component.closest('.' + config.mapClass);
|
||||
if( !mapElement.find('.' + config.systemActiveClass).length ){
|
||||
hiddenOptions.push('find_route');
|
||||
}
|
||||
@@ -1827,7 +1827,7 @@ define([
|
||||
let single = function(e){
|
||||
// check if click was performed on "popover" (x-editable)
|
||||
let popoverClick = false;
|
||||
if( $(e.target).parents('.popover').length ){
|
||||
if( $(e.target).closest('.popover').length ){
|
||||
popoverClick = true;
|
||||
}
|
||||
|
||||
@@ -2014,7 +2014,7 @@ define([
|
||||
// register all available connection types ----------------------------------------------------------------
|
||||
newJsPlumbInstance.registerConnectionTypes(globalMapConfig.connectionTypes);
|
||||
|
||||
// event after a new connection is established --------------------------
|
||||
// event after a new connection is established ------------------------------------------------------------
|
||||
newJsPlumbInstance.bind('connection', function(info, e){
|
||||
// set connection observer
|
||||
setConnectionObserver(newJsPlumbInstance, info.connection);
|
||||
@@ -2114,6 +2114,54 @@ define([
|
||||
return MapUtil.getMapInstance(mapId);
|
||||
};
|
||||
|
||||
/**
|
||||
* check if there is an focus() element found as parent of tabContentElement
|
||||
* -> or if there is any other active UI element found (e.g. dialog, xEditable, Summernote)
|
||||
* @param tabContentElement
|
||||
* @returns {*}
|
||||
*/
|
||||
let systemFormsActive = (tabContentElement) => {
|
||||
let activeNode = null;
|
||||
if(tabContentElement.length){
|
||||
// tabContentElement exists ...
|
||||
tabContentElement = tabContentElement[0];
|
||||
|
||||
// ... check for current active/focus() element and is not the default <body> element ...
|
||||
if(
|
||||
Util.isDomElement(document.activeElement) &&
|
||||
document.activeElement !== document.body
|
||||
){
|
||||
let activeElementTagName = document.activeElement.tagName.toLocaleLowerCase();
|
||||
|
||||
// ... check for active form elements ...
|
||||
let isFormElement = ['input', 'select', 'textarea'].includes(activeElementTagName);
|
||||
let isChildElement = tabContentElement.contains(document.activeElement);
|
||||
|
||||
if(isFormElement && isChildElement){
|
||||
activeNode = activeElementTagName;
|
||||
}else{
|
||||
// ... check for open dialogs/xEditable elements ...
|
||||
if(Util.isDomElement(document.querySelector('.bootbox'))){
|
||||
activeNode = 'dialogOpen';
|
||||
}else if(Util.isDomElement(document.querySelector('.editable-open'))){
|
||||
activeNode = 'xEditableOpen';
|
||||
}else{
|
||||
// ... check for open Summernote editor
|
||||
let summernoteElement = tabContentElement.querySelector('.' + Util.config.summernoteClass);
|
||||
if(
|
||||
Util.isDomElement(summernoteElement) &&
|
||||
typeof $(summernoteElement).data().summernote === 'object'
|
||||
){
|
||||
activeNode = 'SummernoteOpen';
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return activeNode;
|
||||
};
|
||||
|
||||
/**
|
||||
* set observer for a map container
|
||||
* @param map
|
||||
@@ -2484,17 +2532,30 @@ define([
|
||||
|
||||
// triggered from "header" link (if user is active in one of the systems)
|
||||
mapContainer.on('pf:menuSelectSystem', function(e, data){
|
||||
let tempMapContainer = $(this);
|
||||
let systemId = MapUtil.getSystemId(tempMapContainer.data('id'), data.systemId);
|
||||
let system = $(this).find('#' + systemId);
|
||||
let mapElement = $(this);
|
||||
let systemId = MapUtil.getSystemId(mapElement.data('id'), data.systemId);
|
||||
let system = mapElement.find('#' + systemId);
|
||||
|
||||
if(system.length === 1){
|
||||
// scroll to system
|
||||
let tempMapWrapper = tempMapContainer.parents('.' + config.mapWrapperClass);
|
||||
tempMapWrapper.mCustomScrollbar('scrollTo', system);
|
||||
// system found on map ...
|
||||
let select = Util.getObjVal(data, 'forceSelect') !== false;
|
||||
|
||||
// select system
|
||||
MapUtil.showSystemInfo(map, system);
|
||||
if(!select){
|
||||
// ... select is NOT "forced" -> auto select system on jump
|
||||
let activeElement = systemFormsActive(MapUtil.getTabContentElementByMapElement(system));
|
||||
if(activeElement !== null){
|
||||
console.info('Skip auto select systemId %i. Reason: %o', data.systemId, activeElement);
|
||||
}else{
|
||||
select = true;
|
||||
}
|
||||
}
|
||||
|
||||
if(select){
|
||||
let mapWrapper = mapElement.closest('.' + config.mapWrapperClass);
|
||||
mapWrapper.scrollToSystem(MapUtil.getSystemPosition(system));
|
||||
// select system
|
||||
MapUtil.showSystemInfo(map, system);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -2595,118 +2656,125 @@ define([
|
||||
/**
|
||||
* updates all systems on map with current user Data (all users on this map)
|
||||
* update the Data of the user that is currently viewing the map (if available)
|
||||
* @param mapElement
|
||||
* @param userData
|
||||
* @returns {boolean}
|
||||
* @returns {Promise<any>}
|
||||
*/
|
||||
$.fn.updateUserData = function(userData){
|
||||
let returnStatus = true;
|
||||
let updateUserData = (mapElement, userData) => {
|
||||
|
||||
// get new map instance or load existing
|
||||
let map = getMapInstance(userData.config.id);
|
||||
|
||||
let mapElement = map.getContainer();
|
||||
|
||||
// container must exist! otherwise systems can not be updated
|
||||
if(mapElement !== undefined){
|
||||
mapElement = $(mapElement);
|
||||
|
||||
// check if map is frozen
|
||||
if(mapElement.data('frozen') === true){
|
||||
return returnStatus;
|
||||
}
|
||||
|
||||
// compact/small system layout or not
|
||||
let compactView = mapElement.hasClass(MapUtil.config.mapCompactClass);
|
||||
|
||||
// get current character log data
|
||||
let characterLogExists = false;
|
||||
let currentCharacterLog = Util.getCurrentCharacterLog();
|
||||
|
||||
// data for header update
|
||||
let headerUpdateData = {
|
||||
mapId: userData.config.id,
|
||||
userCountInside: 0, // active user on a map
|
||||
userCountOutside: 0, // active user NOT on map
|
||||
userCountInactive: 0 // inactive users (no location)
|
||||
let updateUserDataExecutor = (resolve, reject) => {
|
||||
let payload = {
|
||||
action: 'updateUserData'
|
||||
};
|
||||
|
||||
if(
|
||||
currentCharacterLog &&
|
||||
currentCharacterLog.system
|
||||
){
|
||||
characterLogExists = true;
|
||||
headerUpdateData.currentSystemName = currentCharacterLog.system.name;
|
||||
}
|
||||
// get new map instance or load existing
|
||||
let map = getMapInstance(userData.config.id);
|
||||
let mapElement = map.getContainer();
|
||||
|
||||
// check if current user was found on the map
|
||||
let currentUserOnMap = false;
|
||||
// container must exist! otherwise systems can not be updated
|
||||
if(mapElement !== undefined){
|
||||
mapElement = $(mapElement);
|
||||
|
||||
// get all systems
|
||||
let systems = mapElement.find('.' + config.systemClass);
|
||||
|
||||
for(let i = 0; i < systems.length; i++){
|
||||
// get user Data for System
|
||||
|
||||
let system = $( systems[i] );
|
||||
|
||||
let systemId = $(system).data('systemId');
|
||||
|
||||
let tempUserData = null;
|
||||
|
||||
// check if user is currently in "this" system
|
||||
let currentUserIsHere = false;
|
||||
|
||||
let j = userData.data.systems.length;
|
||||
|
||||
// search backwards to avoid decrement the counter after splice()
|
||||
while(j--){
|
||||
let systemData = userData.data.systems[j];
|
||||
|
||||
// check if any user is in this system
|
||||
if(systemId === systemData.id){
|
||||
tempUserData = systemData;
|
||||
|
||||
// add "user count" to "total map user count"
|
||||
headerUpdateData.userCountInside += tempUserData.user.length;
|
||||
|
||||
// remove system from "search" array -> speed up loop
|
||||
userData.data.systems.splice(j, 1);
|
||||
}
|
||||
// no user update for 'frozen' maps...
|
||||
if(mapElement.data('frozen') === true){
|
||||
return resolve(payload);
|
||||
}
|
||||
|
||||
// the current user can only be in a single system ----------------------------------------------------
|
||||
// compact/small system layout or not
|
||||
let compactView = mapElement.hasClass(MapUtil.config.mapCompactClass);
|
||||
|
||||
// get current character log data
|
||||
let characterLogExists = false;
|
||||
let currentCharacterLog = Util.getCurrentCharacterLog();
|
||||
|
||||
// data for header update
|
||||
let headerUpdateData = {
|
||||
mapId: userData.config.id,
|
||||
userCountInside: 0, // active user on a map
|
||||
userCountOutside: 0, // active user NOT on map
|
||||
userCountInactive: 0, // inactive users (no location)
|
||||
currentLocation: {
|
||||
id: 0, // systemId for current active user
|
||||
name: false // systemName for current active user
|
||||
}
|
||||
};
|
||||
|
||||
if(
|
||||
characterLogExists &&
|
||||
currentCharacterLog.system.id === systemId
|
||||
currentCharacterLog &&
|
||||
currentCharacterLog.system
|
||||
){
|
||||
if( !currentUserOnMap ){
|
||||
currentUserIsHere = true;
|
||||
currentUserOnMap = true;
|
||||
characterLogExists = true;
|
||||
headerUpdateData.currentLocation.name = currentCharacterLog.system.name;
|
||||
}
|
||||
|
||||
// set current location data for header update
|
||||
headerUpdateData.currentSystemId = $(system).data('id');
|
||||
headerUpdateData.currentSystemName = currentCharacterLog.system.name;
|
||||
// check if current user was found on the map
|
||||
let currentUserOnMap = false;
|
||||
|
||||
// get all systems
|
||||
let systems = mapElement.find('.' + config.systemClass);
|
||||
|
||||
for(let system of systems){
|
||||
system = $(system);
|
||||
let systemId = system.data('systemId');
|
||||
let tempUserData = null;
|
||||
|
||||
// check if user is currently in "this" system
|
||||
let currentUserIsHere = false;
|
||||
|
||||
let j = userData.data.systems.length;
|
||||
|
||||
// search backwards to avoid decrement the counter after splice()
|
||||
while(j--){
|
||||
let systemData = userData.data.systems[j];
|
||||
|
||||
// check if any user is in this system
|
||||
if(systemId === systemData.id){
|
||||
tempUserData = systemData;
|
||||
|
||||
// add "user count" to "total map user count"
|
||||
headerUpdateData.userCountInside += tempUserData.user.length;
|
||||
|
||||
// remove system from "search" array -> speed up loop
|
||||
userData.data.systems.splice(j, 1);
|
||||
}
|
||||
}
|
||||
|
||||
// the current user can only be in a single system ------------------------------------------------
|
||||
if(
|
||||
characterLogExists &&
|
||||
currentCharacterLog.system.id === systemId
|
||||
){
|
||||
if( !currentUserOnMap ){
|
||||
currentUserIsHere = true;
|
||||
currentUserOnMap = true;
|
||||
|
||||
// set current location data for header update
|
||||
headerUpdateData.currentLocation.id = system.data('id');
|
||||
headerUpdateData.currentLocation.name = currentCharacterLog.system.name;
|
||||
}
|
||||
}
|
||||
|
||||
system.updateSystemUserData(map, tempUserData, currentUserIsHere, {compactView: compactView});
|
||||
}
|
||||
|
||||
// users who are not in any map system ----------------------------------------------------------------
|
||||
for(let systemData of userData.data.systems){
|
||||
// users without location are grouped in systemId: 0
|
||||
if(systemData.id){
|
||||
headerUpdateData.userCountOutside += systemData.user.length;
|
||||
}else{
|
||||
headerUpdateData.userCountInactive += systemData.user.length;
|
||||
}
|
||||
}
|
||||
|
||||
system.updateSystemUserData(map, tempUserData, currentUserIsHere, {compactView: compactView});
|
||||
// trigger document event -> update header
|
||||
$(document).trigger('pf:updateHeaderMapData', headerUpdateData);
|
||||
}
|
||||
|
||||
// users who are not in any map system --------------------------------------------------------------------
|
||||
for(let i = 0; i < userData.data.systems.length; i++){
|
||||
// users without location are grouped in systemId: 0
|
||||
if(userData.data.systems[i].id){
|
||||
headerUpdateData.userCountOutside += userData.data.systems[i].user.length;
|
||||
}else{
|
||||
headerUpdateData.userCountInactive += userData.data.systems[i].user.length;
|
||||
}
|
||||
}
|
||||
resolve(payload);
|
||||
};
|
||||
|
||||
// trigger document event -> update header
|
||||
$(document).trigger('pf:updateHeaderMapData', headerUpdateData);
|
||||
}
|
||||
|
||||
return returnStatus;
|
||||
return new Promise(updateUserDataExecutor);
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -2851,17 +2919,7 @@ define([
|
||||
updated: parseInt( system.data('updated') )
|
||||
};
|
||||
systemData.userCount = (system.data('userCount') ? parseInt( system.data('userCount') ) : 0);
|
||||
|
||||
// position ---------------------------------------------------------------------------------------------------
|
||||
let positionData = {};
|
||||
let currentX = system.css('left');
|
||||
let currentY = system.css('top');
|
||||
|
||||
// remove 'px'
|
||||
positionData.x = parseInt( currentX.substring(0, currentX.length - 2) );
|
||||
positionData.y = parseInt( currentY.substring(0, currentY.length - 2) );
|
||||
|
||||
systemData.position = positionData;
|
||||
systemData.position = MapUtil.getSystemPosition(system);
|
||||
|
||||
return systemData;
|
||||
};
|
||||
@@ -2982,6 +3040,7 @@ define([
|
||||
return {
|
||||
getMapInstance: getMapInstance,
|
||||
loadMap: loadMap,
|
||||
updateUserData: updateUserData,
|
||||
saveSystemCallback: saveSystemCallback
|
||||
};
|
||||
|
||||
|
||||
@@ -70,7 +70,7 @@ define([
|
||||
};
|
||||
|
||||
/**
|
||||
* scroll to a specific position in the map
|
||||
* scroll to a specific position on map
|
||||
* demo: http://manos.malihu.gr/repository/custom-scrollbar/demo/examples/scrollTo_demo.html
|
||||
* @param position
|
||||
*/
|
||||
@@ -79,4 +79,31 @@ define([
|
||||
$(this).mCustomScrollbar('scrollTo', position);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* scroll to a specific system on map
|
||||
* -> subtract some offset for tooltips/connections
|
||||
* @param position
|
||||
* @returns {*}
|
||||
*/
|
||||
$.fn.scrollToSystem = function(position){
|
||||
position = getOffsetPosition(position, {x: -15, y: -35});
|
||||
return this.each(function(){
|
||||
$(this).mCustomScrollbar('scrollTo', position);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* add/subtract offset coordinates from position
|
||||
* -> no negative values returned
|
||||
* @param position
|
||||
* @param offset
|
||||
* @returns {{x: number, y: number}}
|
||||
*/
|
||||
let getOffsetPosition = (position, offset) => {
|
||||
return {
|
||||
x: Math.max(0, position.x + offset.x),
|
||||
y: Math.max(0, position.y + offset.y)
|
||||
};
|
||||
};
|
||||
});
|
||||
@@ -482,7 +482,7 @@ define([
|
||||
* @param label
|
||||
* @returns {string}
|
||||
*/
|
||||
let getEndpointOverlayContent = (label) => {
|
||||
let getEndpointOverlayContent = label => {
|
||||
let newLabel = '';
|
||||
let colorClass = 'txt-color-grayLighter';
|
||||
|
||||
@@ -508,17 +508,14 @@ define([
|
||||
* @param element
|
||||
* @returns {*}
|
||||
*/
|
||||
let getTabContentElementByMapElement = (element) => {
|
||||
let tabContentElement = $(element).parents('.' + config.mapTabContentClass);
|
||||
return tabContentElement;
|
||||
};
|
||||
let getTabContentElementByMapElement = element => $(element).closest('.' + config.mapTabContentClass);
|
||||
|
||||
/**
|
||||
* checks if there is an "active" connection on a map
|
||||
* @param map
|
||||
* @returns {boolean}
|
||||
*/
|
||||
let hasActiveConnection = (map) => {
|
||||
let hasActiveConnection = map => {
|
||||
let activeConnections = getConnectionsByType(map, 'active');
|
||||
return activeConnections.length > 0;
|
||||
};
|
||||
@@ -1203,6 +1200,21 @@ define([
|
||||
return new Promise(setMapDefaultOptionsExecutor);
|
||||
};
|
||||
|
||||
/**
|
||||
* get system coordinates from systemElement
|
||||
* @param system
|
||||
* @returns {{x: number, y: number}}
|
||||
*/
|
||||
let getSystemPosition = system => {
|
||||
let x = system.css('left');
|
||||
let y = system.css('top');
|
||||
|
||||
return {
|
||||
x: parseInt(x.substring(0, x.length - 2)),
|
||||
y: parseInt(y.substring(0, y.length - 2))
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* scroll map to default (stored) x/y coordinates
|
||||
* @param mapElement
|
||||
@@ -1755,6 +1767,7 @@ define([
|
||||
deleteLocalData: deleteLocalData,
|
||||
visualizeMap: visualizeMap,
|
||||
setMapDefaultOptions: setMapDefaultOptions,
|
||||
getSystemPosition: getSystemPosition,
|
||||
scrollToDefaultPosition: scrollToDefaultPosition,
|
||||
getSystemId: getSystemId,
|
||||
checkRight: checkRight,
|
||||
|
||||
@@ -143,6 +143,7 @@ define([
|
||||
Init.characterStatus = response.characterStatus;
|
||||
Init.routes = response.routes;
|
||||
Init.url = response.url;
|
||||
Init.character = response.character;
|
||||
Init.slack = response.slack;
|
||||
Init.discord = response.discord;
|
||||
Init.structureStatus = response.structureStatus;
|
||||
@@ -308,10 +309,10 @@ define([
|
||||
.then(payload => Promise.all([initMapModule(payload[0]), initMapWorker(payload[1])]))
|
||||
.then(payload => {
|
||||
// mapModule initialized and WebSocket configuration working
|
||||
console.info('%s() complete! command: "%s"; syncStatus: "%s"',
|
||||
payload[1].action,
|
||||
payload[1].data.command,
|
||||
payload[1].data.syncStatus
|
||||
console.ok('Client syncStatus: %s. %O resolved by command: %s!',
|
||||
payload[1].data.syncStatus,
|
||||
payload[1].action + '()',
|
||||
payload[1].data.command
|
||||
);
|
||||
})
|
||||
.catch(payload => {
|
||||
@@ -322,10 +323,10 @@ define([
|
||||
break;
|
||||
case 'initMapWorker':
|
||||
// WebSocket not working -> no error here -> fallback to Ajax
|
||||
console.warn('%s() rejects Promise. command: "%s"; syncStatus: "%s", payload: %o',
|
||||
payload.action,
|
||||
payload.data.command,
|
||||
console.info('Client syncStatus: %s. %O rejected by command: %s! payload: %o',
|
||||
payload.data.syncStatus,
|
||||
payload.action + '()',
|
||||
payload.data.command,
|
||||
payload.data
|
||||
);
|
||||
break;
|
||||
|
||||
@@ -458,7 +458,7 @@ define([
|
||||
mapElement.trigger('pf:updateLocal', currentMapUserData);
|
||||
|
||||
// update map with current user data
|
||||
mapElement.updateUserData(currentMapUserData);
|
||||
Map.updateUserData(mapElement, currentMapUserData);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -491,7 +491,7 @@ define([
|
||||
|
||||
pageElement.prepend(headRendered);
|
||||
|
||||
// init header =====================================================================
|
||||
// init header ================================================================================================
|
||||
|
||||
// init slide menus
|
||||
let slideMenu = new $.slidebars({
|
||||
@@ -516,7 +516,7 @@ define([
|
||||
|
||||
// current location
|
||||
$('#' + Util.config.headCurrentLocationId).find('a').on('click', function(){
|
||||
Util.getMapModule().getActiveMap().triggerMenuEvent('SelectSystem', {systemId: $(this).data('systemId') });
|
||||
Util.getMapModule().getActiveMap().triggerMenuEvent('SelectSystem', {systemId: $(this).data('systemId')});
|
||||
});
|
||||
|
||||
// program status
|
||||
@@ -589,7 +589,7 @@ define([
|
||||
|
||||
pageElement.prepend(footerElement);
|
||||
|
||||
// init footer ==================================================
|
||||
// init footer ================================================================================================
|
||||
pageElement.find('.' + config.footerLicenceLinkClass).on('click', function(){
|
||||
//show credits info dialog
|
||||
$.fn.showCreditsDialog();
|
||||
@@ -728,7 +728,7 @@ define([
|
||||
return false;
|
||||
});
|
||||
|
||||
// END menu events =============================================================================
|
||||
// END menu events ============================================================================================
|
||||
|
||||
// global "popover" callback (for all popovers)
|
||||
$('.' + Util.config.popoverTriggerClass).on('hide.bs.popover', function(e){
|
||||
@@ -772,7 +772,7 @@ define([
|
||||
userCountInside = data.userCountInside;
|
||||
userCountOutside = data.userCountOutside;
|
||||
userCountInactive = data.userCountInactive;
|
||||
currentLocationData = data;
|
||||
currentLocationData = data.currentLocation;
|
||||
}
|
||||
updateHeaderActiveUserCount(userCountInside, userCountOutside, userCountInactive);
|
||||
updateHeaderCurrentLocation(currentLocationData);
|
||||
@@ -825,7 +825,7 @@ define([
|
||||
|
||||
Util.showNotify({title: 'Logged out', text: data.reason, type: 'error'}, false);
|
||||
|
||||
// remove map -------------------------------------------------------
|
||||
// remove map ---------------------------------------------------------------------------------------------
|
||||
Util.getMapModule().velocity('fadeOut', {
|
||||
duration: 300,
|
||||
complete: function(){
|
||||
@@ -933,7 +933,7 @@ define([
|
||||
}
|
||||
};
|
||||
|
||||
// check for character/ship changes ---------------------------------------------
|
||||
// check for character/ship changes ---------------------------------------------------------------------------
|
||||
if(
|
||||
userData &&
|
||||
userData.character
|
||||
@@ -953,7 +953,7 @@ define([
|
||||
return data.id;
|
||||
});
|
||||
|
||||
// update user character data ---------------------------------------------------
|
||||
// update user character data ---------------------------------------------------------------------------------
|
||||
if(currentCharactersOptionIds.toString() !== newCharactersOptionIds.toString()){
|
||||
|
||||
let currentCharacterChanged = false;
|
||||
@@ -976,7 +976,7 @@ define([
|
||||
userInfoElement.data('characterOptionIds', newCharactersOptionIds);
|
||||
}
|
||||
|
||||
// update user ship data --------------------------------------------------------
|
||||
// update user ship data --------------------------------------------------------------------------------------
|
||||
if(currentShipId !== newShipData.typeId){
|
||||
// set new data for next check
|
||||
userShipElement.data('shipData', newShipData);
|
||||
@@ -1058,37 +1058,46 @@ define([
|
||||
};
|
||||
|
||||
/**
|
||||
* update the "current location" element in head
|
||||
* update the "current location" link element in head
|
||||
* @param locationData
|
||||
*/
|
||||
let updateHeaderCurrentLocation = function(locationData){
|
||||
let currentLocationElement = $('#' + Util.config.headCurrentLocationId);
|
||||
let linkElement = currentLocationElement.find('a');
|
||||
let textElement = linkElement.find('span');
|
||||
let updateHeaderCurrentLocation = locationData => {
|
||||
let systemId = locationData.id || 0;
|
||||
let systemName = locationData.name || false;
|
||||
|
||||
let tempSystemName = (locationData.currentSystemName) ? locationData.currentSystemName : false;
|
||||
let tempSystemId = (locationData.currentSystemId) ? locationData.currentSystemId : 0;
|
||||
let currentLocationData = Util.getCurrentLocationData();
|
||||
|
||||
if(
|
||||
linkElement.data('systemName') !== tempSystemName ||
|
||||
linkElement.data('systemId') !== tempSystemId
|
||||
currentLocationData.name !== systemName ||
|
||||
currentLocationData.id !== systemId
|
||||
){
|
||||
linkElement.data('systemName', tempSystemName);
|
||||
linkElement.data('systemId', tempSystemId);
|
||||
linkElement.toggleClass('disabled', !tempSystemId);
|
||||
Util.setCurrentLocationData(systemId, systemName);
|
||||
|
||||
if(tempSystemName !== false){
|
||||
textElement.text(locationData.currentSystemName);
|
||||
let currentLocationElement = $('#' + Util.config.headCurrentLocationId);
|
||||
let linkElement = currentLocationElement.find('a');
|
||||
linkElement.toggleClass('disabled', !systemId);
|
||||
|
||||
if(systemName !== false){
|
||||
linkElement.find('span').text(locationData.name);
|
||||
currentLocationElement.velocity('fadeIn', {duration: Init.animationSpeed.headerLink});
|
||||
}else{
|
||||
if(currentLocationElement.is(':visible')){
|
||||
currentLocationElement.velocity('fadeOut', {duration: Init.animationSpeed.headerLink});
|
||||
}
|
||||
}
|
||||
|
||||
// auto select current system -----------------------------------------------------------------------------
|
||||
let userData = Util.getCurrentUserData();
|
||||
|
||||
if(
|
||||
Boolean(Util.getObjVal(Init, 'character.autoLocationSelect')) &&
|
||||
Util.getObjVal(userData, 'character.selectLocation')
|
||||
){
|
||||
Util.getMapModule().getActiveMap().triggerMenuEvent('SelectSystem', {systemId: systemId, forceSelect: false});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* shows a test notification for desktop messages
|
||||
*/
|
||||
|
||||
@@ -55,6 +55,7 @@ define([
|
||||
formErrorContainerClass: Util.config.formErrorContainerClass,
|
||||
ccpImageServer: Init.url.ccpImageServer,
|
||||
roleLabel: Util.getLabelByRole(Util.getObjVal(Util.getCurrentUserData(), 'character.role')).prop('outerHTML'),
|
||||
characterAutoLocationSelectEnabled: Boolean(Util.getObjVal(Init, 'character.autoLocationSelect'))
|
||||
};
|
||||
|
||||
let content = Mustache.render(template, data);
|
||||
|
||||
76
js/app/ui/dialog/api_status.js
Normal file
76
js/app/ui/dialog/api_status.js
Normal file
@@ -0,0 +1,76 @@
|
||||
/**
|
||||
* changelog dialog (GitHub API repository information)
|
||||
*/
|
||||
|
||||
define([
|
||||
'jquery',
|
||||
'app/init',
|
||||
'app/util',
|
||||
'app/render',
|
||||
'bootbox'
|
||||
], ($, Init, Util, Render, bootbox) => {
|
||||
'use strict';
|
||||
|
||||
let config = {
|
||||
apiStatusDialogClass: 'pf-api-status-dialog' // class for "api status" dialog
|
||||
};
|
||||
|
||||
/**
|
||||
* show api status dialog
|
||||
* @param apiData
|
||||
*/
|
||||
$.fn.apiStatusDialog = function(apiData){
|
||||
|
||||
let data = {
|
||||
apiData: apiData,
|
||||
methodFormat: () => {
|
||||
return (val, render) => {
|
||||
switch(render(val)){
|
||||
case 'get': return 'txt-color-blue';
|
||||
case 'post': return 'txt-color-green';
|
||||
case 'put': return 'txt-color-yellow';
|
||||
case 'delete': return 'txt-color-red';
|
||||
default: return '';
|
||||
}
|
||||
};
|
||||
},
|
||||
statusTitle: () => {
|
||||
return (val, render) => {
|
||||
switch(render(val)){
|
||||
case 'green': return 'ok';
|
||||
case 'yellow': return 'degraded: Slow or potentially dropping requests';
|
||||
case 'red': return 'bad: Most requests are not succeeding and/or are very slow (5s+) on average';
|
||||
default: return 'unknown';
|
||||
}
|
||||
};
|
||||
},
|
||||
secondsFormat: () => {
|
||||
return (val, render) => {
|
||||
return parseFloat(render(val)).toFixed(2) + 's';
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
requirejs(['text!templates/dialog/api_status.html', 'mustache'], (template, Mustache) => {
|
||||
let apiStatusDialog = bootbox.dialog({
|
||||
className: config.apiStatusDialogClass,
|
||||
title: 'API status',
|
||||
message: Mustache.render(template, data),
|
||||
show: false,
|
||||
buttons: {
|
||||
close: {
|
||||
label: 'cancel',
|
||||
className: 'btn-default'
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
apiStatusDialog.initTooltips();
|
||||
|
||||
// show dialog
|
||||
apiStatusDialog.modal('show');
|
||||
|
||||
});
|
||||
};
|
||||
|
||||
});
|
||||
@@ -88,7 +88,7 @@ define([
|
||||
let data = {
|
||||
isFirst: (i === 0),
|
||||
isOdd: (i % 2 !== 0),
|
||||
releaseDate: releaseData.published_at.substr(0, 10),
|
||||
releaseDate: releaseData.publishedAt.substr(0, 10),
|
||||
releaseData: releaseData
|
||||
};
|
||||
|
||||
|
||||
@@ -307,7 +307,7 @@ define([
|
||||
createdCell: function(cell, cellData, rowData, rowIndex, colIndex){
|
||||
// select system
|
||||
$(cell).on('click', function(e){
|
||||
Util.getMapModule().getActiveMap().triggerMenuEvent('SelectSystem', {systemId: rowData.id });
|
||||
Util.getMapModule().getActiveMap().triggerMenuEvent('SelectSystem', {systemId: rowData.id});
|
||||
});
|
||||
}
|
||||
},{
|
||||
@@ -597,7 +597,7 @@ define([
|
||||
createdCell: function(cell, cellData, rowData, rowIndex, colIndex){
|
||||
// select system
|
||||
$(cell).on('click', function(e){
|
||||
Util.getMapModule().getActiveMap().triggerMenuEvent('SelectSystem', {systemId: rowData.source.id });
|
||||
Util.getMapModule().getActiveMap().triggerMenuEvent('SelectSystem', {systemId: rowData.source.id});
|
||||
});
|
||||
}
|
||||
},{
|
||||
@@ -615,7 +615,7 @@ define([
|
||||
createdCell: function(cell, cellData, rowData, rowIndex, colIndex){
|
||||
// select system
|
||||
$(cell).on('click', function(e){
|
||||
Util.getMapModule().getActiveMap().triggerMenuEvent('SelectSystem', {systemId: rowData.target.id });
|
||||
Util.getMapModule().getActiveMap().triggerMenuEvent('SelectSystem', {systemId: rowData.target.id});
|
||||
});
|
||||
}
|
||||
},{
|
||||
|
||||
@@ -85,7 +85,7 @@ define([
|
||||
let markup = '';
|
||||
if(parts.length === 2){
|
||||
// wormhole data -> 2 columns
|
||||
let securityClass = Util.getSecurityClassForSystem(parts[1].length > 3 ? parts[1].substring(0, 2) : parts[1]);
|
||||
let securityClass = Util.getSecurityClassForSystem(getSystemSecurityFromLabel(parts[1]));
|
||||
markup += '<span>' + parts[0] + '</span> ';
|
||||
markup += '<i class="fas fa-long-arrow-alt-right txt-color txt-color-grayLight"></i>';
|
||||
markup += '<span class="' + securityClass + ' ' + Util.config.popoverTriggerClass + ' ' + Util.config.helpDefaultClass +
|
||||
@@ -118,7 +118,7 @@ define([
|
||||
let parts = data.text.split(' - ');
|
||||
if(parts.length === 2){
|
||||
// wormhole data -> 2 columns
|
||||
let securityClass = Util.getSecurityClassForSystem(parts[1].length > 3 ? parts[1].substring(0, 2) : parts[1]);
|
||||
let securityClass = Util.getSecurityClassForSystem(getSystemSecurityFromLabel(parts[1]));
|
||||
|
||||
switch(formatType){
|
||||
case 'wormhole':
|
||||
@@ -182,6 +182,21 @@ define([
|
||||
return $(markup);
|
||||
};
|
||||
|
||||
/**
|
||||
* try to parse a security label into security name
|
||||
* -> "C1/2/3 (unknown)" -> C1
|
||||
* "C3" -> C3
|
||||
* "H" -> H
|
||||
* "0.0" -> 0.0
|
||||
* "C12 Thera" -> C12
|
||||
* @param security
|
||||
* @returns {string}
|
||||
*/
|
||||
let getSystemSecurityFromLabel = security => {
|
||||
let matches = security.match(/^(\w+\.?\w?)/i);
|
||||
return matches ? matches[1] : '';
|
||||
};
|
||||
|
||||
/**
|
||||
* init a select element as "select2" for map selection
|
||||
*/
|
||||
|
||||
@@ -168,7 +168,7 @@ define([
|
||||
class: 'pf-link',
|
||||
html: connectionData.sourceAlias + ' '
|
||||
}).on('click', function(){
|
||||
Util.getMapModule().getActiveMap().triggerMenuEvent('SelectSystem', {systemId: connectionData.source });
|
||||
Util.getMapModule().getActiveMap().triggerMenuEvent('SelectSystem', {systemId: connectionData.source});
|
||||
}),
|
||||
$('<span>', {
|
||||
class: [config.connectionInfoTableLabelSourceClass].join(' ')
|
||||
@@ -183,7 +183,7 @@ define([
|
||||
class: 'pf-link',
|
||||
html: ' ' + connectionData.targetAlias
|
||||
}).on('click', function(){
|
||||
Util.getMapModule().getActiveMap().triggerMenuEvent('SelectSystem', {systemId: connectionData.target });
|
||||
Util.getMapModule().getActiveMap().triggerMenuEvent('SelectSystem', {systemId: connectionData.target});
|
||||
})
|
||||
)
|
||||
)
|
||||
|
||||
@@ -39,7 +39,7 @@ define([
|
||||
// description field
|
||||
descriptionAreaClass: 'pf-system-info-description-area', // class for "description" area
|
||||
addDescriptionButtonClass: 'pf-system-info-description-button', // class for "add description" button
|
||||
descriptionTextareaElementClass: 'pf-system-info-description', // class for "description" textarea element (xEditable)
|
||||
descriptionTextareaElementClass: 'pf-system-info-description', // class for "description" textarea element (Summernote)
|
||||
|
||||
// fonts
|
||||
fontTriglivianClass: 'pf-triglivian', // class for "Triglivian" names (e.g. Abyssal systems)
|
||||
@@ -177,6 +177,7 @@ define([
|
||||
descriptionAreaClass: config.descriptionAreaClass,
|
||||
descriptionButtonClass: config.addDescriptionButtonClass,
|
||||
descriptionTextareaClass: config.descriptionTextareaElementClass,
|
||||
summernoteClass: Util.config.summernoteClass,
|
||||
systemNameClass: () => {
|
||||
return (val, render) => {
|
||||
return render(val) === 'A' ? config.fontTriglivianClass : '';
|
||||
@@ -298,7 +299,7 @@ define([
|
||||
},
|
||||
callbacks: {
|
||||
onInit: function(context){
|
||||
// make editable field a big larger
|
||||
// make editable field a bit larger
|
||||
context.editable.css('height', '150px');
|
||||
|
||||
// set default background color
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
define([
|
||||
'jquery',
|
||||
'app/init',
|
||||
'app/console',
|
||||
'conf/system_effect',
|
||||
'conf/signature_type',
|
||||
'bootbox',
|
||||
@@ -18,7 +19,7 @@ define([
|
||||
'bootstrapConfirmation',
|
||||
'bootstrapToggle',
|
||||
'select2'
|
||||
], ($, Init, SystemEffect, SignatureType, bootbox, localforage) => {
|
||||
], ($, Init, Con, SystemEffect, SignatureType, bootbox, localforage) => {
|
||||
|
||||
'use strict';
|
||||
|
||||
@@ -83,6 +84,9 @@ define([
|
||||
popoverSmallClass: 'pf-popover-small', // class for small "popover"
|
||||
popoverCharacterClass: 'pf-popover-character', // class for character "popover"
|
||||
|
||||
// Summernote
|
||||
summernoteClass: 'pf-summernote', // class for Summernote "WYSIWYG" elements
|
||||
|
||||
// help
|
||||
helpDefaultClass: 'pf-help-default', // class for "help" tooltip elements
|
||||
helpClass: 'pf-help', // class for "help" tooltip elements
|
||||
@@ -878,9 +882,7 @@ define([
|
||||
/**
|
||||
* show current program version information in browser console
|
||||
*/
|
||||
let showVersionInfo = () => {
|
||||
console.info('PATHFINDER ' + getVersion());
|
||||
};
|
||||
let showVersionInfo = () => Con.showVersionInfo(getVersion());
|
||||
|
||||
/**
|
||||
* polyfill for "passive" events
|
||||
@@ -1842,21 +1844,20 @@ define([
|
||||
let areaId = 0;
|
||||
switch(security){
|
||||
case 'H':
|
||||
areaId = 10;
|
||||
areaId = 30;
|
||||
break;
|
||||
case 'L':
|
||||
areaId = 11;
|
||||
areaId = 31;
|
||||
break;
|
||||
case '0.0':
|
||||
areaId = 12;
|
||||
areaId = 32;
|
||||
break;
|
||||
case 'SH':
|
||||
case 'C13':
|
||||
areaId = 13;
|
||||
break;
|
||||
default:
|
||||
// w-space
|
||||
for(let i = 1; i <= 6; i++){
|
||||
for(let i = 1; i <= 18; i++){
|
||||
if(security === 'C' + i){
|
||||
areaId = i;
|
||||
break;
|
||||
@@ -2771,16 +2772,28 @@ define([
|
||||
return Init.currentSystemData;
|
||||
};
|
||||
|
||||
/**
|
||||
* set current location data
|
||||
* -> system data where current user is located
|
||||
* @param systemId
|
||||
* @param systemName
|
||||
*/
|
||||
let setCurrentLocationData = (systemId, systemName) => {
|
||||
let locationLink = $('#' + config.headCurrentLocationId).find('a');
|
||||
locationLink.data('systemId', systemId);
|
||||
locationLink.data('systemName', systemName);
|
||||
};
|
||||
|
||||
/**
|
||||
* get current location data
|
||||
* -> system data where current user is located
|
||||
* @returns {{id: *, name: *}}
|
||||
*/
|
||||
let getCurrentLocationData = () => {
|
||||
let currentLocationLink = $('#' + config.headCurrentLocationId).find('a');
|
||||
let locationLink = $('#' + config.headCurrentLocationId).find('a');
|
||||
return {
|
||||
id: currentLocationLink.data('systemId'),
|
||||
name: currentLocationLink.data('systemName')
|
||||
id: locationLink.data('systemId') || 0,
|
||||
name: locationLink.data('systemName') || false
|
||||
};
|
||||
};
|
||||
|
||||
@@ -3012,6 +3025,13 @@ define([
|
||||
return Array.from(doc.body.childNodes).some(node => node.nodeType === 1);
|
||||
};
|
||||
|
||||
/**
|
||||
* checks if a given object is a DOM element
|
||||
* @param obj
|
||||
* @returns {boolean}
|
||||
*/
|
||||
let isDomElement = obj => !!(obj && obj.nodeType === 1);
|
||||
|
||||
/**
|
||||
* get deep json object value if exists
|
||||
* -> e.g. key = 'first.last.third' string
|
||||
@@ -3189,6 +3209,7 @@ define([
|
||||
getCurrentCharacterId: getCurrentCharacterId,
|
||||
setCurrentSystemData: setCurrentSystemData,
|
||||
getCurrentSystemData: getCurrentSystemData,
|
||||
setCurrentLocationData: setCurrentLocationData,
|
||||
getCurrentLocationData: getCurrentLocationData,
|
||||
getCurrentUserInfo: getCurrentUserInfo,
|
||||
getCurrentCharacterLog: getCurrentCharacterLog,
|
||||
@@ -3216,6 +3237,7 @@ define([
|
||||
htmlEncode: htmlEncode,
|
||||
htmlDecode: htmlDecode,
|
||||
isValidHtml: isValidHtml,
|
||||
isDomElement: isDomElement,
|
||||
getObjVal: getObjVal,
|
||||
redirect: redirect,
|
||||
logout: logout,
|
||||
|
||||
Binary file not shown.
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
BIN
public/css/v1.5.0/pathfinder.css.br
Normal file
BIN
public/css/v1.5.0/pathfinder.css.br
Normal file
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user