Merge pull request #429 from exodus4d/develop

v1.2.0
This commit is contained in:
Mark Friedrich
2017-01-31 18:53:39 +01:00
committed by GitHub
129 changed files with 15175 additions and 9111 deletions

View File

@@ -6,17 +6,20 @@ SERVER_NAME = PATHFINDER
[globals]
; Default Verbosity level of the stack trace.
; Assign values between 0 to 3 for increasing verbosity levels. Check (environment.ini) config for overwriting
; (default: 0)
DEBUG = 0
; If TRUE, the framework, after having logged stack trace and errors, stops execution
; -> (die without any status) when a non-fatal error is detected.
; -> (die without any status) when a non-fatal error is detected. (default: FALSE)
HALT = FALSE
; Timezone to use. Sync program with eve server time
; Timezone to use. Sync program with eve server time. (default: UTC)
TZ = UTC
; Cache backend. Can handle Memcache module, APC, WinCache, XCache and a filesystem-based cache.
CACHE = TRUE
; Cache backend. Can handle Redis, Memcache module, APC, WinCache, XCache and a filesystem-based cache.
; (default: folder=tmp/cache/)
CACHE = folder=tmp/cache/
;CACHE = redis=localhost:6379
; Callback functions ==============================================================================
ONERROR = Controller\Controller->showError
@@ -26,25 +29,25 @@ UNLOAD = Controller\Controller->unload
; Path configurations =============================================================================
; relative to "BASE" dir
; Temporary folder for cache, filesystem locks, compiled F3 templates, etc.
; Temporary folder for cache, filesystem locks, compiled F3 templates, etc. (default: tmp/)
TEMP = tmp/
; Log file folder
; Log file folder. (default: logs/)
LOGS = logs/
; UI/template folder
; UI/template folder. (default: public/)
UI = public/
; Autoloader for user-defined PHP classes that the framework will attempt to autoload at runtime
; Autoloader for user-defined PHP classes that the framework will attempt to autoload at runtime. (default: app/main/)
AUTOLOAD = app/main/
; Favicons
; Favicons. (default: favicon/)
FAVICON = favicon/
; Export folder (e.g. static table data)
; Export folder (e.g. static table data). (default: export/)
EXPORT = export/
; Default language (overwrites HTTP Accept-Language request header) used for "setlocale()" affects number formatting
; Default language (overwrites HTTP Accept-Language request header) used for "setlocale()" affects number formatting. (default: en-US)
LANGUAGE = en-US
; load additional config files

View File

@@ -8,7 +8,7 @@ SERVER = DEVELOP
[ENVIRONMENT.DEVELOP]
; path to index.php (Default: leave blank == "auto-detect")
; -> e.g. set pathfinder/ if your URL looks like https://www.[YOUR_DOMAIN]/pathfinder (subfolder)
; -> e.g. set /pathfinder if your URL looks like https://www.[YOUR_DOMAIN]/pathfinder (subfolder)
BASE =
; deployment URL (e.g. http://localhost)
URL = http://pathfinder.local
@@ -45,9 +45,14 @@ SMTP_PASS = root
SMTP_FROM = pathfinder@localhost.com
SMTP_ERROR = pathfinder@localhost.com
; TCP Socket configuration (optional) (advanced)
;SOCKET_HOST = 127.0.0.1
;SOCKET_PORT = 5555
[ENVIRONMENT.PRODUCTION]
; path to index.php (Default: leave blank == "auto-detect")
; -> e.g. set pathfinder/ if your URL looks like https://www.[YOUR_DOMAIN]/pathfinder (subfolder)
; -> e.g. set /pathfinder if your URL looks like https://www.[YOUR_DOMAIN]/pathfinder (subfolder)
BASE =
; deployment URL (e.g. https://www.pathfinder-w.space)
URL = https://www.pathfinder-w.space
@@ -83,3 +88,7 @@ SMTP_PASS =
SMTP_FROM = registration@pathfinder-w.space
SMTP_ERROR = admin@pathfinder-w.space
; TCP Socket configuration (optional) (advanced)
;SOCKET_HOST = 127.0.0.1
;SOCKET_PORT = 5555

View File

@@ -1,5 +1,112 @@
CHANGELOG
3.6.0 (19 November 2016)
* NEW: [cli] request type
* NEW: console-friendly CLI mode
* NEW: lexicon caching
* NEW: Silent operator skips startup error check (#125)
* NEW: DB\SQL->trans()
* NEW: custom config section parser, i.e. [conf > Foo::bar]
* NEW: support for cache tags in SQL
* NEW: custom FORMATS
* NEW: Mongo mapper fields whitelist
* NEW: WebSocket server
* NEW: Base->extend method (#158)
* NEW: Implement framework variable caching via config, i.e. FOO = "bar" | 3600
* NEW: Lightweight OAuth2 client
* NEW: SEED variable, configurable app-specific hashing prefix (#149, bcosca/fatfree#951, bcosca/fatfree#884, bcosca/fatfree#629)
* NEW: CLI variable
* NEW: Web->send, specify custom filename (#124)
* NEW: Web->send, added flushing flag (#131)
* NEW: Indexed route wildcards, now exposed in PARAMS['*']
* Changed: PHP 5.4 is now the minimum version requirement
* Changed: Prevent database wrappers from being cloned
* Changed: Router works on PATH instead of URI (#126) NB: PARAMS.0 no longer contains the query string
* Changed: Removed ALIASES autobuilding (#118)
* Changed: Route wildcards match empty strings (#119)
* Changed: Disable default debug highlighting, HIGHLIGHT is false now
* General PHP 5.4 optimizations
* Optimized config parsing
* Optimized Base->recursive
* Optimized header extraction
* Optimized cache/expire headers
* Optimized session_start behaviour (bcosca/fatfree#673)
* Optimized reroute regex
* Tweaked cookie removal
* Better route precedence order
* Performance tweak: reduced cache calls
* Refactored lexicon (LOCALES) build-up, much faster now
* Added turkish locale bug workaround
* Geo->tzinfo Update to UTC
* Added Xcache reset (bcosca/fatfree#928)
* Redis cache: allow db name in dsn
* SMTP: Improve server emulation responses
* SMTP: Optimize transmission envelope
* SMTP: Implement mock transmission
* SMTP: Various bug fixes and feature improvements
* SMTP: quit on failed authentication
* Geo->weather: force metric units
* Base->until: Implement CLI interoperability
* Base->format: looser plural syntax
* Base->format: Force decimal as default number format
* Base->merge: Added $keep flag to save result to the hive key
* Base->reroute: Allow array as URL argument for aliasing
* Base->alias: Allow query string (or array) to be appended to alias
* Permit reroute to named routes with URL query segment
* Sync COOKIE global on set()
* Permit non-hive variables to use JS dot notation
* RFC2616: Use absolute URIs for Location header
* Matrix->calendar: Check if calendar extension is loaded
* Markdown: require start of line/whitespace for text processing (#136)
* DB\[SQL|Jig|Mongo]->log(FALSE) disables logging
* DB\SQL->exec: Added timestamp toggle to db log
* DB\SQL->schema: Remove unnecessary line terminators
* DB\SQL\Mapper: allow array filter with empty string
* DB\SQL\Mapper: optimized handling for key-less tables
* DB\SQL\Mapper: added float support (#106)
* DB\SQL\Session: increased default column sizes (#148, bcosca/fatfree#931, bcosca/fatfree#950)
* Web: Catch cURL errors
* Optimize Web->receive (bcosca/fatfree#930)
* Web->minify: fix arbitrary file download vulnerability
* Web->request: fix cache control max-age detection (bcosca/fatfree#908)
* Web->request: Add request headers & error message to return value (bcosca/fatfree#737)
* Web->request: Refactored response to HTTP request
* Web->send flush while sending big files
* Image->rgb: allow hex strings
* Image->captcha: Check if GD module supports TrueType
* Image->load: Return FALSE on load failure
* Image->resize: keep aspect ratio when only width or height was given
* Updated OpenID lib (bcosca/fatfree#965)
* Audit->card: add new mastercard "2" BIN range (bcosca/fatfree#954)
* Deprecated: Bcrypt class
* Preview->render: optimized detection to remove short open PHP tags and allow xml tags (#133)
* Display file and line number in exception handler (bcosca/fatfree#967)
* Added error reporting level to Base->error and ERROR.level (bcosca/fatfree#957)
* Added optional custom cache instance to Session (#141)
* CLI-aware mock()
* XFRAME and PACKAGE can be switched off now (#128)
* Bug fix: wrong time calculation on memcache reset (#170)
* Bug fix: encode CLI parameters
* Bug fix: Close connection on abort explicitly (#162)
* Bug fix: Image->identicon, Avoid double-size sprite rotation (and possible segfault)
* Bug fix: Magic->offsetset, access property as array element (#147)
* Bug fix: multi-line custom template tag parsing (bcosca/fatfree#935)
* Bug fix: cache headers on errors (bcosca/fatfree#885)
* Bug fix: Web, deprecated CURLOPT_SSL_VERIFYHOST in curl
* Bug fix: Web, Invalid user error constant (bcosca/fatfree#962)
* Bug fix: Web->request, redirections for domain-less location (#135)
* Bug fix: DB\SQL\Mapper, reset changed flag after update (#142, #152)
* Bug fix: DB\SQL\Mapper, fix changed flag when using assignment operator #143 #150 #151
* Bug fix: DB\SQL\Mapper, revival of the HAVING clause
* Bug fix: DB\SQL\Mapper, pgsql with non-integer primary keys (bcosca/fatfree#916)
* Bug fix: DB\SQL\Session, quote table name (bcosca/fatfree#977)
* Bug fix: snakeCase returns word starting with underscore (bcosca/fatfree#927)
* Bug fix: mock does not populate PATH variable
* Bug fix: Geo->weather API key (#129)
* Bug fix: Incorrect compilation of array element with zero index
* Bug fix: Compilation of array construct is incorrect
* Bug fix: Trailing slash redirection on UTF-8 paths (#121)
3.5.1 (31 December 2015)
* NEW: ttl attribute in <include> template tag
* NEW: allow anonymous function for template filter

View File

@@ -2,7 +2,7 @@
/*
Copyright (c) 2009-2015 F3::Factory/Bong Cosca, All rights reserved.
Copyright (c) 2009-2016 F3::Factory/Bong Cosca, All rights reserved.
This file is part of the Fat-Free Framework (http://fatfreeframework.com).
@@ -47,7 +47,7 @@ class Audit extends Prefab {
* @param $mx boolean
**/
function email($str,$mx=TRUE) {
$hosts=array();
$hosts=[];
return is_string(filter_var($str,FILTER_VALIDATE_EMAIL)) &&
(!$mx || getmxrr(substr($str,strrpos($str,'@')+1),$hosts));
}
@@ -166,7 +166,8 @@ class Audit extends Prefab {
return 'Discover';
if (preg_match('/^(?:2131|1800|35\d{3})\d{11}$/',$id))
return 'JCB';
if (preg_match('/^5[1-5][0-9]{14}$/',$id))
if (preg_match('/^5[1-5][0-9]{14}$|'.
'^(222[1-9]|2[3-6]\d{2}|27[0-1]\d|2720)\d{12}$/',$id))
return 'MasterCard';
if (preg_match('/^4[0-9]{12}(?:[0-9]{3})?$/',$id))
return 'Visa';

View File

@@ -2,7 +2,7 @@
/*
Copyright (c) 2009-2015 F3::Factory/Bong Cosca, All rights reserved.
Copyright (c) 2009-2016 F3::Factory/Bong Cosca, All rights reserved.
This file is part of the Fat-Free Framework (http://fatfreeframework.com).
@@ -20,7 +20,6 @@
*/
//! Authorization/authentication plug-in
class Auth {
@@ -48,19 +47,19 @@ class Auth {
protected function _jig($id,$pw,$realm) {
return (bool)
call_user_func_array(
array($this->mapper,'load'),
array(
[$this->mapper,'load'],
[
array_merge(
array(
[
'@'.$this->args['id'].'==? AND '.
'@'.$this->args['pw'].'==?'.
(isset($this->args['realm'])?
(' AND @'.$this->args['realm'].'==?'):''),
$id,$pw
),
(isset($this->args['realm'])?array($realm):array())
],
(isset($this->args['realm'])?[$realm]:[])
)
)
]
);
}
@@ -74,12 +73,12 @@ class Auth {
protected function _mongo($id,$pw,$realm) {
return (bool)
$this->mapper->load(
array(
[
$this->args['id']=>$id,
$this->args['pw']=>$pw
)+
]+
(isset($this->args['realm'])?
array($this->args['realm']=>$realm):array())
[$this->args['realm']=>$realm]:[])
);
}
@@ -93,19 +92,19 @@ class Auth {
protected function _sql($id,$pw,$realm) {
return (bool)
call_user_func_array(
array($this->mapper,'load'),
array(
[$this->mapper,'load'],
[
array_merge(
array(
[
$this->args['id'].'=? AND '.
$this->args['pw'].'=?'.
(isset($this->args['realm'])?
(' AND '.$this->args['realm'].'=?'):''),
$id,$pw
),
(isset($this->args['realm'])?array($realm):array())
],
(isset($this->args['realm'])?[$realm]:[])
)
)
]
);
}

File diff suppressed because it is too large Load Diff

View File

@@ -2,7 +2,7 @@
/*
Copyright (c) 2009-2015 F3::Factory/Bong Cosca, All rights reserved.
Copyright (c) 2009-2016 F3::Factory/Bong Cosca, All rights reserved.
This file is part of the Fat-Free Framework (http://fatfreeframework.com).
@@ -34,7 +34,7 @@ class Basket extends Magic {
//! Current item identifier
$id,
//! Current item contents
$item=array();
$item=[];
/**
* Return TRUE if field is defined
@@ -86,7 +86,7 @@ class Basket extends Magic {
* @param $val mixed
**/
function find($key=NULL,$val=NULL) {
$out=array();
$out=[];
if (isset($_SESSION[$this->key])) {
foreach ($_SESSION[$this->key] as $id=>$item)
if (!isset($key) ||
@@ -122,7 +122,7 @@ class Basket extends Magic {
return $this->item=$found[0]->item;
}
$this->reset();
return array();
return [];
}
/**
@@ -175,7 +175,7 @@ class Basket extends Magic {
**/
function reset() {
$this->id=NULL;
$this->item=array();
$this->item=[];
}
/**
@@ -219,7 +219,7 @@ class Basket extends Magic {
unset($_SESSION[$this->key]);
return $out;
}
return array();
return [];
}
/**
@@ -229,7 +229,8 @@ class Basket extends Magic {
**/
function __construct($key='basket') {
$this->key=$key;
@session_start();
if (session_status()!=PHP_SESSION_ACTIVE)
session_start();
Base::instance()->sync('SESSION');
$this->reset();
}

View File

@@ -1,26 +1,27 @@
<?php
/*
/**
* Lightweight password hashing library
*
* Copyright (c) 2009-2016 F3::Factory/Bong Cosca, All rights reserved.
*
* This file is part of the Fat-Free Framework (http://fatfreeframework.com).
*
* This is free software: you can redistribute it and/or modify it under the
* terms of the GNU General Public License as published by the Free Software
* Foundation, either version 3 of the License, or later.
*
* Fat-Free Framework is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with Fat-Free Framework. If not, see <http://www.gnu.org/licenses/>.
*
* @deprecated use http://php.net/manual/en/ref.password.php instead (PHP 5.5+ only)
**/
Copyright (c) 2009-2015 F3::Factory/Bong Cosca, All rights reserved.
This file is part of the Fat-Free Framework (http://fatfreeframework.com).
This is free software: you can redistribute it and/or modify it under the
terms of the GNU General Public License as published by the Free Software
Foundation, either version 3 of the License, or later.
Fat-Free Framework is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received a copy of the GNU General Public License along
with Fat-Free Framework. If not, see <http://www.gnu.org/licenses/>.
*/
//! Lightweight password hashing library
class Bcrypt extends Prefab {
//@{ Error messages

516
app/lib/cli/ws.php Normal file
View File

@@ -0,0 +1,516 @@
<?php
/*
Copyright (c) 2009-2016 F3::Factory/Bong Cosca, All rights reserved.
This file is part of the Fat-Free Framework (http://fatfreeframework.com).
This is free software: you can redistribute it and/or modify it under the
terms of the GNU General Public License as published by the Free Software
Foundation, either version 3 of the License, or later.
Fat-Free Framework is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received a copy of the GNU General Public License along
with Fat-Free Framework. If not, see <http://www.gnu.org/licenses/>.
*/
namespace CLI;
//! RFC6455 WebSocket server
class WS {
const
//! UUID magic string
Magic='258EAFA5-E914-47DA-95CA-C5AB0DC85B11',
//! Max packet size
Packet=65536;
//@{ Mask bits for first byte of header
const
Text=0x01,
Binary=0x02,
Close=0x08,
Ping=0x09,
Pong=0x0a,
OpCode=0x0f,
Finale=0x80;
//@}
//@{ Mask bits for second byte of header
const
Length=0x7f;
//@}
protected
$addr,
$ctx,
$wait,
$sockets,
$agents=[],
$events=[];
/**
* Allocate stream socket
* @return NULL
* @param $socket resource
**/
function alloc($socket) {
if (is_bool($str=$this->read($socket))) {
$this->close($socket);
return;
}
// Get WebSocket headers
$hdrs=[];
$CRLF="\r\n";
$verb=NULL;
$uri=NULL;
foreach (explode($CRLF,trim($str)) as $line)
if (preg_match('/^(\w+)\s(.+)\sHTTP\/1\.\d$/',
trim($line),$match)) {
$verb=$match[1];
$uri=$match[2];
}
else
if (preg_match('/^(.+): (.+)/',trim($line),$match))
// Standardize header
$hdrs[
strtr(
ucwords(
strtolower(
strtr($match[1],'-',' ')
)
),' ','-'
)
]=$match[2];
else {
$this->close($socket);
return;
}
if (empty($hdrs['Upgrade']) &&
empty($hdrs['Sec-Websocket-Key'])) {
// Not a WebSocket request
if ($verb && $uri)
$this->write(
$socket,
$str='HTTP/1.1 400 Bad Request'.$CRLF.
'Connection: close'.$CRLF.$CRLF
);
$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) {
// Connect agent to server
$this->sockets[]=$socket;
$this->agents[(int)$socket]=
new Agent($this,$socket,$verb,$uri,$hdrs);
}
else
$this->close($socket);
}
/**
* Close stream socket
* @return NULL
* @param $socket resource
**/
function close($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 (isset($this->events['error']) &&
is_callable($func=$this->events['error']))
$func($this);
return FALSE;
}
/**
* Write to stream socket
* @return int|FALSE
* @param $socket resource
* @param $str string
**/
function write($socket,$str) {
for ($i=0,$bytes=0;$i<strlen($str);$i+=$bytes) {
if (($bytes=@fwrite($socket,substr($str,$i))) &&
@fflush($socket))
continue;
if (isset($this->events['error']) &&
is_callable($func=$this->events['error']))
$func($this);
return FALSE;
}
return $bytes;
}
/**
* Return socket agents
* @return array
* @param $uri string
***/
function agents($uri=NULL) {
return array_filter(
$this->agents,
function($val) use($uri) {
return $uri?($val->uri()==$uri):TRUE;
}
);
}
/**
* Return event handlers
* @return array
**/
function events() {
return $this->events;
}
/**
* Bind function to event handler
* @return object
* @param $event string
* @param $func callable
**/
function on($event,$func) {
$this->events[$event]=$func;
return $this;
}
/**
* Terminate server
* @return NULL
* @param $signal int
**/
function kill($signal) {
die;
}
/**
* Execute the server process
* @return object
**/
function run() {
$fw=\Base::instance();
// Assign signal handlers
declare(ticks=1);
pcntl_signal(SIGINT,[$this,'kill']);
pcntl_signal(SIGTERM,[$this,'kill']);
gc_enable();
// Activate WebSocket listener
$listen=stream_socket_server(
$this->addr,$errno,$errstr,
STREAM_SERVER_BIND|STREAM_SERVER_LISTEN,
$this->ctx
);
$socket=socket_import_stream($listen);
register_shutdown_function(function() use($listen) {
foreach ($this->sockets as $socket)
if ($socket!=$listen)
$this->free($socket);
$this->close($listen);
if (isset($this->events['stop']) &&
is_callable($func=$this->events['stop']))
$func($this);
});
if ($errstr)
user_error($errstr,E_USER_ERROR);
if (isset($this->events['start']) &&
is_callable($func=$this->events['start']))
$func($this);
$this->sockets=[$listen];
$empty=[];
$wait=$this->wait;
while (TRUE) {
$active=$this->sockets;
$mark=microtime(TRUE);
$count=@stream_select(
$active,$empty,$empty,(int)$wait,round(1e6*($wait-(int)$wait))
);
if (is_bool($count) && $wait) {
if (isset($this->events['error']) &&
is_callable($func=$this->events['error']))
$func($this);
die;
}
if ($count) {
// Process active connections
foreach ($active as $socket) {
if (!is_resource($socket))
continue;
if ($socket==$listen) {
if ($socket=@stream_socket_accept($listen,0))
$this->alloc($socket);
else
if (isset($this->events['error']) &&
is_callable($func=$this->events['error']))
$func($this);
}
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;
}
}
}
}
$wait-=microtime(TRUE)-$mark;
while ($wait<1e-6) {
$wait+=$this->wait;
$count=0;
}
}
if (!$count) {
$mark=microtime(TRUE);
foreach ($this->sockets as $socket) {
if (!is_resource($socket))
continue;
$id=(int)$socket;
if ($socket!=$listen &&
isset($this->agents[$id]) &&
isset($this->events['idle']) &&
is_callable($func=$this->events['idle']))
$func($this->agents[$id]);
}
$wait=$this->wait-microtime(TRUE)+$mark;
}
gc_collect_cycles();
}
}
/**
* Instantiate object
* @return object
* @param $addr string
* @param $ctx resource
* @param $wait int
**/
function __construct($addr,$ctx=NULL,$wait=60) {
$this->addr=$addr;
$this->ctx=$ctx?:stream_context_create();
$this->wait=$wait;
$this->events=[];
}
}
//! RFC6455 remote socket
class Agent {
protected
$server,
$id,
$socket,
$flag,
$verb,
$uri,
$headers,
$events,
$buffer;
/**
* Return server instance
* @return object
**/
function server() {
return $this->server;
}
/**
* Return socket ID
* @return string
**/
function id() {
return $this->id;
}
/**
* Return request method
* @return string
**/
function verb() {
return $this->verb;
}
/**
* Return request URI
* @return string
**/
function uri() {
return $this->uri;
}
/**
* Return socket headers
* @return string
**/
function headers() {
return $this->headers;
}
/**
* Frame and transmit payload
* @return string|FALSE
* @param $socket resource
* @param $op int
* @param $payload string
**/
function send($op,$data='') {
$mask=WS::Finale | $op & WS::OpCode;
$len=strlen($data);
$str='';
if ($len>0xffff)
$str=pack('CCNN',$mask,0x7f,$len);
else
if ($len>0x7d)
$str=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();
return FALSE;
}
if (!in_array($op,[WS::Pong,WS::Close]) &&
isset($this->events['send']) &&
is_callable($func=$this->events['send']))
$func($this,$op,$data);
return $data;
}
/**
* Retrieve and unmask payload
* @return array|FALSE
**/
function fetch() {
// Unmask payload
$server=$this->server();
if (is_bool($buf=$server->read($this->socket))) {
$this->free();
return FALSE;
}
$buf=($this->buffer.=$buf);
$op=ord($buf[0]) & WS::OpCode;
$len=ord($buf[1]) & WS::Length;
$pos=2;
if ($len==0x7e) {
$len=ord($buf[2])*256+ord($buf[3]);
$pos+=2;
}
else
if ($len==0x7f) {
for ($i=0,$len=0;$i<8;$i++)
$len=$len*256+ord($buf[$i+2]);
$pos+=8;
}
for ($i=0,$mask=[];$i<4;$i++)
$mask[$i]=ord($buf[$pos+$i]);
$pos+=4;
if (strlen($buf)<$len+$pos)
return FALSE;
for ($i=0,$data='';$i<$len;$i++)
$data.=chr(ord($buf[$pos+$i])^$mask[$i%4]);
$this->buffer='';
return [$op,$data];
}
/**
* Free stream socket
* @return NULL
**/
function free() {
$this->server->free($this->socket);
}
/**
* Destroy object
* @return NULL
**/
function __destruct() {
if (isset($this->events['disconnect']) &&
is_callable($func=$this->events['disconnect']))
$func($this);
}
/**
* Instantiate object
* @return object
* @param $server object
* @param $socket resource
* @param $verb string
* @param $uri string
* @param $hdrs array
**/
function __construct($server,$socket,$verb,$uri,array $hdrs) {
$this->server=$server;
$this->id=stream_socket_get_name($socket,TRUE);
$this->socket=$socket;
$this->verb=$verb;
$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);
}
}

View File

@@ -2,7 +2,7 @@
/*
Copyright (c) 2009-2015 F3::Factory/Bong Cosca, All rights reserved.
Copyright (c) 2009-2016 F3::Factory/Bong Cosca, All rights reserved.
This file is part of the Fat-Free Framework (http://fatfreeframework.com).
@@ -32,11 +32,11 @@ abstract class Cursor extends \Magic implements \IteratorAggregate {
protected
//! Query results
$query=array(),
$query=[],
//! Current position
$ptr=0,
//! Event listeners
$trigger=array();
$trigger=[];
/**
* Return database type
@@ -126,7 +126,7 @@ abstract class Cursor extends \Magic implements \IteratorAggregate {
**/
function findone($filter=NULL,array $options=NULL,$ttl=0) {
if (!$options)
$options=array();
$options=[];
// Override limit
$options['limit']=1;
return ($data=$this->find($filter,$options,$ttl))?$data[0]:FALSE;
@@ -148,11 +148,11 @@ abstract class Cursor extends \Magic implements \IteratorAggregate {
$total=$this->count($filter,$ttl);
$count=ceil($total/$size);
$pos=max(0,min($pos,$count-1));
return array(
return [
'subset'=>$this->find($filter,
array_merge(
$options?:array(),
array('limit'=>$size,'offset'=>$pos*$size)
$options?:[],
['limit'=>$size,'offset'=>$pos*$size]
),
$ttl
),
@@ -160,7 +160,7 @@ abstract class Cursor extends \Magic implements \IteratorAggregate {
'limit'=>$size,
'count'=>$count,
'pos'=>$pos<$count?$pos:0
);
];
}
/**
@@ -378,7 +378,7 @@ abstract class Cursor extends \Magic implements \IteratorAggregate {
* @return NULL
**/
function reset() {
$this->query=array();
$this->query=[];
$this->ptr=0;
}

View File

@@ -2,7 +2,7 @@
/*
Copyright (c) 2009-2015 F3::Factory/Bong Cosca, All rights reserved.
Copyright (c) 2009-2016 F3::Factory/Bong Cosca, All rights reserved.
This file is part of the Fat-Free Framework (http://fatfreeframework.com).
@@ -51,7 +51,7 @@ class Jig {
function &read($file) {
if (!$this->dir || !is_file($dst=$this->dir.$file)) {
if (!isset($this->data[$file]))
$this->data[$file]=array();
$this->data[$file]=[];
return $this->data[$file];
}
$fw=\Base::instance();
@@ -106,11 +106,14 @@ class Jig {
}
/**
* Return profiler results
* Return profiler results (or disable logging)
* @param $flag bool
* @return string
**/
function log() {
return $this->log;
function log($flag=TRUE) {
if ($flag)
return $this->log;
$this->log=FALSE;
}
/**
@@ -129,12 +132,16 @@ class Jig {
**/
function drop() {
if (!$this->dir)
$this->data=array();
$this->data=[];
elseif ($glob=@glob($this->dir.'/*',GLOB_NOSORT))
foreach ($glob as $file)
@unlink($file);
}
//! Prohibit cloning
private function __clone() {
}
/**
* Instantiate class
* @param $dir string

View File

@@ -2,7 +2,7 @@
/*
Copyright (c) 2009-2015 F3::Factory/Bong Cosca, All rights reserved.
Copyright (c) 2009-2016 F3::Factory/Bong Cosca, All rights reserved.
This file is part of the Fat-Free Framework (http://fatfreeframework.com).
@@ -33,7 +33,7 @@ class Mapper extends \DB\Cursor {
//! Document identifier
$id,
//! Document contents
$document=array();
$document=[];
/**
* Return database type
@@ -97,7 +97,7 @@ class Mapper extends \DB\Cursor {
$mapper->id=$id;
foreach ($row as $field=>$val)
$mapper->document[$field]=$val;
$mapper->query=array(clone($mapper));
$mapper->query=[clone($mapper)];
if (isset($mapper->trigger['load']))
\Base::instance()->call($mapper->trigger['load'],$mapper);
return $mapper;
@@ -111,7 +111,7 @@ class Mapper extends \DB\Cursor {
function cast($obj=NULL) {
if (!$obj)
$obj=$this;
return $obj->document+array('_id'=>$this->id);
return $obj->document+['_id'=>$this->id];
}
/**
@@ -157,20 +157,20 @@ class Mapper extends \DB\Cursor {
**/
function find($filter=NULL,array $options=NULL,$ttl=0,$log=TRUE) {
if (!$options)
$options=array();
$options+=array(
$options=[];
$options+=[
'order'=>NULL,
'limit'=>0,
'offset'=>0
);
];
$fw=\Base::instance();
$cache=\Cache::instance();
$db=$this->db;
$now=microtime(TRUE);
$data=array();
$data=[];
if (!$fw->get('CACHE') || !$ttl || !($cached=$cache->exists(
$hash=$fw->hash($this->db->dir().
$fw->stringify(array($filter,$options))).'.jig',$data)) ||
$fw->stringify([$filter,$options])).'.jig',$data)) ||
$cached[0]+$ttl<microtime(TRUE)) {
$data=$db->read($this->file);
if (is_null($data))
@@ -188,8 +188,8 @@ class Mapper extends \DB\Cursor {
$args=isset($filter[1]) && is_array($filter[1])?
$filter[1]:
array_slice($filter,1,NULL,TRUE);
$args=is_array($args)?$args:array(1=>$args);
$keys=$vals=array();
$args=is_array($args)?$args:[1=>$args];
$keys=$vals=[];
$tokens=array_slice(
token_get_all('<?php '.$this->token($expr)),1);
$data=array_filter($data,
@@ -248,7 +248,7 @@ class Mapper extends \DB\Cursor {
$val1[$col]=NULL;
if (!array_key_exists($col,$val2))
$val2[$col]=NULL;
list($v1,$v2)=array($val1[$col],$val2[$col]);
list($v1,$v2)=[$val1[$col],$val2[$col]];
if ($out=strnatcmp($v1,$v2)*
(($order==SORT_ASC)*2-1))
return $out;
@@ -263,7 +263,7 @@ class Mapper extends \DB\Cursor {
// Save to cache backend
$cache->set($hash,$data,$ttl);
}
$out=array();
$out=[];
foreach ($data as $id=>&$doc) {
unset($doc['_id']);
$out[]=$this->factory($id,$doc);
@@ -303,7 +303,7 @@ class Mapper extends \DB\Cursor {
* @param $ofs int
**/
function skip($ofs=1) {
$this->document=($out=parent::skip($ofs))?$out->document:array();
$this->document=($out=parent::skip($ofs))?$out->document:[];
$this->id=$out?$out->id:NULL;
if ($this->document && isset($this->trigger['load']))
\Base::instance()->call($this->trigger['load'],$this);
@@ -324,10 +324,10 @@ class Mapper extends \DB\Cursor {
!connection_aborted())
usleep(mt_rand(0,100));
$this->id=$id;
$pkey=array('_id'=>$this->id);
$pkey=['_id'=>$this->id];
if (isset($this->trigger['beforeinsert']) &&
\Base::instance()->call($this->trigger['beforeinsert'],
array($this,$pkey))===FALSE)
[$this,$pkey])===FALSE)
return $this->document;
$data[$id]=$this->document;
$db->write($this->file,$data);
@@ -335,8 +335,8 @@ class Mapper extends \DB\Cursor {
$this->file.' [insert] '.json_encode($this->document));
if (isset($this->trigger['afterinsert']))
\Base::instance()->call($this->trigger['afterinsert'],
array($this,$pkey));
$this->load(array('@_id=?',$this->id));
[$this,$pkey]);
$this->load(['@_id=?',$this->id]);
return $this->document;
}
@@ -350,7 +350,7 @@ class Mapper extends \DB\Cursor {
$data=&$db->read($this->file);
if (isset($this->trigger['beforeupdate']) &&
\Base::instance()->call($this->trigger['beforeupdate'],
array($this,array('_id'=>$this->id)))===FALSE)
[$this,['_id'=>$this->id]])===FALSE)
return $this->document;
$data[$this->id]=$this->document;
$db->write($this->file,$data);
@@ -358,7 +358,7 @@ class Mapper extends \DB\Cursor {
$this->file.' [update] '.json_encode($this->document));
if (isset($this->trigger['afterupdate']))
\Base::instance()->call($this->trigger['afterupdate'],
array($this,array('_id'=>$this->id)));
[$this,['_id'=>$this->id]]);
return $this->document;
}
@@ -371,7 +371,7 @@ class Mapper extends \DB\Cursor {
$db=$this->db;
$now=microtime(TRUE);
$data=&$db->read($this->file);
$pkey=array('_id'=>$this->id);
$pkey=['_id'=>$this->id];
if ($filter) {
foreach ($this->find($filter,NULL,FALSE) as $mapper)
if (!$mapper->erase())
@@ -386,14 +386,14 @@ class Mapper extends \DB\Cursor {
return FALSE;
if (isset($this->trigger['beforeerase']) &&
\Base::instance()->call($this->trigger['beforeerase'],
array($this,$pkey))===FALSE)
[$this,$pkey])===FALSE)
return FALSE;
$db->write($this->file,$data);
if ($filter) {
$args=isset($filter[1]) && is_array($filter[1])?
$filter[1]:
array_slice($filter,1,NULL,TRUE);
$args=is_array($args)?$args:array(1=>$args);
$args=is_array($args)?$args:[1=>$args];
foreach ($args as $key=>$val) {
$vals[]=\Base::instance()->
stringify(is_array($val)?$val[0]:$val);
@@ -405,7 +405,7 @@ class Mapper extends \DB\Cursor {
($filter?preg_replace($keys,$vals,$filter[0],1):''));
if (isset($this->trigger['aftererase']))
\Base::instance()->call($this->trigger['aftererase'],
array($this,$pkey));
[$this,$pkey]);
return TRUE;
}
@@ -415,7 +415,7 @@ class Mapper extends \DB\Cursor {
**/
function reset() {
$this->id=NULL;
$this->document=array();
$this->document=[];
parent::reset();
}

View File

@@ -2,7 +2,7 @@
/*
Copyright (c) 2009-2015 F3::Factory/Bong Cosca, All rights reserved.
Copyright (c) 2009-2016 F3::Factory/Bong Cosca, All rights reserved.
This file is part of the Fat-Free Framework (http://fatfreeframework.com).
@@ -63,12 +63,13 @@ class Session extends Mapper {
* @param $id string
**/
function read($id) {
$this->load(array('@session_id=?',$this->sid=$id));
$this->load(['@session_id=?',$this->sid=$id]);
if ($this->dry())
return FALSE;
if ($this->get('ip')!=$this->_ip || $this->get('agent')!=$this->_agent) {
$fw=\Base::instance();
if (!isset($this->onsuspect) || FALSE===$fw->call($this->onsuspect,array($this,$id))) {
if (!isset($this->onsuspect) ||
$fw->call($this->onsuspect,[$this,$id])===FALSE) {
//NB: `session_destroy` can't be called at that stage (`session_start` not completed)
$this->destroy($id);
$this->close();
@@ -101,7 +102,7 @@ class Session extends Mapper {
* @param $id string
**/
function destroy($id) {
$this->erase(array('@session_id=?',$id));
$this->erase(['@session_id=?',$id]);
return TRUE;
}
@@ -111,7 +112,7 @@ class Session extends Mapper {
* @param $max int
**/
function cleanup($max) {
$this->erase(array('@stamp+?<?',$max,time()));
$this->erase(['@stamp+?<?',$max,time()]);
return TRUE;
}
@@ -168,18 +169,17 @@ class Session extends Mapper {
parent::__construct($db,$file);
$this->onsuspect=$onsuspect;
session_set_save_handler(
array($this,'open'),
array($this,'close'),
array($this,'read'),
array($this,'write'),
array($this,'destroy'),
array($this,'cleanup')
[$this,'open'],
[$this,'close'],
[$this,'read'],
[$this,'write'],
[$this,'destroy'],
[$this,'cleanup']
);
register_shutdown_function('session_commit');
$fw=\Base::instance();
$headers=$fw->get('HEADERS');
$this->_csrf=$fw->hash($fw->get('ROOT').$fw->get('BASE')).'.'.
$fw->hash(mt_rand());
$this->_csrf=$fw->get('SEED').'.'.$fw->hash(mt_rand());
if ($key)
$fw->set($key,$this->_csrf);
$this->_agent=isset($headers['User-Agent'])?$headers['User-Agent']:'';

View File

@@ -2,7 +2,7 @@
/*
Copyright (c) 2009-2015 F3::Factory/Bong Cosca, All rights reserved.
Copyright (c) 2009-2016 F3::Factory/Bong Cosca, All rights reserved.
This file is part of the Fat-Free Framework (http://fatfreeframework.com).
@@ -57,21 +57,27 @@ class Mongo {
}
/**
* Return MongoDB profiler results
* Return MongoDB profiler results (or disable logging)
* @param $flag bool
* @return string
**/
function log() {
$cursor=$this->selectcollection('system.profile')->find();
foreach (iterator_to_array($cursor) as $frame)
if (!preg_match('/\.system\..+$/',$frame['ns']))
$this->log.=date('r',$frame['ts']->sec).' ('.
sprintf('%.1f',$frame['millis']).'ms) '.
$frame['ns'].' ['.$frame['op'].'] '.
(empty($frame['query'])?
'':json_encode($frame['query'])).
(empty($frame['command'])?
'':json_encode($frame['command'])).
PHP_EOL;
function log($flag=TRUE) {
if ($flag) {
$cursor=$this->selectcollection('system.profile')->find();
foreach (iterator_to_array($cursor) as $frame)
if (!preg_match('/\.system\..+$/',$frame['ns']))
$this->log.=date('r',$frame['ts']->sec).' ('.
sprintf('%.1f',$frame['millis']).'ms) '.
$frame['ns'].' ['.$frame['op'].'] '.
(empty($frame['query'])?
'':json_encode($frame['query'])).
(empty($frame['command'])?
'':json_encode($frame['command'])).
PHP_EOL;
} else {
$this->log=FALSE;
$this->setprofilinglevel(-1);
}
return $this->log;
}
@@ -81,7 +87,8 @@ class Mongo {
**/
function drop() {
$out=$this->db->drop();
$this->setprofilinglevel(2);
if ($this->log!==FALSE)
$this->setprofilinglevel(2);
return $out;
}
@@ -92,7 +99,11 @@ class Mongo {
* @param $args array
**/
function __call($func,array $args) {
return call_user_func_array(array($this->db,$func),$args);
return call_user_func_array([$this->db,$func],$args);
}
//! Prohibit cloning
private function __clone() {
}
/**
@@ -104,7 +115,7 @@ class Mongo {
function __construct($dsn,$dbname,array $options=NULL) {
$this->uuid=\Base::instance()->hash($this->dsn=$dsn);
$class=class_exists('\MongoClient')?'\MongoClient':'\Mongo';
$this->db=new \MongoDB(new $class($dsn,$options?:array()),$dbname);
$this->db=new \MongoDB(new $class($dsn,$options?:[]),$dbname);
$this->setprofilinglevel(2);
}

View File

@@ -2,7 +2,7 @@
/*
Copyright (c) 2009-2015 F3::Factory/Bong Cosca, All rights reserved.
Copyright (c) 2009-2016 F3::Factory/Bong Cosca, All rights reserved.
This file is part of the Fat-Free Framework (http://fatfreeframework.com).
@@ -31,9 +31,11 @@ class Mapper extends \DB\Cursor {
//! Mongo collection
$collection,
//! Mongo document
$document=array(),
$document=[],
//! Mongo cursor
$cursor;
$cursor,
//! Defined fields
$fields;
/**
* Return database type
@@ -92,7 +94,7 @@ class Mapper extends \DB\Cursor {
$mapper->reset();
foreach ($row as $key=>$val)
$mapper->document[$key]=$val;
$mapper->query=array(clone($mapper));
$mapper->query=[clone($mapper)];
if (isset($mapper->trigger['load']))
\Base::instance()->call($mapper->trigger['load'],$mapper);
return $mapper;
@@ -119,48 +121,48 @@ class Mapper extends \DB\Cursor {
**/
function select($fields=NULL,$filter=NULL,array $options=NULL,$ttl=0) {
if (!$options)
$options=array();
$options+=array(
$options=[];
$options+=[
'group'=>NULL,
'order'=>NULL,
'limit'=>0,
'offset'=>0
);
];
$fw=\Base::instance();
$cache=\Cache::instance();
if (!($cached=$cache->exists($hash=$fw->hash($this->db->dsn().
$fw->stringify(array($fields,$filter,$options))).'.mongo',
$fw->stringify([$fields,$filter,$options])).'.mongo',
$result)) || !$ttl || $cached[0]+$ttl<microtime(TRUE)) {
if ($options['group']) {
$grp=$this->collection->group(
$options['group']['keys'],
$options['group']['initial'],
$options['group']['reduce'],
array(
[
'condition'=>$filter,
'finalize'=>$options['group']['finalize']
)
]
);
$tmp=$this->db->selectcollection(
$fw->get('HOST').'.'.$fw->get('BASE').'.'.
uniqid(NULL,TRUE).'.tmp'
);
$tmp->batchinsert($grp['retval'],array('w'=>1));
$filter=array();
$tmp->batchinsert($grp['retval'],['w'=>1]);
$filter=[];
$collection=$tmp;
}
else {
$filter=$filter?:array();
$filter=$filter?:[];
$collection=$this->collection;
}
$this->cursor=$collection->find($filter,$fields?:array());
$this->cursor=$collection->find($filter,$fields?:[]);
if ($options['order'])
$this->cursor=$this->cursor->sort($options['order']);
if ($options['limit'])
$this->cursor=$this->cursor->limit($options['limit']);
if ($options['offset'])
$this->cursor=$this->cursor->skip($options['offset']);
$result=array();
$result=[];
while ($this->cursor->hasnext())
$result[]=$this->cursor->getnext();
if ($options['group'])
@@ -169,7 +171,7 @@ class Mapper extends \DB\Cursor {
// Save to cache backend
$cache->set($hash,$result,$ttl);
}
$out=array();
$out=[];
foreach ($result as $doc)
$out[]=$this->factory($doc);
return $out;
@@ -184,14 +186,14 @@ class Mapper extends \DB\Cursor {
**/
function find($filter=NULL,array $options=NULL,$ttl=0) {
if (!$options)
$options=array();
$options+=array(
$options=[];
$options+=[
'group'=>NULL,
'order'=>NULL,
'limit'=>0,
'offset'=>0
);
return $this->select(NULL,$filter,$options,$ttl);
];
return $this->select($this->fields,$filter,$options,$ttl);
}
/**
@@ -204,9 +206,9 @@ class Mapper extends \DB\Cursor {
$fw=\Base::instance();
$cache=\Cache::instance();
if (!($cached=$cache->exists($hash=$fw->hash($fw->stringify(
array($filter))).'.mongo',$result)) || !$ttl ||
[$filter])).'.mongo',$result)) || !$ttl ||
$cached[0]+$ttl<microtime(TRUE)) {
$result=$this->collection->count($filter?:array());
$result=$this->collection->count($filter?:[]);
if ($fw->get('CACHE') && $ttl)
// Save to cache backend
$cache->set($hash,$result,$ttl);
@@ -221,7 +223,7 @@ class Mapper extends \DB\Cursor {
* @param $ofs int
**/
function skip($ofs=1) {
$this->document=($out=parent::skip($ofs))?$out->document:array();
$this->document=($out=parent::skip($ofs))?$out->document:[];
if ($this->document && isset($this->trigger['load']))
\Base::instance()->call($this->trigger['load'],$this);
return $out;
@@ -236,13 +238,13 @@ class Mapper extends \DB\Cursor {
return $this->update();
if (isset($this->trigger['beforeinsert']) &&
\Base::instance()->call($this->trigger['beforeinsert'],
array($this,array('_id'=>$this->document['_id'])))===FALSE)
[$this,['_id'=>$this->document['_id']]])===FALSE)
return $this->document;
$this->collection->insert($this->document);
$pkey=array('_id'=>$this->document['_id']);
$pkey=['_id'=>$this->document['_id']];
if (isset($this->trigger['afterinsert']))
\Base::instance()->call($this->trigger['afterinsert'],
array($this,$pkey));
[$this,$pkey]);
$this->load($pkey);
return $this->document;
}
@@ -252,16 +254,16 @@ class Mapper extends \DB\Cursor {
* @return array
**/
function update() {
$pkey=array('_id'=>$this->document['_id']);
$pkey=['_id'=>$this->document['_id']];
if (isset($this->trigger['beforeupdate']) &&
\Base::instance()->call($this->trigger['beforeupdate'],
array($this,$pkey))===FALSE)
[$this,$pkey])===FALSE)
return $this->document;
$this->collection->update(
$pkey,$this->document,array('upsert'=>TRUE));
$pkey,$this->document,['upsert'=>TRUE]);
if (isset($this->trigger['afterupdate']))
\Base::instance()->call($this->trigger['afterupdate'],
array($this,$pkey));
[$this,$pkey]);
return $this->document;
}
@@ -273,17 +275,17 @@ class Mapper extends \DB\Cursor {
function erase($filter=NULL) {
if ($filter)
return $this->collection->remove($filter);
$pkey=array('_id'=>$this->document['_id']);
$pkey=['_id'=>$this->document['_id']];
if (isset($this->trigger['beforeerase']) &&
\Base::instance()->call($this->trigger['beforeerase'],
array($this,$pkey))===FALSE)
[$this,$pkey])===FALSE)
return FALSE;
$result=$this->collection->
remove(array('_id'=>$this->document['_id']));
remove(['_id'=>$this->document['_id']]);
parent::erase();
if (isset($this->trigger['aftererase']))
\Base::instance()->call($this->trigger['aftererase'],
array($this,$pkey));
[$this,$pkey]);
return $result;
}
@@ -292,7 +294,7 @@ class Mapper extends \DB\Cursor {
* @return NULL
**/
function reset() {
$this->document=array();
$this->document=[];
parent::reset();
}
@@ -351,10 +353,12 @@ class Mapper extends \DB\Cursor {
* @return void
* @param $db object
* @param $collection string
* @param $fields array
**/
function __construct(\DB\Mongo $db,$collection) {
function __construct(\DB\Mongo $db,$collection,$fields=NULL) {
$this->db=$db;
$this->collection=$db->selectcollection($collection);
$this->fields=$fields;
$this->reset();
}

View File

@@ -2,7 +2,7 @@
/*
Copyright (c) 2009-2015 F3::Factory/Bong Cosca, All rights reserved.
Copyright (c) 2009-2016 F3::Factory/Bong Cosca, All rights reserved.
This file is part of the Fat-Free Framework (http://fatfreeframework.com).
@@ -63,12 +63,13 @@ class Session extends Mapper {
* @param $id string
**/
function read($id) {
$this->load(array('session_id'=>$this->sid=$id));
$this->load(['session_id'=>$this->sid=$id]);
if ($this->dry())
return FALSE;
if ($this->get('ip')!=$this->_ip || $this->get('agent')!=$this->_agent) {
$fw=\Base::instance();
if (!isset($this->onsuspect) || FALSE===$fw->call($this->onsuspect,array($this,$id))) {
if (!isset($this->onsuspect) ||
$fw->call($this->onsuspect,[$this,$id])===FALSE) {
//NB: `session_destroy` can't be called at that stage (`session_start` not completed)
$this->destroy($id);
$this->close();
@@ -101,7 +102,7 @@ class Session extends Mapper {
* @param $id string
**/
function destroy($id) {
$this->erase(array('session_id'=>$id));
$this->erase(['session_id'=>$id]);
return TRUE;
}
@@ -111,7 +112,7 @@ class Session extends Mapper {
* @param $max int
**/
function cleanup($max) {
$this->erase(array('$where'=>'this.stamp+'.$max.'<'.time()));
$this->erase(['$where'=>'this.stamp+'.$max.'<'.time()]);
return TRUE;
}
@@ -168,18 +169,17 @@ class Session extends Mapper {
parent::__construct($db,$table);
$this->onsuspect=$onsuspect;
session_set_save_handler(
array($this,'open'),
array($this,'close'),
array($this,'read'),
array($this,'write'),
array($this,'destroy'),
array($this,'cleanup')
[$this,'open'],
[$this,'close'],
[$this,'read'],
[$this,'write'],
[$this,'destroy'],
[$this,'cleanup']
);
register_shutdown_function('session_commit');
$fw=\Base::instance();
$headers=$fw->get('HEADERS');
$this->_csrf=$fw->hash($fw->get('ROOT').$fw->get('BASE')).'.'.
$fw->hash(mt_rand());
$this->_csrf=$fw->get('SEED').'.'.$fw->hash(mt_rand());
if ($key)
$fw->set($key,$this->_csrf);
$this->_agent=isset($headers['User-Agent'])?$headers['User-Agent']:'';

View File

@@ -2,7 +2,7 @@
/*
Copyright (c) 2009-2015 F3::Factory/Bong Cosca, All rights reserved.
Copyright (c) 2009-2016 F3::Factory/Bong Cosca, All rights reserved.
This file is part of the Fat-Free Framework (http://fatfreeframework.com).
@@ -30,6 +30,9 @@ class SQL {
E_PKey='Table %s does not have a primary key';
//@}
const
PARAM_FLOAT='float';
protected
//! UUID
$uuid,
@@ -78,6 +81,14 @@ class SQL {
return $out;
}
/**
* Return transaction flag
* @return bool
**/
function trans() {
return $this->trans;
}
/**
* Map data type of argument to a PDO constant
* @return int
@@ -93,6 +104,8 @@ class SQL {
return \PDO::PARAM_INT;
case 'resource':
return \PDO::PARAM_LOB;
case 'float':
return self::PARAM_FLOAT;
default:
return \PDO::PARAM_STR;
}
@@ -106,6 +119,10 @@ class SQL {
**/
function value($type,$val) {
switch ($type) {
case self::PARAM_FLOAT:
return (float)(is_string($val)
? str_replace(',','.',preg_replace('/([.,])(?!\d+$)/','',$val))
: $val);
case \PDO::PARAM_NULL:
return (unset)$val;
case \PDO::PARAM_INT:
@@ -124,15 +141,19 @@ class SQL {
* @return array|int|FALSE
* @param $cmds string|array
* @param $args string|array
* @param $ttl int
* @param $ttl int|array
* @param $log bool
* @param $stamp bool
**/
function exec($cmds,$args=NULL,$ttl=0,$log=TRUE) {
function exec($cmds,$args=NULL,$ttl=0,$log=TRUE,$stamp=FALSE) {
$tag='';
if (is_array($ttl))
list($ttl,$tag)=$ttl;
$auto=FALSE;
if (is_null($args))
$args=array();
$args=[];
elseif (is_scalar($args))
$args=array(1=>$args);
$args=[1=>$args];
if (is_array($cmds)) {
if (count($args)<($count=count($cmds)))
// Apply arguments to SQL commands
@@ -144,9 +165,11 @@ class SQL {
}
else {
$count=1;
$cmds=array($cmds);
$args=array($args);
$cmds=[$cmds];
$args=[$args];
}
if ($this->log===FALSE)
$log=FALSE;
$fw=\Base::instance();
$cache=\Cache::instance();
$result=FALSE;
@@ -161,10 +184,10 @@ class SQL {
if (!preg_replace('/(^\s+|[\s;]+$)/','',$cmd))
continue;
$now=microtime(TRUE);
$keys=$vals=array();
$keys=$vals=[];
if ($fw->get('CACHE') && $ttl && ($cached=$cache->exists(
$hash=$fw->hash($this->dsn.$cmd.
$fw->stringify($arg)).'.sql',$result)) &&
$fw->stringify($arg)).($tag?'.'.$tag:'').'.sql',$result)) &&
$cached[0]+$ttl>microtime(TRUE)) {
foreach ($arg as $key=>$val) {
$vals[]=$fw->stringify(is_array($val)?$val[0]:$val);
@@ -172,7 +195,7 @@ class SQL {
'/';
}
if ($log)
$this->log.=date('r').' ('.
$this->log.=($stamp?(date('r').' '):'').'('.
sprintf('%.1f',1e3*(microtime(TRUE)-$now)).'ms) '.
'[CACHED] '.
preg_replace($keys,$vals,
@@ -182,20 +205,22 @@ class SQL {
foreach ($arg as $key=>$val) {
if (is_array($val)) {
// User-specified data type
$query->bindvalue($key,$val[0],$val[1]);
$query->bindvalue($key,$val[0],
$val[1]==self::PARAM_FLOAT?\PDO::PARAM_STR:$val[1]);
$vals[]=$fw->stringify($this->value($val[1],$val[0]));
}
else {
// Convert to PDO data type
$query->bindvalue($key,$val,
$type=$this->type($val));
($type=$this->type($val))==self::PARAM_FLOAT?
\PDO::PARAM_STR:$type);
$vals[]=$fw->stringify($this->value($type,$val));
}
$keys[]='/'.preg_quote(is_numeric($key)?chr(0).'?':$key).
'/';
}
if ($log)
$this->log.=date('r').' ('.
$this->log.=($stamp?(date('r').' '):'').'('.
sprintf('%.1f',1e3*(microtime(TRUE)-$now)).'ms) '.
preg_replace($keys,$vals,
str_replace('?',chr(0).'?',$cmd),1).PHP_EOL;
@@ -216,7 +241,7 @@ class SQL {
if (preg_match('/sqlite2?/',$this->engine))
foreach ($result as $pos=>$rec) {
unset($result[$pos]);
$result[$pos]=array();
$result[$pos]=[];
foreach ($rec as $key=>$val)
$result[$pos][trim($key,'\'"[]`')]=$val;
}
@@ -269,20 +294,20 @@ class SQL {
* @return array|FALSE
* @param $table string
* @param $fields array|string
* @param $ttl int
* @param $ttl int|array
**/
function schema($table,$fields=NULL,$ttl=0) {
if (strpos($table,'.'))
list($schema,$table)=explode('.',$table);
// Supported engines
$cmd=array(
'sqlite2?'=>array(
'PRAGMA table_info("'.$table.'");',
'name','type','dflt_value','notnull',0,'pk',TRUE),
'mysql'=>array(
'SHOW columns FROM `'.$this->dbname.'`.`'.$table.'`;',
'Field','Type','Default','Null','YES','Key','PRI'),
'mssql|sqlsrv|sybase|dblib|pgsql|odbc'=>array(
$cmd=[
'sqlite2?'=>[
'PRAGMA table_info("'.$table.'")',
'name','type','dflt_value','notnull',0,'pk',TRUE],
'mysql'=>[
'SHOW columns FROM `'.$this->dbname.'`.`'.$table.'`',
'Field','Type','Default','Null','YES','Key','PRI'],
'mssql|sqlsrv|sybase|dblib|pgsql|odbc'=>[
'SELECT '.
'c.column_name AS field,'.
'c.data_type AS type,'.
@@ -309,10 +334,9 @@ class SQL {
'c.table_name='.$this->quote($table).
($this->dbname?
(' AND c.table_catalog='.
$this->quote($this->dbname)):'').
';',
'field','type','defval','nullable','YES','pkey','PRIMARY KEY'),
'oci'=>array(
$this->quote($this->dbname)):''),
'field','type','defval','nullable','YES','pkey','PRIMARY KEY'],
'oci'=>[
'SELECT c.column_name AS field, '.
'c.data_type AS type, '.
'c.data_default AS defval, '.
@@ -326,35 +350,35 @@ class SQL {
'AND constraint_type='.$this->quote('P').') AS pkey '.
'FROM all_tab_cols c '.
'WHERE c.table_name='.$this->quote($table),
'FIELD','TYPE','DEFVAL','NULLABLE','Y','PKEY','P')
);
'FIELD','TYPE','DEFVAL','NULLABLE','Y','PKEY','P']
];
if (is_string($fields))
$fields=\Base::instance()->split($fields);
foreach ($cmd as $key=>$val)
if (preg_match('/'.$key.'/',$this->engine)) {
// Improve InnoDB performance on MySQL with
// SET GLOBAL innodb_stats_on_metadata=0;
// This requires SUPER privilege!
$rows=array();
$rows=[];
foreach ($this->exec($val[0],NULL,$ttl) as $row) {
if (!$fields || in_array($row[$val[1]],$fields))
$rows[$row[$val[1]]]=array(
$rows[$row[$val[1]]]=[
'type'=>$row[$val[2]],
'pdo_type'=>
preg_match('/int\b|integer/i',$row[$val[2]])?
\PDO::PARAM_INT:
(preg_match('/bool/i',$row[$val[2]])?
\PDO::PARAM_BOOL:
(preg_match('/blob|bytea|image|binary/i',
$row[$val[2]])?
\PDO::PARAM_LOB:
\PDO::PARAM_STR)),
(preg_match(
'/blob|bytea|image|binary/i',
$row[$val[2]])?\PDO::PARAM_LOB:
(preg_match(
'/float|decimal|real|numeric|double/i',
$row[$val[2]])?self::PARAM_FLOAT:
\PDO::PARAM_STR))),
'default'=>is_string($row[$val[3]])?
preg_replace('/^\s*([\'"])(.*)\1\s*/','\2',
$row[$val[3]]):$row[$val[3]],
'nullable'=>$row[$val[4]]==$val[5],
'pkey'=>$row[$val[6]]==$val[7]
);
];
}
return $rows;
}
@@ -420,20 +444,22 @@ class SQL {
* Return quoted identifier name
* @return string
* @param $key
**/
function quotekey($key) {
$delims=array(
* @param bool $split
**/
function quotekey($key, $split=TRUE) {
$delims=[
'mysql'=>'``',
'sqlite2?|pgsql|oci'=>'""',
'mssql|sqlsrv|odbc|sybase|dblib'=>'[]'
);
];
$use='';
foreach ($delims as $engine=>$delim)
if (preg_match('/'.$engine.'/',$this->engine)) {
$use=$delim;
break;
}
return $use[0].implode($use[1].'.'.$use[0],explode('.',$key)).$use[1];
return $use[0].($split ? implode($use[1].'.'.$use[0],explode('.',$key))
: $key).$use[1];
}
/**
@@ -443,7 +469,11 @@ class SQL {
* @param $args array
**/
function __call($func,array $args) {
return call_user_func_array(array($this->pdo,$func),$args);
return call_user_func_array([$this->pdo,$func],$args);
}
//! Prohibit cloning
private function __clone() {
}
/**
@@ -459,10 +489,10 @@ class SQL {
if (preg_match('/^.+?(?:dbname|database)=(.+?)(?=;|$)/is',$dsn,$parts))
$this->dbname=$parts[1];
if (!$options)
$options=array();
$options=[];
if (isset($parts[0]) && strstr($parts[0],':',TRUE)=='mysql')
$options+=array(\PDO::MYSQL_ATTR_INIT_COMMAND=>'SET NAMES '.
strtolower(str_replace('-','',$fw->get('ENCODING'))).';');
$options+=[\PDO::MYSQL_ATTR_INIT_COMMAND=>'SET NAMES '.
strtolower(str_replace('-','',$fw->get('ENCODING'))).';'];
$this->pdo=new \PDO($dsn,$user,$pw,$options);
$this->engine=$this->pdo->getattribute(\PDO::ATTR_DRIVER_NAME);
}

View File

@@ -2,7 +2,7 @@
/*
Copyright (c) 2009-2015 F3::Factory/Bong Cosca, All rights reserved.
Copyright (c) 2009-2016 F3::Factory/Bong Cosca, All rights reserved.
This file is part of the Fat-Free Framework (http://fatfreeframework.com).
@@ -39,7 +39,7 @@ class Mapper extends \DB\Cursor {
//! Defined fields
$fields,
//! Adhoc fields
$adhoc=array();
$adhoc=[];
/**
* Return database type
@@ -90,7 +90,7 @@ class Mapper extends \DB\Cursor {
if (array_key_exists($key,$this->fields)) {
$val=is_null($val) && $this->fields[$key]['nullable']?
NULL:$this->db->value($this->fields[$key]['pdo_type'],$val);
if ($this->fields[$key]['value']!==$val ||
if ($this->fields[$key]['initial']!==$val ||
$this->fields[$key]['default']!==$val && is_null($val))
$this->fields[$key]['changed']=TRUE;
return $this->fields[$key]['value']=$val;
@@ -100,7 +100,7 @@ class Mapper extends \DB\Cursor {
$this->adhoc[$key]['value']=$val;
else
// Parenthesize expression in case it's a subquery
$this->adhoc[$key]=array('expr'=>'('.$val.')','value'=>NULL);
$this->adhoc[$key]=['expr'=>'('.$val.')','value'=>NULL];
return $val;
}
@@ -144,6 +144,8 @@ class Mapper extends \DB\Cursor {
return 'bool';
case \PDO::PARAM_STR:
return 'string';
case \DB\SQL::PARAM_FLOAT:
return 'float';
}
}
@@ -163,10 +165,11 @@ class Mapper extends \DB\Cursor {
else
continue;
$mapper->{$var}[$key]['value']=$val;
$mapper->{$var}[$key]['initial']=$val;
if ($var=='fields' && $mapper->{$var}[$key]['pkey'])
$mapper->{$var}[$key]['previous']=$val;
}
$mapper->query=array(clone($mapper));
$mapper->query=[clone($mapper)];
if (isset($mapper->trigger['load']))
\Base::instance()->call($mapper->trigger['load'],$mapper);
return $mapper;
@@ -194,37 +197,36 @@ class Mapper extends \DB\Cursor {
* @param $fields string
* @param $filter string|array
* @param $options array
* @param $ttl int
* @param $ttl int|array
**/
function select($fields,$filter=NULL,array $options=NULL,$ttl=0) {
if (!$options)
$options=array();
$options+=array(
$options=[];
$options+=[
'group'=>NULL,
'order'=>NULL,
'limit'=>0,
'offset'=>0
);
];
$db=$this->db;
$sql='SELECT '.$fields.' FROM '.$this->table;
$args=array();
if ($filter) {
if (is_array($filter)) {
$args=isset($filter[1]) && is_array($filter[1])?
$filter[1]:
array_slice($filter,1,NULL,TRUE);
$args=is_array($args)?$args:array(1=>$args);
list($filter)=$filter;
}
$sql.=' WHERE '.$filter;
$args=[];
if (is_array($filter)) {
$args=isset($filter[1]) && is_array($filter[1])?
$filter[1]:
array_slice($filter,1,NULL,TRUE);
$args=is_array($args)?$args:[1=>$args];
list($filter)=$filter;
}
if ($filter)
$sql.=' WHERE '.$filter;
if ($options['group']) {
$sql.=' GROUP BY '.implode(',',array_map(
function($str) use($db) {
return preg_replace_callback(
'/\b(\w+)\h*(HAVING.+|$)/i',
function($parts) use($db) {
return $db->quotekey($parts[1]);
return $db->quotekey($parts[1]).(isset($parts[2])?(' '.$parts[2]):'');
},
$str
);
@@ -243,7 +245,7 @@ class Mapper extends \DB\Cursor {
}
if (preg_match('/mssql|sqlsrv|odbc/', $this->engine) &&
($options['limit'] || $options['offset'])) {
$pkeys=array();
$pkeys=[];
foreach ($this->fields as $key=>$field)
if ($field['pkey'])
$pkeys[]=$key;
@@ -274,7 +276,7 @@ class Mapper extends \DB\Cursor {
$sql.=' OFFSET '.(int)$options['offset'];
}
$result=$this->db->exec($sql,$args,$ttl);
$out=array();
$out=[];
foreach ($result as &$row) {
foreach ($row as $field=>&$val) {
if (array_key_exists($field,$this->fields)) {
@@ -297,24 +299,24 @@ class Mapper extends \DB\Cursor {
* @return static[]
* @param $filter string|array
* @param $options array
* @param $ttl int
* @param $ttl int|array
**/
function find($filter=NULL,array $options=NULL,$ttl=0) {
if (!$options)
$options=array();
$options+=array(
$options=[];
$options+=[
'group'=>NULL,
'order'=>NULL,
'limit'=>0,
'offset'=>0
);
];
$adhoc='';
foreach ($this->adhoc as $key=>$field)
$adhoc.=','.$field['expr'].' AS '.$this->db->quotekey($key);
return $this->select(
($options['group'] && !preg_match('/mysql|sqlite/',$this->engine)?
$options['group']:
implode(',',array_map(array($this->db,'quotekey'),
implode(',',array_map([$this->db,'quotekey'],
array_keys($this->fields)))).$adhoc,$filter,$options,$ttl);
}
@@ -322,18 +324,18 @@ class Mapper extends \DB\Cursor {
* Count records that match criteria
* @return int
* @param $filter string|array
* @param $ttl int
* @param $ttl int|array
**/
function count($filter=NULL,$ttl=0) {
$sql='SELECT COUNT(*) AS '.
$this->db->quotekey('rows').' FROM '.$this->table;
$args=array();
$args=[];
if ($filter) {
if (is_array($filter)) {
$args=isset($filter[1]) && is_array($filter[1])?
$filter[1]:
array_slice($filter,1,NULL,TRUE);
$args=is_array($args)?$args:array(1=>$args);
$args=is_array($args)?$args:[1=>$args];
list($filter)=$filter;
}
$sql.=' WHERE '.$filter;
@@ -353,6 +355,7 @@ class Mapper extends \DB\Cursor {
$dry=$this->dry();
foreach ($this->fields as $key=>&$field) {
$field['value']=$dry?NULL:$out->fields[$key]['value'];
$field['initial']=$field['value'];
$field['changed']=FALSE;
if ($field['pkey'])
$field['previous']=$dry?NULL:$out->fields[$key]['value'];
@@ -372,22 +375,22 @@ class Mapper extends \DB\Cursor {
* @return object
**/
function insert() {
$args=array();
$args=[];
$actr=0;
$nctr=0;
$fields='';
$values='';
$filter='';
$pkeys=array();
$nkeys=array();
$ckeys=array();
$pkeys=[];
$nkeys=[];
$ckeys=[];
$inc=NULL;
foreach ($this->fields as $key=>$field)
if ($field['pkey'])
$pkeys[$key]=$field['previous'];
if (isset($this->trigger['beforeinsert']) &&
\Base::instance()->call($this->trigger['beforeinsert'],
array($this,$pkeys))===FALSE)
[$this,$pkeys])===FALSE)
return $this;
foreach ($this->fields as $key=>&$field) {
if ($field['pkey']) {
@@ -396,13 +399,13 @@ class Mapper extends \DB\Cursor {
empty($field['value']) && !$field['nullable'])
$inc=$key;
$filter.=($filter?' AND ':'').$this->db->quotekey($key).'=?';
$nkeys[$nctr+1]=array($field['value'],$field['pdo_type']);
$nkeys[$nctr+1]=[$field['value'],$field['pdo_type']];
$nctr++;
}
if ($field['changed'] && $key!=$inc) {
$fields.=($actr?',':'').$this->db->quotekey($key);
$values.=($actr?',':'').'?';
$args[$actr+1]=array($field['value'],$field['pdo_type']);
$args[$actr+1]=[$field['value'],$field['pdo_type']];
$actr++;
$ckeys[]=$key;
}
@@ -420,18 +423,21 @@ class Mapper extends \DB\Cursor {
$seq=NULL;
if ($this->engine=='pgsql') {
$names=array_keys($pkeys);
$seq=$this->source.'_'.end($names).'_seq';
$aik=end($names);
if ($this->fields[$aik]['pdo_type']==\PDO::PARAM_INT)
$seq=$this->source.'_'.$aik.'_seq';
}
if ($this->engine!='oci')
if ($this->engine!='oci' && !($this->engine=='pgsql' && !$seq))
$this->_id=$this->db->lastinsertid($seq);
// Reload to obtain default and auto-increment field values
$this->load($inc?
array($inc.'=?',$this->db->value(
$this->fields[$inc]['pdo_type'],$this->_id)):
array($filter,$nkeys));
if ($inc || $filter)
$this->load($inc?
[$inc.'=?',$this->db->value(
$this->fields[$inc]['pdo_type'],$this->_id)]:
[$filter,$nkeys]);
if (isset($this->trigger['afterinsert']))
\Base::instance()->call($this->trigger['afterinsert'],
array($this,$pkeys));
[$this,$pkeys]);
}
return $this;
}
@@ -441,38 +447,42 @@ class Mapper extends \DB\Cursor {
* @return object
**/
function update() {
$args=array();
$args=[];
$ctr=0;
$pairs='';
$filter='';
$pkeys=array();
$pkeys=[];
foreach ($this->fields as $key=>$field)
if ($field['pkey'])
$pkeys[$key]=$field['previous'];
if (isset($this->trigger['beforeupdate']) &&
\Base::instance()->call($this->trigger['beforeupdate'],
array($this,$pkeys))===FALSE)
[$this,$pkeys])===FALSE)
return $this;
foreach ($this->fields as $key=>$field)
if ($field['changed']) {
$pairs.=($pairs?',':'').$this->db->quotekey($key).'=?';
$args[$ctr+1]=array($field['value'],$field['pdo_type']);
$ctr++;
$args[++$ctr]=[$field['value'],$field['pdo_type']];
}
foreach ($this->fields as $key=>$field)
if ($field['pkey']) {
$filter.=($filter?' AND ':' WHERE ').
$this->db->quotekey($key).'=?';
$args[$ctr+1]=array($field['previous'],$field['pdo_type']);
$ctr++;
$args[++$ctr]=[$field['previous'],$field['pdo_type']];
}
if ($pairs) {
$sql='UPDATE '.$this->table.' SET '.$pairs.$filter;
$this->db->exec($sql,$args);
if (isset($this->trigger['afterupdate']))
\Base::instance()->call($this->trigger['afterupdate'],
array($this,$pkeys));
[$this,$pkeys]);
}
// reset changed flag after calling afterupdate
foreach ($this->fields as $key=>&$field) {
$field['changed']=FALSE;
$field['initial']=$field['value'];
unset($field);
}
return $this;
}
@@ -483,25 +493,25 @@ class Mapper extends \DB\Cursor {
**/
function erase($filter=NULL) {
if ($filter) {
$args=array();
$args=[];
if (is_array($filter)) {
$args=isset($filter[1]) && is_array($filter[1])?
$filter[1]:
array_slice($filter,1,NULL,TRUE);
$args=is_array($args)?$args:array(1=>$args);
$args=is_array($args)?$args:[1=>$args];
list($filter)=$filter;
}
return $this->db->
exec('DELETE FROM '.$this->table.' WHERE '.$filter.';',$args);
}
$args=array();
$args=[];
$ctr=0;
$filter='';
$pkeys=array();
$pkeys=[];
foreach ($this->fields as $key=>&$field) {
if ($field['pkey']) {
$filter.=($filter?' AND ':'').$this->db->quotekey($key).'=?';
$args[$ctr+1]=array($field['previous'],$field['pdo_type']);
$args[$ctr+1]=[$field['previous'],$field['pdo_type']];
$pkeys[$key]=$field['previous'];
$ctr++;
}
@@ -518,13 +528,13 @@ class Mapper extends \DB\Cursor {
parent::erase();
if (isset($this->trigger['beforeerase']) &&
\Base::instance()->call($this->trigger['beforeerase'],
array($this,$pkeys))===FALSE)
[$this,$pkeys])===FALSE)
return 0;
$out=$this->db->
exec('DELETE FROM '.$this->table.' WHERE '.$filter.';',$args);
if (isset($this->trigger['aftererase']))
\Base::instance()->call($this->trigger['aftererase'],
array($this,$pkeys));
[$this,$pkeys]);
return $out;
}
@@ -535,6 +545,7 @@ class Mapper extends \DB\Cursor {
function reset() {
foreach ($this->fields as &$field) {
$field['value']=NULL;
$field['initial']=NULL;
$field['changed']=FALSE;
if ($field['pkey'])
$field['previous']=NULL;
@@ -591,7 +602,7 @@ class Mapper extends \DB\Cursor {
* @param $adhoc bool
**/
function fields($adhoc=TRUE) {
return array_keys($this->fields+($adhoc?$this->adhoc:array()));
return array_keys($this->fields+($adhoc?$this->adhoc:[]));
}
/**
@@ -617,7 +628,7 @@ class Mapper extends \DB\Cursor {
* @param $db object
* @param $table string
* @param $fields array|string
* @param $ttl int
* @param $ttl int|array
**/
function __construct(\DB\SQL $db,$table,$fields=NULL,$ttl=60) {
$this->db=$db;

View File

@@ -2,7 +2,7 @@
/*
Copyright (c) 2009-2015 F3::Factory/Bong Cosca, All rights reserved.
Copyright (c) 2009-2016 F3::Factory/Bong Cosca, All rights reserved.
This file is part of the Fat-Free Framework (http://fatfreeframework.com).
@@ -63,12 +63,13 @@ class Session extends Mapper {
* @param $id string
**/
function read($id) {
$this->load(array('session_id=?',$this->sid=$id));
$this->load(['session_id=?',$this->sid=$id]);
if ($this->dry())
return FALSE;
if ($this->get('ip')!=$this->_ip || $this->get('agent')!=$this->_agent) {
$fw=\Base::instance();
if (!isset($this->onsuspect) || FALSE===$fw->call($this->onsuspect,array($this,$id))) {
if (!isset($this->onsuspect) ||
$fw->call($this->onsuspect,[$this,$id])===FALSE) {
//NB: `session_destroy` can't be called at that stage (`session_start` not completed)
$this->destroy($id);
$this->close();
@@ -101,7 +102,7 @@ class Session extends Mapper {
* @param $id string
**/
function destroy($id) {
$this->erase(array('session_id=?',$id));
$this->erase(['session_id=?',$id]);
return TRUE;
}
@@ -111,7 +112,7 @@ class Session extends Mapper {
* @param $max int
**/
function cleanup($max) {
$this->erase(array('stamp+?<?',$max,time()));
$this->erase(['stamp+?<?',$max,time()]);
return TRUE;
}
@@ -176,12 +177,12 @@ class Session extends Mapper {
'CREATE TABLE dbo.'):
('CREATE TABLE IF NOT EXISTS '.
((($name=$db->name())&&$db->driver()!='pgsql')?
($name.'.'):''))).
$table.' ('.$eol.
($db->quotekey($name,FALSE).'.'):''))).
$db->quotekey($table,FALSE).' ('.$eol.
$tab.$db->quotekey('session_id').' VARCHAR(255),'.$eol.
$tab.$db->quotekey('data').' TEXT,'.$eol.
$tab.$db->quotekey('ip').' VARCHAR(45),'.$eol.
$tab.$db->quotekey('agent').' VARCHAR(255),'.$eol.
$tab.$db->quotekey('agent').' VARCHAR(300),'.$eol.
$tab.$db->quotekey('stamp').' INTEGER,'.$eol.
$tab.'PRIMARY KEY ('.$db->quotekey('session_id').')'.$eol.
');'
@@ -190,18 +191,17 @@ class Session extends Mapper {
parent::__construct($db,$table);
$this->onsuspect=$onsuspect;
session_set_save_handler(
array($this,'open'),
array($this,'close'),
array($this,'read'),
array($this,'write'),
array($this,'destroy'),
array($this,'cleanup')
[$this,'open'],
[$this,'close'],
[$this,'read'],
[$this,'write'],
[$this,'destroy'],
[$this,'cleanup']
);
register_shutdown_function('session_commit');
$fw=\Base::instance();
$headers=$fw->get('HEADERS');
$this->_csrf=$fw->hash($fw->get('ROOT').$fw->get('BASE')).'.'.
$fw->hash(mt_rand());
$this->_csrf=$fw->get('SEED').'.'.$fw->hash(mt_rand());
if ($key)
$fw->set($key,$this->_csrf);
$this->_agent=isset($headers['User-Agent'])?$headers['User-Agent']:'';

View File

@@ -2,7 +2,7 @@
/*
Copyright (c) 2009-2015 F3::Factory/Bong Cosca, All rights reserved.
Copyright (c) 2009-2016 F3::Factory/Bong Cosca, All rights reserved.
This file is part of the Fat-Free Framework (http://fatfreeframework.com).
@@ -36,7 +36,7 @@ class F3 {
static function __callstatic($func,array $args) {
if (!self::$fw)
self::$fw=Base::instance();
return call_user_func_array(array(self::$fw,$func),$args);
return call_user_func_array([self::$fw,$func],$args);
}
}

View File

@@ -2,7 +2,7 @@
/*
Copyright (c) 2009-2015 F3::Factory/Bong Cosca, All rights reserved.
Copyright (c) 2009-2016 F3::Factory/Bong Cosca, All rights reserved.
This file is part of the Fat-Free Framework (http://fatfreeframework.com).
@@ -28,6 +28,7 @@ class Image {
E_Color='Invalid color specified: %s',
E_File='File not found',
E_Font='CAPTCHA font not found',
E_TTF='No TrueType support in GD module',
E_Length='Invalid CAPTCHA length: %s';
//@}
@@ -54,9 +55,11 @@ class Image {
/**
* Convert RGB hex triad to array
* @return array|FALSE
* @param $color int
* @param $color int|string
**/
function rgb($color) {
if (is_string($color))
$color=hexdec($color);
$hex=str_pad($hex=dechex($color),$color<4096?3:6,'0',STR_PAD_LEFT);
if (($len=strlen($hex))>6)
user_error(sprintf(self::E_Color,'0x'.$hex),E_USER_ERROR);
@@ -225,9 +228,17 @@ class Image {
* @param $crop bool
* @param $enlarge bool
**/
function resize($width,$height,$crop=TRUE,$enlarge=TRUE) {
function resize($width=NULL,$height=NULL,$crop=TRUE,$enlarge=TRUE) {
if (is_null($width) && is_null($height))
return $this;
$origw=$this->width();
$origh=$this->height();
if (is_null($width))
$width=round(($height/$origh)*$origw);
if (is_null($height))
$height=round(($width/$origw)*$origh);
// Adjust dimensions; retain aspect ratio
$ratio=($origw=imagesx($this->data))/($origh=imagesy($this->data));
$ratio=$origw/$origh;
if (!$crop) {
if ($width/$ratio<=$height)
$height=round($width/$ratio);
@@ -331,24 +342,24 @@ class Image {
* @param $blocks int
**/
function identicon($str,$size=64,$blocks=4) {
$sprites=array(
array(.5,1,1,0,1,1),
array(.5,0,1,0,.5,1,0,1),
array(.5,0,1,0,1,1,.5,1,1,.5),
array(0,.5,.5,0,1,.5,.5,1,.5,.5),
array(0,.5,1,0,1,1,0,1,1,.5),
array(1,0,1,1,.5,1,1,.5,.5,.5),
array(0,0,1,0,1,.5,0,0,.5,1,0,1),
array(0,0,.5,0,1,.5,.5,1,0,1,.5,.5),
array(.5,0,.5,.5,1,.5,1,1,.5,1,.5,.5,0,.5),
array(0,0,1,0,.5,.5,1,.5,.5,1,.5,.5,0,1),
array(0,.5,.5,1,1,.5,.5,0,1,0,1,1,0,1),
array(.5,0,1,0,1,1,.5,1,1,.75,.5,.5,1,.25),
array(0,.5,.5,0,.5,.5,1,0,1,.5,.5,1,.5,.5,0,1),
array(0,0,1,0,1,1,0,1,1,.5,.5,.25,.5,.75,0,.5,.5,.25),
array(0,.5,.5,.5,.5,0,1,0,.5,.5,1,.5,.5,1,.5,.5,0,1),
array(0,0,1,0,.5,.5,.5,0,0,.5,1,.5,.5,1,.5,.5,0,1)
);
$sprites=[
[.5,1,1,0,1,1],
[.5,0,1,0,.5,1,0,1],
[.5,0,1,0,1,1,.5,1,1,.5],
[0,.5,.5,0,1,.5,.5,1,.5,.5],
[0,.5,1,0,1,1,0,1,1,.5],
[1,0,1,1,.5,1,1,.5,.5,.5],
[0,0,1,0,1,.5,0,0,.5,1,0,1],
[0,0,.5,0,1,.5,.5,1,0,1,.5,.5],
[.5,0,.5,.5,1,.5,1,1,.5,1,.5,.5,0,.5],
[0,0,1,0,.5,.5,1,.5,.5,1,.5,.5,0,1],
[0,.5,.5,1,1,.5,.5,0,1,0,1,1,0,1],
[.5,0,1,0,1,1,.5,1,1,.75,.5,.5,1,.25],
[0,.5,.5,0,.5,.5,1,0,1,.5,.5,1,.5,.5,0,1],
[0,0,1,0,1,1,0,1,1,.5,.5,.25,.5,.75,0,.5,.5,.25],
[0,.5,.5,.5,.5,0,1,0,.5,.5,1,.5,.5,1,.5,.5,0,1],
[0,0,1,0,.5,.5,.5,0,0,.5,1,.5,.5,1,.5,.5,0,1]
];
$hash=sha1($str);
$this->data=imagecreatetruecolor($size,$size);
list($r,$g,$b)=$this->rgb(hexdec(substr($hash,-3)));
@@ -360,15 +371,10 @@ class Image {
for ($i=$j,$x=$blocks-1-$j;$i<$x;$i++) {
$sprite=imagecreatetruecolor($dim,$dim);
imagefill($sprite,0,0,IMG_COLOR_TRANSPARENT);
if ($block=$sprites[
hexdec($hash[($j*$blocks+$i)*2])%$ctr]) {
for ($k=0,$pts=count($block);$k<$pts;$k++)
$block[$k]*=$dim;
imagefilledpolygon($sprite,$block,$pts/2,$fg);
}
$sprite=imagerotate($sprite,
90*(hexdec($hash[($j*$blocks+$i)*2+1])%4),
imagecolorallocatealpha($sprite,0,0,0,127));
$block=$sprites[hexdec($hash[($j*$blocks+$i)*2])%$ctr];
for ($k=0,$pts=count($block);$k<$pts;$k++)
$block[$k]*=$dim;
imagefilledpolygon($sprite,$block,$pts/2,$fg);
for ($k=0;$k<4;$k++) {
imagecopyresampled($this->data,$sprite,
$i*$dim/2,$j*$dim/2,0,0,$dim/2,$dim/2,$dim,$dim);
@@ -398,6 +404,10 @@ class Image {
user_error(sprintf(self::E_Length,$len),E_USER_ERROR);
return FALSE;
}
if (!function_exists('imagettftext')) {
user_error(self::E_TTF,E_USER_ERROR);
return FALSE;
}
$fw=Base::instance();
foreach ($fw->split($path?:$fw->get('UI').';./') as $dir)
if (is_file($path=$dir.$font)) {
@@ -405,7 +415,7 @@ class Image {
$ssl?bin2hex(openssl_random_pseudo_bytes($len)):uniqid(),
-$len));
$block=$size*3;
$tmp=array();
$tmp=[];
for ($i=0,$width=0,$height=0;$i<$len;$i++) {
// Process at 2x magnification
$box=imagettfbbox($size*2,0,$path,$seed[$i]);
@@ -472,8 +482,10 @@ class Image {
header('Content-Type: image/'.$format);
header('X-Powered-By: '.Base::instance()->get('PACKAGE'));
}
call_user_func_array('image'.$format,
array_merge(array($this->data),$args));
call_user_func_array(
'image'.$format,
array_merge([$this->data,NULL],$args)
);
}
/**
@@ -484,8 +496,10 @@ class Image {
$args=func_get_args();
$format=$args?array_shift($args):'png';
ob_start();
call_user_func_array('image'.$format,
array_merge(array($this->data),$args));
call_user_func_array(
'image'.$format,
array_merge([$this->data,NULL],$args)
);
return ob_get_clean();
}
@@ -507,8 +521,7 @@ class Image {
if (!is_dir($dir=$fw->get('TEMP')))
mkdir($dir,Base::MODE,TRUE);
$this->count++;
$fw->write($dir.'/'.
$fw->hash($fw->get('ROOT').$fw->get('BASE')).'.'.
$fw->write($dir.'/'.$fw->get('SEED').'.'.
$fw->hash($this->file).'-'.$this->count.'.png',
$this->dump());
}
@@ -523,8 +536,7 @@ class Image {
function restore($state=1) {
$fw=Base::instance();
if ($this->flag && is_file($file=($path=$fw->get('TEMP').
$fw->hash($fw->get('ROOT').$fw->get('BASE')).'.'.
$fw->hash($this->file).'-').$state.'.png')) {
$fw->get('SEED').'.'.$fw->hash($this->file).'-').$state.'.png')) {
if (is_resource($this->data))
imagedestroy($this->data);
$this->data=imagecreatefromstring($fw->read($file));
@@ -553,11 +565,12 @@ class Image {
/**
* Load string
* @return object
* @return object|FALSE
* @param $str string
**/
function load($str) {
$this->data=imagecreatefromstring($str);
if (!$this->data=@imagecreatefromstring($str))
return FALSE;
imagesavealpha($this->data,TRUE);
$this->save();
return $this;
@@ -592,9 +605,7 @@ class Image {
if (is_resource($this->data)) {
imagedestroy($this->data);
$fw=Base::instance();
$path=$fw->get('TEMP').
$fw->hash($fw->get('ROOT').$fw->get('BASE')).'.'.
$fw->hash($this->file);
$path=$fw->get('TEMP').$fw->get('SEED').'.'.$fw->hash($this->file);
if ($glob=@glob($path.'*.png',GLOB_NOSORT))
foreach ($glob as $match)
if (preg_match('/-(\d+)\.png/',$match))

View File

@@ -2,7 +2,7 @@
/*
Copyright (c) 2009-2015 F3::Factory/Bong Cosca, All rights reserved.
Copyright (c) 2009-2016 F3::Factory/Bong Cosca, All rights reserved.
This file is part of the Fat-Free Framework (http://fatfreeframework.com).

View File

@@ -2,7 +2,7 @@
/*
Copyright (c) 2009-2015 F3::Factory/Bong Cosca, All rights reserved.
Copyright (c) 2009-2016 F3::Factory/Bong Cosca, All rights reserved.
This file is part of the Fat-Free Framework (http://fatfreeframework.com).
@@ -70,7 +70,7 @@ abstract class Magic implements ArrayAccess {
**/
function offsetset($key,$val) {
return Base::instance()->visible($this,$key)?
($this->key=$val):$this->set($key,$val);
($this->$key=$val):$this->set($key,$val);
}
/**

View File

@@ -2,7 +2,7 @@
/*
Copyright (c) 2009-2015 F3::Factory/Bong Cosca, All rights reserved.
Copyright (c) 2009-2016 F3::Factory/Bong Cosca, All rights reserved.
This file is part of the Fat-Free Framework (http://fatfreeframework.com).
@@ -321,7 +321,7 @@ class Markdown extends Prefab {
$tmp='';
while ($str!=$tmp)
$str=preg_replace_callback(
'/(?<!\\\\)([*_]{1,3})(.*?)(?!\\\\)\1(?=[\s[:punct:]]|$)/',
'/(?<=\s|^)(?<!\\\\)([*_]{1,3})(.*?)(?!\\\\)\1(?=[\s[:punct:]]|$)/',
function($expr) {
switch (strlen($expr[1])) {
case 1:
@@ -427,12 +427,12 @@ class Markdown extends Prefab {
**/
function esc($str) {
if (!$this->special)
$this->special=array(
$this->special=[
'...'=>'&hellip;',
'(tm)'=>'&trade;',
'(r)'=>'&reg;',
'(c)'=>'&copy;'
);
];
foreach ($this->special as $key=>$val)
$str=preg_replace('/'.preg_quote($key,'/').'/i',$val,$str);
return htmlspecialchars($str,ENT_COMPAT,
@@ -454,7 +454,7 @@ class Markdown extends Prefab {
* @param $str string
**/
function scan($str) {
$inline=array('img','a','text','auto','code');
$inline=['img','a','text','auto','code'];
foreach ($inline as $func)
$str=$this->{'_'.$func}($str);
return $str;
@@ -468,7 +468,7 @@ class Markdown extends Prefab {
protected function build($str) {
if (!$this->blocks) {
// Regexes for capturing entire blocks
$this->blocks=array(
$this->blocks=[
'blockquote'=>'/^(?:\h?>\h?.*?(?:\n+|$))+/',
'pre'=>'/^(?:(?: {4}|\t).+?(?:\n+|$))+/',
'fence'=>'/^`{3}\h*(\w+)?.*?[^\n]*\n+(.+?)`{3}[^\n]*'.
@@ -486,7 +486,7 @@ class Markdown extends Prefab {
'(?:\/>|>(?:(?>[^><]+)|(?R))*<\/\2>))'.
'\h*(?:\n{2,}|\n*$)|<[\?%].+?[\?%]>\h*(?:\n?$|\n*))/s',
'p'=>'/^(.+?(?:\n{2,}|\n*$))/s'
);
];
}
$self=$this;
// Treat lines with nothing but whitespaces as empty lines
@@ -546,7 +546,7 @@ class Markdown extends Prefab {
if (preg_match($regex,substr($str,$ptr),$match)) {
$ptr+=strlen($match[0]);
$dst.=call_user_func_array(
array($this,'_'.$func),
[$this,'_'.$func],
count($match)>1?array_slice($match,1):$match
);
break;

View File

@@ -2,7 +2,7 @@
/*
Copyright (c) 2009-2015 F3::Factory/Bong Cosca, All rights reserved.
Copyright (c) 2009-2016 F3::Factory/Bong Cosca, All rights reserved.
This file is part of the Fat-Free Framework (http://fatfreeframework.com).
@@ -45,7 +45,7 @@ class Matrix extends Prefab {
* @param $var array
**/
function transpose(array &$var) {
$out=array();
$out=[];
foreach ($var as $keyx=>$cols)
foreach ($cols as $keyy=>$valy)
$out[$keyy][$keyx]=$valy;
@@ -63,7 +63,7 @@ class Matrix extends Prefab {
uasort(
$var,
function($val1,$val2) use($col,$order) {
list($v1,$v2)=array($val1[$col],$val2[$col]);
list($v1,$v2)=[$val1[$col],$val2[$col]];
$out=is_numeric($v1) && is_numeric($v2)?
Base::instance()->sign($v1-$v2):strcmp($v1,$v2);
if ($order==SORT_DESC)
@@ -96,12 +96,15 @@ class Matrix extends Prefab {
* @param $first int
**/
function calendar($date='now',$first=0) {
$parts=getdate(strtotime($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=array();
for ($i=0;$i<$days;$i++)
$out[floor(($ref+$i)/7)][($ref+$i)%7]=$i+1;
$out=FALSE;
if (extension_loaded('calendar')) {
$parts=getdate(strtotime($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=[];
for ($i=0;$i<$days;$i++)
$out[floor(($ref+$i)/7)][($ref+$i)%7]=$i+1;
}
return $out;
}

View File

@@ -2,7 +2,7 @@
/*
Copyright (c) 2009-2015 F3::Factory/Bong Cosca, All rights reserved.
Copyright (c) 2009-2016 F3::Factory/Bong Cosca, All rights reserved.
This file is part of the Fat-Free Framework (http://fatfreeframework.com).
@@ -33,7 +33,9 @@ class Session {
//! IP,
$_ip,
//! Suspect callback
$onsuspect;
$onsuspect,
//! Cache instance
$_cache;
/**
* Open session
@@ -61,11 +63,12 @@ class Session {
**/
function read($id) {
$this->sid=$id;
if (!$data=Cache::instance()->get($id.'.@'))
if (!$data=$this->_cache->get($id.'.@'))
return FALSE;
if ($data['ip']!=$this->_ip || $data['agent']!=$this->_agent) {
$fw=Base::instance();
if (!isset($this->onsuspect) || FALSE===$fw->call($this->onsuspect,array($this,$id))) {
if (!isset($this->onsuspect) ||
$fw->call($this->onsuspect,[$this,$id])===FALSE) {
//NB: `session_destroy` can't be called at that stage (`session_start` not completed)
$this->destroy($id);
$this->close();
@@ -85,13 +88,13 @@ class Session {
function write($id,$data) {
$fw=Base::instance();
$jar=$fw->get('JAR');
Cache::instance()->set($id.'.@',
array(
$this->_cache->set($id.'.@',
[
'data'=>$data,
'ip'=>$this->_ip,
'agent'=>$this->_agent,
'stamp'=>time()
),
],
$jar['expire']?($jar['expire']-time()):0
);
return TRUE;
@@ -103,7 +106,7 @@ class Session {
* @param $id string
**/
function destroy($id) {
Cache::instance()->clear($id.'.@');
$this->_cache->clear($id.'.@');
return TRUE;
}
@@ -113,7 +116,7 @@ class Session {
* @param $max int
**/
function cleanup($max) {
Cache::instance()->reset('.@',$max);
$this->_cache->reset('.@',$max);
return TRUE;
}
@@ -148,7 +151,7 @@ class Session {
function stamp() {
if (!$this->sid)
session_start();
return Cache::instance()->exists($this->sid.'.@',$data)?
return $this->_cache->exists($this->sid.'.@',$data)?
$data['stamp']:FALSE;
}
@@ -165,21 +168,21 @@ class Session {
* @param $onsuspect callback
* @param $key string
**/
function __construct($onsuspect=NULL,$key=NULL) {
function __construct($onsuspect=NULL,$key=NULL,$cache=null) {
$this->onsuspect=$onsuspect;
$this->_cache=$cache?:Cache::instance();
session_set_save_handler(
array($this,'open'),
array($this,'close'),
array($this,'read'),
array($this,'write'),
array($this,'destroy'),
array($this,'cleanup')
[$this,'open'],
[$this,'close'],
[$this,'read'],
[$this,'write'],
[$this,'destroy'],
[$this,'cleanup']
);
register_shutdown_function('session_commit');
$fw=\Base::instance();
$headers=$fw->get('HEADERS');
$this->_csrf=$fw->hash($fw->get('ROOT').$fw->get('BASE')).'.'.
$fw->hash(mt_rand());
$this->_csrf=$fw->get('SEED').'.'.$fw->hash(mt_rand());
if ($key)
$fw->set($key,$this->_csrf);
$this->_agent=isset($headers['User-Agent'])?$headers['User-Agent']:'';

View File

@@ -2,7 +2,7 @@
/*
Copyright (c) 2009-2015 F3::Factory/Bong Cosca, All rights reserved.
Copyright (c) 2009-2016 F3::Factory/Bong Cosca, All rights reserved.
This file is part of the Fat-Free Framework (http://fatfreeframework.com).
@@ -118,20 +118,41 @@ class SMTP extends Magic {
* @return string
* @param $cmd string
* @param $log bool
* @param $mock bool
**/
protected function dialog($cmd=NULL,$log=TRUE) {
$socket=&$this->socket;
if (!is_null($cmd))
fputs($socket,$cmd."\r\n");
protected function dialog($cmd=NULL,$log=TRUE,$mock=FALSE) {
$reply='';
while (!feof($socket) && ($info=stream_get_meta_data($socket)) &&
!$info['timed_out'] && $str=fgets($socket,4096)) {
$reply.=$str;
if (preg_match('/(?:^|\n)\d{3} .+?\r\n/s',$reply))
if ($mock) {
$host=str_replace('ssl://','',$this->host);
switch ($cmd) {
case NULL:
$reply='220 '.$host.' ESMTP ready'."\n";
break;
case 'DATA':
$reply='354 Go ahead'."\n";
break;
case 'QUIT':
$reply='221 '.$host.' closing connection'."\n";
break;
default:
$reply='250 OK'."\n";
break;
}
}
else {
$socket=&$this->socket;
if ($cmd)
fputs($socket,$cmd."\r\n");
while (!feof($socket) && ($info=stream_get_meta_data($socket)) &&
!$info['timed_out'] && $str=fgets($socket,4096)) {
$reply.=$str;
if (preg_match('/(?:^|\n)\d{3} .+?\r\n/s',$reply))
break;
}
}
if ($log) {
$this->log.=$cmd."\n";
if ($cmd)
$this->log.=$cmd."\n";
$this->log.=str_replace("\r",'',$reply);
}
return $reply;
@@ -147,9 +168,9 @@ class SMTP extends Magic {
function attach($file,$alias=NULL,$cid=NULL) {
if (!is_file($file))
user_error(sprintf(self::E_Attach,$file),E_USER_ERROR);
if (is_string($alias))
$file=array($alias=>$file);
$this->attachments[]=array('filename'=>$file,'cid'=>$cid);
if ($alias)
$file=[$alias=>$file];
$this->attachments[]=['filename'=>$file,'cid'=>$cid];
}
/**
@@ -157,8 +178,9 @@ class SMTP extends Magic {
* @return bool
* @param $message string
* @param $log bool
* @param $mock bool
**/
function send($message,$log=TRUE) {
function send($message,$log=TRUE,$mock=FALSE) {
if ($this->scheme=='ssl' && !extension_loaded('openssl'))
return FALSE;
// Message should not be blank
@@ -168,20 +190,25 @@ class SMTP extends Magic {
// Retrieve headers
$headers=$this->headers;
// Connect to the server
$socket=&$this->socket;
$socket=@fsockopen($this->host,$this->port);
if (!$socket)
return FALSE;
stream_set_blocking($socket,TRUE);
if (!$mock) {
$socket=&$this->socket;
$socket=@fsockopen($this->host,$this->port,$errno,$errstr);
if (!$socket) {
$fw->error(500,$errstr);
return FALSE;
}
stream_set_blocking($socket,TRUE);
}
// Get server's initial response
$this->dialog(NULL,FALSE);
$this->dialog(NULL,TRUE,$mock);
// Announce presence
$reply=$this->dialog('EHLO '.$fw->get('HOST'),$log);
$reply=$this->dialog('EHLO '.$fw->get('HOST'),$log,$mock);
if (strtolower($this->scheme)=='tls') {
$this->dialog('STARTTLS',$log);
stream_socket_enable_crypto(
$socket,TRUE,STREAM_CRYPTO_METHOD_TLS_CLIENT);
$reply=$this->dialog('EHLO '.$fw->get('HOST'),$log);
$this->dialog('STARTTLS',$log,$mock);
if (!$mock)
stream_socket_enable_crypto(
$socket,TRUE,STREAM_CRYPTO_METHOD_TLS_CLIENT);
$reply=$this->dialog('EHLO '.$fw->get('HOST'),$log,$mock);
}
if (preg_match('/8BITMIME/',$reply))
$headers['Content-Transfer-Encoding']='8bit';
@@ -192,12 +219,22 @@ class SMTP extends Magic {
}
if ($this->user && $this->pw && preg_match('/AUTH/',$reply)) {
// Authenticate
$this->dialog('AUTH LOGIN',$log);
$this->dialog(base64_encode($this->user),$log);
$this->dialog(base64_encode($this->pw),$log);
$this->dialog('AUTH LOGIN',$log,$mock);
$this->dialog(base64_encode($this->user),$log,$mock);
$auth_rply=$this->dialog(base64_encode($this->pw),$log,$mock);
if (!preg_match('/^235\s.*/',$auth_rply)) {
$this->dialog('QUIT',$log,$mock);
if (!$mock && $socket)
fclose($socket);
return FALSE;
}
}
if (empty($headers['Message-ID']))
$headers['Message-ID']='<'.uniqid('',TRUE).'@'.$this->host.'>';
if (empty($headers['Date']))
$headers['Date']=date('r');
// Required headers
$reqd=array('From','To','Subject');
$reqd=['From','To','Subject'];
foreach ($reqd as $id)
if (empty($headers[$id]))
user_error(sprintf(self::E_Header,$id),E_USER_ERROR);
@@ -205,21 +242,32 @@ class SMTP extends Magic {
$str='';
// Stringify headers
foreach ($headers as $key=>&$val) {
if (!in_array($key,$reqd) && (!$this->attachments ||
$key!='Content-Type' && $key!='Content-Transfer-Encoding'))
if (!in_array($key,$reqd) &&
(!$this->attachments ||
$key!='Content-Type' &&
$key!='Content-Transfer-Encoding'))
$str.=$key.': '.$val.$eol;
if (in_array($key,array('From','To','Cc','Bcc')) &&
!preg_match('/[<>]/',$val))
$val='<'.$val.'>';
if (in_array($key,['From','To','Cc','Bcc'])) {
$email='';
preg_match_all('/(?:".+?" )?(?:<.+?>|[^ ,]+)/',
$val,$matches,PREG_SET_ORDER);
foreach ($matches as $raw)
$email.=($email?', ':'').
(preg_match('/<.+?>/',$raw[0])?
$raw[0]:
('<'.$raw[0].'>'));
$val=$email;
}
unset($val);
}
// Start message dialog
$this->dialog('MAIL FROM: '.strstr($headers['From'],'<'),$log);
$this->dialog('MAIL FROM: '.strstr($headers['From'],'<'),$log,$mock);
foreach ($fw->split($headers['To'].
(isset($headers['Cc'])?(';'.$headers['Cc']):'').
(isset($headers['Bcc'])?(';'.$headers['Bcc']):'')) as $dst)
$this->dialog('RCPT TO: '.strstr($dst,'<'),$log);
$this->dialog('DATA',$log);
(isset($headers['Bcc'])?(';'.$headers['Bcc']):'')) as $dst) {
$this->dialog('RCPT TO: '.strstr($dst,'<'),$log,$mock);
}
$this->dialog('DATA',$log,$mock);
if ($this->attachments) {
// Replace Content-Type
$type=$headers['Content-Type'];
@@ -241,28 +289,25 @@ class SMTP extends Magic {
$out.=$str.$eol;
$out.=$message.$eol;
foreach ($this->attachments as $attachment) {
if (is_array($attachment['filename'])) {
if (is_array($attachment['filename']))
list($alias,$file)=each($attachment['filename']);
$filename=$alias;
$attachment['filename']=$file;
}
else
$filename=basename($attachment['filename']);
$alias=basename($file=$attachment['filename']);
$out.='--'.$hash.$eol;
$out.='Content-Type: application/octet-stream'.$eol;
$out.='Content-Transfer-Encoding: base64'.$eol;
if ($attachment['cid'])
$out.='Content-ID: '.$attachment['cid'].$eol;
$out.='Content-Disposition: attachment; '.
'filename="'.$filename.'"'.$eol;
'filename="'.$alias.'"'.$eol;
$out.=$eol;
$out.=chunk_split(base64_encode(
file_get_contents($attachment['filename']))).$eol;
file_get_contents($file))).$eol;
}
$out.=$eol;
$out.='--'.$hash.'--'.$eol;
$out.='.';
$this->dialog($out,FALSE);
$this->dialog($out,TRUE,$mock);
}
else {
// Send mail headers
@@ -274,10 +319,10 @@ class SMTP extends Magic {
$out.=$message.$eol;
$out.='.';
// Send message
$this->dialog($out);
$this->dialog($out,TRUE,$mock);
}
$this->dialog('QUIT',$log);
if ($socket)
$this->dialog('QUIT',$log,$mock);
if (!$mock && $socket)
fclose($socket);
return TRUE;
}
@@ -290,12 +335,13 @@ class SMTP extends Magic {
* @param $user string
* @param $pw string
**/
function __construct($host='localhost',$port=25,$scheme=null,$user=null,$pw=null) {
$this->headers=array(
function __construct(
$host='localhost',$port=25,$scheme=NULL,$user=NULL,$pw=NULL) {
$this->headers=[
'MIME-Version'=>'1.0',
'Content-Type'=>'text/plain; '.
'charset='.Base::instance()->get('ENCODING')
);
];
$this->host=$host;
if (strtolower($this->scheme=strtolower($scheme))=='ssl')
$this->host='ssl://'.$host;

View File

@@ -2,7 +2,7 @@
/*
Copyright (c) 2009-2015 F3::Factory/Bong Cosca, All rights reserved.
Copyright (c) 2009-2016 F3::Factory/Bong Cosca, All rights reserved.
This file is part of the Fat-Free Framework (http://fatfreeframework.com).
@@ -32,7 +32,7 @@ class Template extends Preview {
//! Template tags
$tags,
//! Custom tag handlers
$custom=array();
$custom=[];
/**
* Template -set- tag handler
@@ -60,14 +60,14 @@ class Template extends Preview {
($attrib['with']=$this->token($attrib['with'])) &&
preg_match_all('/(\w+)\h*=\h*(.+?)(?=,|$)/',
$attrib['with'],$pairs,PREG_SET_ORDER)?
'array('.implode(',',
('['.implode(',',
array_map(function($pair) {
return '\''.$pair[1].'\'=>'.
(preg_match('/^\'.*\'$/',$pair[2]) ||
preg_match('/\$/',$pair[2])?
$pair[2]:
\Base::instance()->stringify($pair[2]));
},$pairs)).')+get_defined_vars()':
},$pairs)).']+get_defined_vars()'):
'get_defined_vars()';
$ttl=isset($attrib['ttl'])?(int)$attrib['ttl']:0;
return
@@ -127,7 +127,7 @@ class Template extends Preview {
(isset($attrib['counter'])?
(($ctr=$this->token($attrib['counter'])).'=0; '):'').
'foreach (('.
$this->token($attrib['group']).'?:array()) as '.
$this->token($attrib['group']).'?:[]) as '.
(isset($attrib['key'])?
($this->token($attrib['key']).'=>'):'').
$this->token($attrib['value']).'):'.
@@ -147,12 +147,12 @@ class Template extends Preview {
// Grab <true> and <false> blocks
foreach ($node as $pos=>$block)
if (isset($block['true']))
$true=array($pos,$block);
$true=[$pos,$block];
elseif (isset($block['false']))
$false=array($pos,$block);
$false=[$pos,$block];
if (isset($true,$false) && $true[0]>$false[0])
// Reverse <true> and <false> blocks
list($node[$true[0]],$node[$false[0]])=array($false[1],$true[1]);
list($node[$true[0]],$node[$false[0]])=[$false[1],$true[1]];
return
'<?php if ('.$this->token($attrib['if']).'): ?>'.
$this->build($node).
@@ -229,7 +229,7 @@ class Template extends Preview {
* @return string
* @param $node array|string
**/
protected function build($node) {
function build($node) {
if (is_string($node))
return parent::build($node);
$out='';
@@ -259,7 +259,7 @@ class Template extends Preview {
if ($func[0]=='_')
return call_user_func_array($this->custom[$func],$args);
if (method_exists($this,$func))
return call_user_func_array(array($this,$func),$args);
return call_user_func_array([$this,$func],$args);
user_error(sprintf(self::E_Method,$func),E_USER_ERROR);
}
@@ -270,9 +270,9 @@ class Template extends Preview {
**/
function parse($text) {
// Build tree structure
for ($ptr=0,$w=5,$len=strlen($text),$tree=array(),$tmp='';$ptr<$len;)
for ($ptr=0,$w=5,$len=strlen($text),$tree=[],$tmp='';$ptr<$len;)
if (preg_match('/^(.{0,'.$w.'}?)<(\/?)(?:F3:)?'.
'('.$this->tags.')\b((?:\h+[\w-]+'.
'('.$this->tags.')\b((?:\s+[\w-]+'.
'(?:\h*=\h*(?:"(?:.*?)"|\'(?:.*?)\'))?|'.
'\h*\{\{.+?\}\})*)\h*(\/?)>/is',
substr($text,$ptr),$match)) {
@@ -281,7 +281,7 @@ class Template extends Preview {
// Element node
if ($match[2]) {
// Find matching start tag
$stack=array();
$stack=[];
for($i=count($tree)-1;$i>=0;$i--) {
$item = $tree[$i];
if (is_array($item) && array_key_exists($match[3],$item)
@@ -296,7 +296,7 @@ class Template extends Preview {
else {
// Start tag
$node=&$tree[][$match[3]];
$node=array();
$node=[];
if ($match[4]) {
// Process attributes
preg_match_all(

View File

@@ -2,7 +2,7 @@
/*
Copyright (c) 2009-2015 F3::Factory/Bong Cosca, All rights reserved.
Copyright (c) 2009-2016 F3::Factory/Bong Cosca, All rights reserved.
This file is part of the Fat-Free Framework (http://fatfreeframework.com).
@@ -32,7 +32,7 @@ class Test {
protected
//! Test results
$data=array(),
$data=[],
//! Success indicator
$passed=TRUE;
@@ -61,7 +61,7 @@ class Test {
function expect($cond,$text=NULL) {
$out=(bool)$cond;
if ($this->level==$out || $this->level==self::FLAG_Both) {
$data=array('status'=>$out,'text'=>$text,'source'=>NULL);
$data=['status'=>$out,'text'=>$text,'source'=>NULL];
foreach (debug_backtrace() as $frame)
if (isset($frame['file'])) {
$data['source']=Base::instance()->

View File

@@ -2,7 +2,7 @@
/*
Copyright (c) 2009-2015 F3::Factory/Bong Cosca, All rights reserved.
Copyright (c) 2009-2016 F3::Factory/Bong Cosca, All rights reserved.
This file is part of the Fat-Free Framework (http://fatfreeframework.com).
@@ -180,7 +180,7 @@ class UTF extends Prefab {
* @param $str string
**/
function emojify($str) {
$map=array(
$map=[
':('=>'\u2639', // frown
':)'=>'\u263a', // smile
'<3'=>'\u2665', // heart
@@ -191,7 +191,7 @@ class UTF extends Prefab {
':,'=>'\u1f60f', // think
':/'=>'\u1f623', // skeptic
'8O'=>'\u1f632', // oops
)+Base::instance()->get('EMOJI');
]+Base::instance()->get('EMOJI');
return $this->translate(str_replace(array_keys($map),
array_values($map),$str));
}

View File

@@ -2,7 +2,7 @@
/*
Copyright (c) 2009-2015 F3::Factory/Bong Cosca, All rights reserved.
Copyright (c) 2009-2016 F3::Factory/Bong Cosca, All rights reserved.
This file is part of the Fat-Free Framework (http://fatfreeframework.com).
@@ -39,7 +39,7 @@ class Web extends Prefab {
**/
function mime($file) {
if (preg_match('/\w+$/',$file,$ext)) {
$map=array(
$map=[
'au'=>'audio/basic',
'avi'=>'video/avi',
'bmp'=>'image/bmp',
@@ -77,7 +77,7 @@ class Web extends Prefab {
'xls'=>'application/vnd.ms-excel',
'xml'=>'application/xml',
'zip'=>'application/x-zip-compressed'
);
];
foreach ($map as $key=>$val)
if (preg_match('/'.$key.'/',strtolower($ext[0])))
return $val;
@@ -93,7 +93,7 @@ class Web extends Prefab {
* @param $list string|array
**/
function acceptable($list=NULL) {
$accept=array();
$accept=[];
foreach (explode(',',str_replace(' ','',@$_SERVER['HTTP_ACCEPT']))
as $mime)
if (preg_match('/(.+?)(?:;q=([\d\.]+)|$)/',$mime,$parts))
@@ -124,8 +124,10 @@ class Web extends Prefab {
* @param $mime string
* @param $kbps int
* @param $force bool
* @param $name string
* @param $flush bool
**/
function send($file,$mime=NULL,$kbps=0,$force=TRUE) {
function send($file,$mime=NULL,$kbps=0,$force=TRUE,$name=NULL,$flush=TRUE) {
if (!is_file($file))
return FALSE;
$size=filesize($file);
@@ -133,7 +135,7 @@ class Web extends Prefab {
header('Content-Type: '.($mime?:$this->mime($file)));
if ($force)
header('Content-Disposition: attachment; '.
'filename="'.basename($file).'"');
'filename="'.($name!==NULL?$name:basename($file)).'"');
header('Accept-Ranges: bytes');
header('Content-Length: '.$size);
header('X-Powered-By: '.Base::instance()->get('PACKAGE'));
@@ -152,6 +154,10 @@ class Web extends Prefab {
}
// Send 1KiB and reset timer
echo fread($handle,1024);
if ($flush) {
ob_flush();
flush();
}
}
fclose($handle);
return $size;
@@ -170,9 +176,7 @@ class Web extends Prefab {
if (!is_dir($dir))
mkdir($dir,Base::MODE,TRUE);
if ($fw->get('VERB')=='PUT') {
$tmp=$fw->get('TEMP').
$fw->hash($fw->get('ROOT').$fw->get('BASE')).'.'.
$fw->hash(uniqid());
$tmp=$fw->get('TEMP').$fw->get('SEED').'.'.$fw->hash(uniqid());
if (!$fw->get('RAW'))
$fw->write($tmp,$fw->get('BODY'));
else {
@@ -188,7 +192,7 @@ class Web extends Prefab {
fclose($src);
}
$base=basename($fw->get('URI'));
$file=array(
$file=[
'name'=>$dir.
($slug && preg_match('/(.+?)(\.\w+)?$/',$base,$parts)?
(is_callable($slug)?
@@ -199,24 +203,24 @@ class Web extends Prefab {
'tmp_name'=>$tmp,
'type'=>$this->mime($base),
'size'=>filesize($tmp)
);
];
return (!file_exists($file['name']) || $overwrite) &&
(!$func || $fw->call($func,array($file))!==FALSE) &&
(!$func || $fw->call($func,[$file])!==FALSE) &&
rename($tmp,$file['name']);
}
$fetch=function($arr)use(&$fetch){
$fetch=function($arr) use(&$fetch) {
if (!is_array($arr))
return array($arr);
$data=array();
return [$arr];
$data=[];
foreach($arr as $k=>$sub)
$data=array_merge($data,$fetch($sub));
return $data;
};
$out=array();
$out=[];
foreach ($_FILES as $name=>$item) {
$files=array();
foreach($item as $k=>$mix)
foreach($fetch($mix) as $i=>$val)
$files=[];
foreach ($item as $k=>$mix)
foreach ($fetch($mix) as $i=>$val)
$files[$i][$k]=$val;
foreach ($files as $file) {
if (empty($file['name']))
@@ -230,9 +234,8 @@ class Web extends Prefab {
(isset($parts[2])?$parts[2]:''))):
$base);
$out[$file['name']]=!$file['error'] &&
is_uploaded_file($file['tmp_name']) &&
(!file_exists($file['name']) || $overwrite) &&
(!$func || $fw->call($func,array($file,$name))!==FALSE) &&
(!$func || $fw->call($func,[$file,$name])!==FALSE) &&
move_uploaded_file($file['tmp_name'],$file['name']);
}
}
@@ -277,7 +280,7 @@ class Web extends Prefab {
ini_get('default_socket_timeout');
curl_setopt($curl,CURLOPT_CONNECTTIMEOUT,$timeout);
curl_setopt($curl,CURLOPT_TIMEOUT,$timeout);
$headers=array();
$headers=[];
curl_setopt($curl,CURLOPT_HEADERFUNCTION,
// Callback for response headers
function($curl,$line) use(&$headers) {
@@ -286,22 +289,32 @@ class Web extends Prefab {
return strlen($line);
}
);
curl_setopt($curl,CURLOPT_SSL_VERIFYHOST,2);
curl_setopt($curl,CURLOPT_SSL_VERIFYPEER,FALSE);
ob_start();
curl_exec($curl);
$err=curl_error($curl);
curl_close($curl);
$body=ob_get_clean();
if ($options['follow_location'] &&
if (!$err &&
$options['follow_location'] &&
preg_match('/^Location: (.+)$/m',implode(PHP_EOL,$headers),$loc)) {
$options['max_redirects']--;
if($loc[1][0] == '/') {
$parts=parse_url($url);
$loc[1]=$parts['scheme'].'://'.$parts['host'].
((isset($parts['port']) && !in_array($parts['port'],[80,443]))
?':'.$parts['port']:'').$loc[1];
}
return $this->request($loc[1],$options);
}
return array(
return [
'body'=>$body,
'headers'=>$headers,
'engine'=>'cURL',
'cached'=>FALSE
);
'cached'=>FALSE,
'error'=>$err
];
}
/**
@@ -314,28 +327,36 @@ class Web extends Prefab {
$eol="\r\n";
$options['header']=implode($eol,$options['header']);
$body=@file_get_contents($url,FALSE,
stream_context_create(array('http'=>$options)));
stream_context_create(['http'=>$options]));
$headers=isset($http_response_header)?
$http_response_header:array();
$match=NULL;
foreach ($headers as $header)
if (preg_match('/Content-Encoding: (.+)/',$header,$match))
break;
if ($match)
switch ($match[1]) {
case 'gzip':
$body=gzdecode($body);
$http_response_header:[];
$err='';
if (is_string($body)) {
$match=NULL;
foreach ($headers as $header)
if (preg_match('/Content-Encoding: (.+)/',$header,$match))
break;
case 'deflate':
$body=gzuncompress($body);
break;
}
return array(
if ($match)
switch ($match[1]) {
case 'gzip':
$body=gzdecode($body);
break;
case 'deflate':
$body=gzuncompress($body);
break;
}
}
else {
$tmp=error_get_last();
$err=$tmp['message'];
}
return [
'body'=>$body,
'headers'=>$headers,
'engine'=>'stream',
'cached'=>FALSE
);
'cached'=>FALSE,
'error'=>$err
];
}
/**
@@ -346,7 +367,7 @@ class Web extends Prefab {
**/
protected function _socket($url,$options) {
$eol="\r\n";
$headers=array();
$headers=[];
$body='';
$parts=parse_url($url);
$empty=empty($parts['port']);
@@ -361,54 +382,54 @@ class Web extends Prefab {
$parts['path']='/';
if (empty($parts['query']))
$parts['query']='';
$socket=@fsockopen($parts['host'],$parts['port']);
if (!$socket)
return FALSE;
stream_set_blocking($socket,TRUE);
stream_set_timeout($socket,isset($options['timeout'])?
$options['timeout']:ini_get('default_socket_timeout'));
fputs($socket,$options['method'].' '.$parts['path'].
($parts['query']?('?'.$parts['query']):'').' HTTP/1.0'.$eol
);
fputs($socket,implode($eol,$options['header']).$eol.$eol);
if (isset($options['content']))
fputs($socket,$options['content'].$eol);
// Get response
$content='';
while (!feof($socket) &&
($info=stream_get_meta_data($socket)) &&
!$info['timed_out'] && !connection_aborted() &&
$str=fgets($socket,4096))
$content.=$str;
fclose($socket);
$html=explode($eol.$eol,$content,2);
$body=isset($html[1])?$html[1]:'';
$headers=array_merge($headers,$current=explode($eol,$html[0]));
$match=NULL;
foreach ($current as $header)
if (preg_match('/Content-Encoding: (.+)/',$header,$match))
break;
if ($match)
switch ($match[1]) {
case 'gzip':
$body=gzdecode($body);
break;
case 'deflate':
$body=gzuncompress($body);
if ($socket=@fsockopen($parts['host'],$parts['port'],$code,$err)) {
stream_set_blocking($socket,TRUE);
stream_set_timeout($socket,isset($options['timeout'])?
$options['timeout']:ini_get('default_socket_timeout'));
fputs($socket,$options['method'].' '.$parts['path'].
($parts['query']?('?'.$parts['query']):'').' HTTP/1.0'.$eol
);
fputs($socket,implode($eol,$options['header']).$eol.$eol);
if (isset($options['content']))
fputs($socket,$options['content'].$eol);
// Get response
$content='';
while (!feof($socket) &&
($info=stream_get_meta_data($socket)) &&
!$info['timed_out'] && !connection_aborted() &&
$str=fgets($socket,4096))
$content.=$str;
fclose($socket);
$html=explode($eol.$eol,$content,2);
$body=isset($html[1])?$html[1]:'';
$headers=array_merge($headers,$current=explode($eol,$html[0]));
$match=NULL;
foreach ($current as $header)
if (preg_match('/Content-Encoding: (.+)/',$header,$match))
break;
if ($match)
switch ($match[1]) {
case 'gzip':
$body=gzdecode($body);
break;
case 'deflate':
$body=gzuncompress($body);
break;
}
if ($options['follow_location'] &&
preg_match('/Location: (.+?)'.preg_quote($eol).'/',
$html[0],$loc)) {
$options['max_redirects']--;
return $this->request($loc[1],$options);
}
if ($options['follow_location'] &&
preg_match('/Location: (.+?)'.preg_quote($eol).'/',
$html[0],$loc)) {
$options['max_redirects']--;
return $this->request($loc[1],$options);
}
return array(
return [
'body'=>$body,
'headers'=>$headers,
'engine'=>'socket',
'cached'=>FALSE
);
'cached'=>FALSE,
'error'=>$err
];
}
/**
@@ -419,17 +440,17 @@ class Web extends Prefab {
**/
function engine($arg='curl') {
$arg=strtolower($arg);
$flags=array(
$flags=[
'curl'=>extension_loaded('curl'),
'stream'=>ini_get('allow_url_fopen'),
'socket'=>function_exists('fsockopen')
);
];
if ($flags[$arg])
return $this->wrapper=$arg;
foreach ($flags as $key=>$val)
if ($val)
return $this->wrapper=$key;
user_error(E_Request,E_USER_ERROR);
user_error(self::E_Request,E_USER_ERROR);
}
/**
@@ -440,7 +461,7 @@ class Web extends Prefab {
**/
function subst(array &$old,$new) {
if (is_string($new))
$new=array($new);
$new=[$new];
foreach ($new as $hdr) {
$old=preg_grep('/'.preg_quote(strstr($hdr,':',TRUE),'/').':.+/',
$old,PREG_GREP_INVERT);
@@ -469,11 +490,11 @@ class Web extends Prefab {
elseif (!preg_match('/https?/',$parts['scheme']))
return FALSE;
if (!is_array($options))
$options=array();
$options=[];
if (empty($options['header']))
$options['header']=array();
$options['header']=[];
elseif (is_string($options['header']))
$options['header']=array($options['header']);
$options['header']=[$options['header']];
if (!$this->wrapper)
$this->engine();
if ($this->wrapper!='stream') {
@@ -487,13 +508,13 @@ class Web extends Prefab {
$this->subst($options['header'],'Host: '.$parts['host']);
}
$this->subst($options['header'],
array(
[
'Accept-Encoding: gzip,deflate',
'User-Agent: '.(isset($options['user_agent'])?
$options['user_agent']:
'Mozilla/5.0 (compatible; '.php_uname('s').')'),
'Connection: close'
)
]
);
if (isset($options['content']) && is_string($options['content'])) {
if ($options['method']=='POST' &&
@@ -508,13 +529,13 @@ class Web extends Prefab {
'Authorization: Basic '.
base64_encode($parts['user'].':'.$parts['pass'])
);
$options+=array(
$options+=[
'method'=>'GET',
'header'=>$options['header'],
'follow_location'=>TRUE,
'max_redirects'=>20,
'ignore_errors'=>FALSE
);
];
$eol="\r\n";
if ($fw->get('CACHE') &&
preg_match('/GET|HEAD/',$options['method'])) {
@@ -534,11 +555,14 @@ class Web extends Prefab {
$result=$cache->get($hash);
$result['cached']=TRUE;
}
elseif (preg_match('/Cache-Control: max-age=(.+?)'.
preg_quote($eol).'/',implode($eol,$result['headers']),$exp))
elseif (preg_match('/Cache-Control:(?:.*)max-age=(\d+)(?:,?.*'.
preg_quote($eol).')/',implode($eol,$result['headers']),$exp))
$cache->set($hash,$result,$exp[1]);
}
return $result;
$req=[$options['method'].' '.$url];
foreach ($options['header'] as $header)
array_push($req,$header);
return array_merge(['request'=>$req],$result);
}
/**
@@ -563,7 +587,9 @@ class Web extends Prefab {
$path=$fw->get('UI').';./';
foreach ($fw->split($path,FALSE) as $dir)
foreach ($files as $file)
if (is_file($save=$fw->fixslashes($dir.$file))) {
if (is_file($save=$fw->fixslashes($dir.$file)) &&
is_bool(strpos($save,'../')) &&
preg_match('/\.(css|js)$/i',$file)) {
if ($fw->get('CACHE') &&
($cached=$cache->exists(
$hash=$fw->hash($save).'.'.$ext[0],$data)) &&
@@ -637,7 +663,7 @@ class Web extends Prefab {
}
continue;
}
if (in_array($src[$ptr],array('\'','"'))) {
if (in_array($src[$ptr],['\'','"'])) {
$match=$src[$ptr];
$data.=$match;
$ptr++;
@@ -693,14 +719,14 @@ class Web extends Prefab {
NULL,LIBXML_NOBLANKS|LIBXML_NOERROR);
if (!is_object($xml))
return FALSE;
$out=array();
$out=[];
if (isset($xml->channel)) {
$out['source']=(string)$xml->channel->title;
$max=min($max,count($xml->channel->item));
for ($i=0;$i<$max;$i++) {
$item=$xml->channel->item[$i];
$list=array(''=>NULL)+$item->getnamespaces(TRUE);
$fields=array();
$list=[''=>NULL]+$item->getnamespaces(TRUE);
$fields=[];
foreach ($list as $ns=>$uri)
foreach ($item->children($uri) as $key=>$val)
$fields[$ns.($ns?':':'').$key]=(string)$val;
@@ -748,7 +774,7 @@ class Web extends Prefab {
function slug($text) {
return trim(strtolower(preg_replace('/([^\pL\pN])+/u','-',
trim(strtr(str_replace('\'','',$text),
array(
[
'Ǎ'=>'A','А'=>'A','Ā'=>'A','Ă'=>'A','Ą'=>'A','Å'=>'A',
'Ǻ'=>'A','Ä'=>'Ae','Á'=>'A','À'=>'A','Ã'=>'A','Â'=>'A',
'Æ'=>'AE','Ǽ'=>'AE','Б'=>'B','Ç'=>'C','Ć'=>'C','Ĉ'=>'C',
@@ -796,7 +822,7 @@ class Web extends Prefab {
'ǜ'=>'u','ǔ'=>'u','ǖ'=>'u','ũ'=>'u','ü'=>'ue','в'=>'v',
'ŵ'=>'w','ы'=>'y','ÿ'=>'y','ý'=>'y','ŷ'=>'y','ź'=>'z',
'ž'=>'z','з'=>'z','ż'=>'z','ж'=>'zh','ь'=>'','ъ'=>''
)+Base::instance()->get('DIACRITICS'))))),'-');
]+Base::instance()->get('DIACRITICS'))))),'-');
}
/**
@@ -850,8 +876,7 @@ if (!function_exists('gzdecode')) {
$fw=Base::instance();
if (!is_dir($tmp=$fw->get('TEMP')))
mkdir($tmp,Base::MODE,TRUE);
file_put_contents($file=$tmp.'/'.
$fw->hash($fw->get('ROOT').$fw->get('BASE')).'.'.
file_put_contents($file=$tmp.'/'.$fw->get('SEED').'.'.
$fw->hash(uniqid(NULL,TRUE)).'.gz',$str,LOCK_EX);
ob_start();
readgzfile($file);

View File

@@ -2,7 +2,7 @@
/*
Copyright (c) 2009-2015 F3::Factory/Bong Cosca, All rights reserved.
Copyright (c) 2009-2016 F3::Factory/Bong Cosca, All rights reserved.
This file is part of the Fat-Free Framework (http://fatfreeframework.com).
@@ -34,14 +34,14 @@ class Geo extends \Prefab {
$ref=new \DateTimeZone($zone);
$loc=$ref->getLocation();
$trn=$ref->getTransitions($now=time(),$now);
$out=array(
$out=[
'offset'=>$ref->
getOffset(new \DateTime('now',new \DateTimeZone('GMT')))/3600,
getOffset(new \DateTime('now',new \DateTimeZone('UTC')))/3600,
'country'=>$loc['country_code'],
'latitude'=>$loc['latitude'],
'longitude'=>$loc['longitude'],
'dst'=>$trn[0]['isdst']
);
];
unset($ref);
return $out;
}
@@ -72,7 +72,7 @@ class Geo extends \Prefab {
if (($req=$web->request('http://www.geoplugin.net/json.gp'.
($public?('?ip='.$ip):''))) &&
$data=json_decode($req['body'],TRUE)) {
$out=array();
$out=[];
foreach ($data as $key=>$val)
if (!strpos($key,'currency') && $key!=='geoplugin_status'
&& $key!=='geoplugin_region')
@@ -87,17 +87,17 @@ class Geo extends \Prefab {
* @return array|FALSE
* @param $latitude float
* @param $longitude float
* @param $key string
**/
function weather($latitude,$longitude) {
function weather($latitude,$longitude,$key) {
$fw=\Base::instance();
$web=\Web::instance();
$query=array(
$query=[
'lat'=>$latitude,
'lon'=>$longitude
);
$req=$web->request(
'http://api.openweathermap.org/data/2.5/weather?'.
http_build_query($query));
'lon'=>$longitude,
'APPID'=>$key,
'units'=>'metric'
];
return ($req=$web->request(
'http://api.openweathermap.org/data/2.5/weather?'.
http_build_query($query)))?

View File

@@ -2,7 +2,7 @@
/*
Copyright (c) 2009-2015 F3::Factory/Bong Cosca, All rights reserved.
Copyright (c) 2009-2016 F3::Factory/Bong Cosca, All rights reserved.
This file is part of the Fat-Free Framework (http://fatfreeframework.com).

134
app/lib/web/oauth2.php Normal file
View File

@@ -0,0 +1,134 @@
<?php
/*
Copyright (c) 2009-2016 F3::Factory/Bong Cosca, All rights reserved.
This file is part of the Fat-Free Framework (http://fatfreeframework.com).
This is free software: you can redistribute it and/or modify it under the
terms of the GNU General Public License as published by the Free Software
Foundation, either version 3 of the License, or later.
Fat-Free Framework is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received a copy of the GNU General Public License along
with Fat-Free Framework. If not, see <http://www.gnu.org/licenses/>.
*/
namespace Web;
//! Lightweight OAuth2 client
class OAuth2 extends \Magic {
protected
//! Scopes and claims
$args=[];
/**
* Return OAuth2 authentication URI
* @return string
* @param $endpoint string
**/
function uri($endpoint) {
return $endpoint.'?'.http_build_query($this->args);
}
/**
* Send request to API/token endpoint
* @return string|FALSE
* @param $uri string
* @param $method string
* @param $token array
**/
function request($uri,$method,$token=NULL) {
$web=\Web::instance();
$options=[
'method'=>$method,
'content'=>http_build_query($this->args),
'header'=>['Accept: application/json']
];
if ($token)
array_push($options['header'],'Authorization: Bearer '.$token);
elseif ($method=='POST')
array_push($options['header'],'Authorization: Basic '.
base64_encode(
$this->args['client_id'].':'.
$this->args['client_secret']
)
);
$response=$web->request($uri,$options);
return $response['body'] &&
preg_grep('/HTTP\/1\.\d 200/',$response['headers'])?
json_decode($response['body'],TRUE):
FALSE;
}
/**
* Parse JSON Web token
* @return array
* @param $token string
**/
function jwt($token) {
return json_decode(
base64_decode(
str_replace(
['-','_'],
['+','/'],
explode('.',$token)[1]
)
),
TRUE
);
}
/**
* Return TRUE if scope/claim exists
* @return bool
* @param $key string
**/
function exists($key) {
return isset($this->args[$key]);
}
/**
* Bind value to scope/claim
* @return string
* @param $key string
* @param $val string
**/
function set($key,$val) {
return $this->args[$key]=$val;
}
/**
* Return value of scope/claim
* @return mixed
* @param $key string
**/
function &get($key) {
if (isset($this->args[$key]))
$val=&$this->args[$key];
else
$val=NULL;
return $val;
}
/**
* Remove scope/claim
* @return NULL
* @param $key
**/
function clear($key=NULL) {
if ($key)
unset($this->args[$key]);
else
$this->args=[];
}
}

View File

@@ -2,7 +2,7 @@
/*
Copyright (c) 2009-2015 F3::Factory/Bong Cosca, All rights reserved.
Copyright (c) 2009-2016 F3::Factory/Bong Cosca, All rights reserved.
This file is part of the Fat-Free Framework (http://fatfreeframework.com).
@@ -29,7 +29,7 @@ class OpenID extends \Magic {
//! OpenID provider endpoint URL
$url,
//! HTTP request parameters
$args=array();
$args=[];
/**
* Determine OpenID provider
@@ -38,11 +38,11 @@ class OpenID extends \Magic {
**/
protected function discover($proxy) {
// Normalize
if (!preg_match('/https?:\/\//i',$this->args['identity']))
$this->args['identity']='http://'.$this->args['identity'];
$url=parse_url($this->args['identity']);
if (!preg_match('/https?:\/\//i',$this->args['endpoint']))
$this->args['endpoint']='http://'.$this->args['endpoint'];
$url=parse_url($this->args['endpoint']);
// Remove fragment; reconnect parts
$this->args['identity']=$url['scheme'].'://'.
$this->args['endpoint']=$url['scheme'].'://'.
(isset($url['user'])?
($url['user'].
(isset($url['pass'])?(':'.$url['pass']):'').'@'):'').
@@ -50,7 +50,7 @@ class OpenID extends \Magic {
(isset($url['query'])?('?'.$url['query']):'');
// HTML-based discovery of OpenID provider
$req=\Web::instance()->
request($this->args['identity'],array('proxy'=>$proxy));
request($this->args['endpoint'],['proxy'=>$proxy]);
if (!$req)
return FALSE;
$type=array_values(preg_grep('/Content-Type:/',$req['headers']));
@@ -63,8 +63,9 @@ class OpenID extends \Magic {
$svc=$xrds['XRD']['Service'];
if (isset($svc[0]))
$svc=$svc[0];
$svc_type=is_array($svc['Type'])?$svc['Type']:array($svc['Type']);
if (preg_grep('/http:\/\/specs\.openid\.net\/auth\/2.0\/'.
'(?:server|signon)/',$svc['Type'])) {
'(?:server|signon)/',$svc_type)) {
$this->args['provider']=$svc['URI'];
if (isset($svc['LocalID']))
$this->args['localidentity']=$svc['LocalID'];
@@ -89,7 +90,7 @@ class OpenID extends \Magic {
preg_match_all('/\b(rel|href)\h*=\h*'.
'(?:"(.+?)"|\'(.+?)\')/s',$parts[1],$attr,
PREG_SET_ORDER)) {
$node=array();
$node=[];
foreach ($attr as $kv)
$node[$kv[1]]=isset($kv[2])?$kv[2]:$kv[3];
if (isset($node['rel']) &&
@@ -140,7 +141,7 @@ class OpenID extends \Magic {
* @param $attr array
* @param $reqd string|array
**/
function auth($proxy=NULL,$attr=array(),array $reqd=NULL) {
function auth($proxy=NULL,$attr=[],array $reqd=NULL) {
$fw=\Base::instance();
$root=$fw->get('SCHEME').'://'.$fw->get('HOST');
if (empty($this->args['trust_root']))
@@ -157,7 +158,7 @@ class OpenID extends \Magic {
$this->args['ax.required']=is_string($reqd)?
$reqd:implode(',',$reqd);
}
$var=array();
$var=[];
foreach ($this->args as $key=>$val)
$var['openid.'.$key]=$val;
$fw->reroute($this->url.'?'.http_build_query($var));
@@ -179,16 +180,16 @@ class OpenID extends \Magic {
$this->args['mode']!='error' &&
$this->url=$this->discover($proxy)) {
$this->args['mode']='check_authentication';
$var=array();
$var=[];
foreach ($this->args as $key=>$val)
$var['openid.'.$key]=$val;
$req=\Web::instance()->request(
$this->url,
array(
[
'method'=>'POST',
'content'=>http_build_query($var),
'proxy'=>$proxy
)
]
);
return (bool)preg_match('/is_valid:true/i',$req['body']);
}
@@ -245,4 +246,3 @@ class OpenID extends \Magic {
}
}

View File

@@ -2,7 +2,7 @@
/*
Copyright (c) 2009-2015 F3::Factory/Bong Cosca, All rights reserved.
Copyright (c) 2009-2016 F3::Factory/Bong Cosca, All rights reserved.
This file is part of the Fat-Free Framework (http://fatfreeframework.com).
@@ -38,7 +38,7 @@ class Pingback extends \Prefab {
$web=\Web::instance();
$req=$web->request($url);
$found=FALSE;
if ($req && $req['body']) {
if ($req['body']) {
// Look for pingback header
foreach ($req['headers'] as $header)
if (preg_match('/^X-Pingback:\h*(.+)/',$header,$href)) {
@@ -71,7 +71,7 @@ class Pingback extends \Prefab {
$doc=new \DOMDocument('1.0',$fw->get('ENCODING'));
$doc->stricterrorchecking=FALSE;
$doc->recover=TRUE;
if ($req && @$doc->loadhtml($req['body'])) {
if (@$doc->loadhtml($req['body'])) {
// Parse anchor tags
$links=$doc->getelementsbytagname('a');
foreach ($links as $link) {
@@ -79,17 +79,17 @@ class Pingback extends \Prefab {
// Find pingback-enabled resources
if ($permalink && $found=$this->enabled($permalink)) {
$req=$web->request($found,
array(
[
'method'=>'POST',
'header'=>'Content-Type: application/xml',
'content'=>xmlrpc_encode_request(
'pingback.ping',
array($source,$permalink),
array('encoding'=>$fw->get('ENCODING'))
[$source,$permalink],
['encoding'=>$fw->get('ENCODING')]
)
)
]
);
if ($req && $req['body'])
if ($req['body'])
$this->log.=date('r').' '.
$permalink.' [permalink:'.$found.']'.PHP_EOL.
$req['body'].PHP_EOL;
@@ -118,7 +118,7 @@ class Pingback extends \Prefab {
$path=$fw->get('BASE');
$web=\Web::instance();
$args=xmlrpc_decode_request($fw->get('BODY'),$method,$charset);
$options=array('encoding'=>$charset);
$options=['encoding'=>$charset];
if ($method=='pingback.ping' && isset($args[0],$args[1])) {
list($source,$permalink)=$args;
$doc=new \DOMDocument('1.0',$fw->get('ENCODING'));
@@ -138,8 +138,7 @@ class Pingback extends \Prefab {
$links=$doc->getelementsbytagname('a');
foreach ($links as $link) {
if ($link->getattribute('href')==$permalink) {
call_user_func_array($func,
array($source,$req['body']));
call_user_func_array($func,[$source,$req['body']]);
// Success
die(xmlrpc_encode_request(NULL,$source,$options));
}

View File

@@ -7,7 +7,9 @@
*/
namespace Controller;
use Controller\Api as Api;
use lib\Config;
use lib\Socket;
use Model;
class AccessController extends Controller {
@@ -40,4 +42,32 @@ class AccessController extends Controller {
}
}
/**
* broadcast map data to clients
* -> send over TCP Socket
* @param Model\MapModel $map
* @return int (number of active connections for this map)
*/
protected function broadcastMapData(Model\MapModel $map){
$mapData = $this->getFormattedMapData($map);
return (int)(new Socket( Config::getSocketUri() ))->sendData('mapUpdate', $mapData);
}
/**
* get formatted Map Data
* @param Model\MapModel $map
* @return array
*/
protected function getFormattedMapData(Model\MapModel $map){
$mapData = $map->getData();
return [
'config' => $mapData->mapData,
'data' => [
'systems' => $mapData->systems,
'connections' => $mapData->connections,
]
];
}
}

View File

@@ -86,6 +86,9 @@ class Connection extends Controller\AccessController {
$connection->save();
$newConnectionData = $connection->getData();
// broadcast map changes
$this->broadcastMapData($connection->mapId);
}
}
}
@@ -100,18 +103,31 @@ class Connection extends Controller\AccessController {
* @throws \Exception
*/
public function delete(\Base $f3){
$connectionIds = $f3->get('POST.connectionIds');
$activeCharacter = $this->getCharacter();
$mapId = (int)$f3->get('POST.mapId');
$connectionIds = (array)$f3->get('POST.connectionIds');
/**
* @var Model\ConnectionModel $connection
*/
$connection = Model\BasicModel::getNew('ConnectionModel');
foreach($connectionIds as $connectionId){
$connection->getById($connectionId);
$connection->delete( $activeCharacter );
if($mapId){
$activeCharacter = $this->getCharacter();
/**
* @var Model\MapModel $map
*/
$map = Model\BasicModel::getNew('MapModel');
$map->getById($mapId);
if( $map->hasAccess($activeCharacter) ){
foreach($connectionIds as $connectionId){
if( $connection = $map->getConnectionById($connectionId) ){
$connection->delete( $activeCharacter );
$connection->reset();
}
}
// broadcast map changes
$this->broadcastMapData($map);
}
$connection->reset();
}
echo json_encode([]);

View File

@@ -9,6 +9,7 @@
namespace Controller\Api;
use Controller;
use lib\Config;
use lib\Socket;
use Model;
/**
@@ -19,6 +20,7 @@ use Model;
class Map extends Controller\AccessController {
// cache keys
const CACHE_KEY_INIT = 'CACHED_INIT';
const CACHE_KEY_MAP_DATA = 'CACHED.MAP_DATA.%s';
const CACHE_KEY_USER_DATA = 'CACHED.USER_DATA.%s_%s';
@@ -68,135 +70,131 @@ class Map extends Controller\AccessController {
* @param \Base $f3
*/
public function init(\Base $f3){
// expire time in seconds
$expireTimeHead = 60 * 60 * 12;
$expireTimeCache = 60 * 60;
$expireTimeSQL = 60 * 60 * 12;
$f3->expire($expireTimeHead);
if( !$f3->exists(self::CACHE_KEY_INIT, $return )){
$return = (object) [];
$return->error = [];
$return = (object) [];
$return->error = [];
// static program data ----------------------------------------------------------------------------------------
$return->timer = $f3->get('PATHFINDER.TIMER');
// static program data ----------------------------------------------------------------------------------------
$return->timer = $f3->get('PATHFINDER.TIMER');
// get all available map types --------------------------------------------------------------------------------
$mapType = Model\BasicModel::getNew('MapTypeModel');
$rows = $mapType->find('active = 1', null, $expireTimeSQL);
// get all available map types --------------------------------------------------------------------------------
$mapType = Model\BasicModel::getNew('MapTypeModel');
$rows = $mapType->find('active = 1', null, $expireTimeSQL);
// default map type config
$mapsDefaultConfig = Config::getMapsDefaultConfig();
$mapTypeData = [];
foreach((array)$rows as $rowData){
$data = [
'id' => $rowData->id,
'label' => $rowData->label,
'class' => $rowData->class,
'classTab' => $rowData->classTab
$mapTypeData = [];
foreach((array)$rows as $rowData){
$data = [
'id' => $rowData->id,
'label' => $rowData->label,
'class' => $rowData->class,
'classTab' => $rowData->classTab,
'defaultConfig' => $mapsDefaultConfig[$rowData->name]
];
$mapTypeData[$rowData->name] = $data;
}
$return->mapTypes = $mapTypeData;
// get all available map scopes -------------------------------------------------------------------------------
$mapScope = Model\BasicModel::getNew('MapScopeModel');
$rows = $mapScope->find('active = 1', null, $expireTimeSQL);
$mapScopeData = [];
foreach((array)$rows as $rowData){
$data = [
'id' => $rowData->id,
'label' => $rowData->label
];
$mapScopeData[$rowData->name] = $data;
}
$return->mapScopes = $mapScopeData;
// get all available system status ----------------------------------------------------------------------------
$systemStatus = Model\BasicModel::getNew('SystemStatusModel');
$rows = $systemStatus->find('active = 1', null, $expireTimeSQL);
$systemScopeData = [];
foreach((array)$rows as $rowData){
$data = [
'id' => $rowData->id,
'label' => $rowData->label,
'class' => $rowData->class
];
$systemScopeData[$rowData->name] = $data;
}
$return->systemStatus = $systemScopeData;
// get all available system types -----------------------------------------------------------------------------
$systemType = Model\BasicModel::getNew('SystemTypeModel');
$rows = $systemType->find('active = 1', null, $expireTimeSQL);
$systemTypeData = [];
foreach((array)$rows as $rowData){
$data = [
'id' => $rowData->id,
'name' => $rowData->name
];
$systemTypeData[$rowData->name] = $data;
}
$return->systemType = $systemTypeData;
// get available connection scopes ----------------------------------------------------------------------------
$connectionScope = Model\BasicModel::getNew('ConnectionScopeModel');
$rows = $connectionScope->find('active = 1', null, $expireTimeSQL);
$connectionScopeData = [];
foreach((array)$rows as $rowData){
$data = [
'id' => $rowData->id,
'label' => $rowData->label,
'connectorDefinition' => $rowData->connectorDefinition
];
$connectionScopeData[$rowData->name] = $data;
}
$return->connectionScopes = $connectionScopeData;
// get available character status -----------------------------------------------------------------------------
$characterStatus = Model\BasicModel::getNew('CharacterStatusModel');
$rows = $characterStatus->find('active = 1', null, $expireTimeSQL);
$characterStatusData = [];
foreach((array)$rows as $rowData){
$data = [
'id' => $rowData->id,
'name' => $rowData->name,
'class' => $rowData->class
];
$characterStatusData[$rowData->name] = $data;
}
$return->characterStatus = $characterStatusData;
// route search config ----------------------------------------------------------------------------------------
$return->routeSearch = [
'defaultCount' => $this->getF3()->get('PATHFINDER.ROUTE.SEARCH_DEFAULT_COUNT'),
'maxDefaultCount' => $this->getF3()->get('PATHFINDER.ROUTE.MAX_Default_COUNT'),
'limit' => $this->getF3()->get('PATHFINDER.ROUTE.LIMIT'),
];
$mapTypeData[$rowData->name] = $data;
}
$return->mapTypes = $mapTypeData;
// get all available map scopes -------------------------------------------------------------------------------
$mapScope = Model\BasicModel::getNew('MapScopeModel');
$rows = $mapScope->find('active = 1', null, $expireTimeSQL);
$mapScopeData = [];
foreach((array)$rows as $rowData){
$data = [
'id' => $rowData->id,
'label' => $rowData->label
// get program routes -----------------------------------------------------------------------------------------
$return->routes = [
'ssoLogin' => $this->getF3()->alias( 'sso', ['action' => 'requestAuthorization'] )
];
$mapScopeData[$rowData->name] = $data;
}
$return->mapScopes = $mapScopeData;
// get all available system status ----------------------------------------------------------------------------
$systemStatus = Model\BasicModel::getNew('SystemStatusModel');
$rows = $systemStatus->find('active = 1', null, $expireTimeSQL);
$systemScopeData = [];
foreach((array)$rows as $rowData){
$data = [
'id' => $rowData->id,
'label' => $rowData->label,
'class' => $rowData->class
// get notification status ------------------------------------------------------------------------------------
$return->notificationStatus = [
'rallySet' => (bool)Config::getNotificationMail('RALLY_SET')
];
$systemScopeData[$rowData->name] = $data;
$f3->set(self::CACHE_KEY_INIT, $return, $expireTimeCache );
}
$return->systemStatus = $systemScopeData;
// get all available system types -----------------------------------------------------------------------------
$systemType = Model\BasicModel::getNew('SystemTypeModel');
$rows = $systemType->find('active = 1', null, $expireTimeSQL);
$systemTypeData = [];
foreach((array)$rows as $rowData){
$data = [
'id' => $rowData->id,
'name' => $rowData->name
];
$systemTypeData[$rowData->name] = $data;
}
$return->systemType = $systemTypeData;
// Add data that should not be cached =========================================================================
// get available connection scopes ----------------------------------------------------------------------------
$connectionScope = Model\BasicModel::getNew('ConnectionScopeModel');
$rows = $connectionScope->find('active = 1', null, $expireTimeSQL);
$connectionScopeData = [];
foreach((array)$rows as $rowData){
$data = [
'id' => $rowData->id,
'label' => $rowData->label,
'connectorDefinition' => $rowData->connectorDefinition
];
$connectionScopeData[$rowData->name] = $data;
}
$return->connectionScopes = $connectionScopeData;
// get available character status -----------------------------------------------------------------------------
$characterStatus = Model\BasicModel::getNew('CharacterStatusModel');
$rows = $characterStatus->find('active = 1', null, $expireTimeSQL);
$characterStatusData = [];
foreach((array)$rows as $rowData){
$data = [
'id' => $rowData->id,
'name' => $rowData->name,
'class' => $rowData->class
];
$characterStatusData[$rowData->name] = $data;
}
$return->characterStatus = $characterStatusData;
// get max number of shared entities per map ------------------------------------------------------------------
$maxSharedCount = [
'character' => $f3->get('PATHFINDER.MAP.PRIVATE.MAX_SHARED'),
'corporation' => $f3->get('PATHFINDER.MAP.CORPORATION.MAX_SHARED'),
'alliance' => $f3->get('PATHFINDER.MAP.ALLIANCE.MAX_SHARED'),
];
$return->maxSharedCount = $maxSharedCount;
// get activity log options per map ---------------------------------------------------------------------------
$activityLogging = [
'character' => $f3->get('PATHFINDER.MAP.PRIVATE.ACTIVITY_LOGGING'),
'corporation' => $f3->get('PATHFINDER.MAP.CORPORATION.ACTIVITY_LOGGING'),
'alliance' => $f3->get('PATHFINDER.MAP.ALLIANCE.ACTIVITY_LOGGING'),
];
$return->activityLogging = $activityLogging;
// route search config ----------------------------------------------------------------------------------------
$return->routeSearch = [
'defaultCount' => $this->getF3()->get('PATHFINDER.ROUTE.SEARCH_DEFAULT_COUNT'),
'maxDefaultCount' => $this->getF3()->get('PATHFINDER.ROUTE.MAX_Default_COUNT'),
'limit' => $this->getF3()->get('PATHFINDER.ROUTE.LIMIT'),
];
// get program routes -----------------------------------------------------------------------------------------
$return->routes = [
'ssoLogin' => $this->getF3()->alias( 'sso', ['action' => 'requestAuthorization'] )
];
// get notification status ------------------------------------------------------------------------------------
$return->notificationStatus = [
'rallySet' => (bool)Config::getNotificationMail('RALLY_SET')
// program mode (e.g. "maintenance") --------------------------------------------------------------------------
$return->programMode = [
'maintenance' => $this->getF3()->get('PATHFINDER.LOGIN.MODE_MAINTENANCE')
];
// get SSO error messages that should be shown immediately ----------------------------------------------------
@@ -222,6 +220,7 @@ class Map extends Controller\AccessController {
$return = (object) [];
$return->error = [];
$return->warning = [];
if(
isset($importData['typeId']) &&
@@ -246,96 +245,116 @@ class Map extends Controller\AccessController {
$connection = Model\BasicModel::getNew('ConnectionModel');
$connection->setActivityLogging(false);
foreach($importData['mapData'] as $mapData){
if(
isset($mapData['config']) &&
isset($mapData['data'])
){
// to many systems for import
$mapTypeModel = Model\BasicModel::getNew('MapTypeModel');
$mapTypeModel->getById( (int)$importData['typeId'] );
if( !$mapTypeModel->dry() ){
$defaultConfig = Config::getMapsDefaultConfig($mapTypeModel->name);
foreach($importData['mapData'] as $mapData){
if(
isset($mapData['data']['systems']) &&
isset($mapData['data']['connections'])
isset($mapData['config']) &&
isset($mapData['data'])
){
if(isset($mapData['config']['id'])){
unset($mapData['config']['id']);
}
$map->setData($mapData['config']);
$map->typeId = (int)$importData['typeId'];
$map->save();
// new system IDs will be generated
// therefore we need to temp store a mapping between IDs
$tempSystemIdMapping = [];
foreach($mapData['data']['systems'] as $systemData){
if(isset($systemData['id'])){
$oldId = (int)$systemData['id'];
unset($systemData['id']);
$system->setData($systemData);
$system->mapId = $map;
$system->createdCharacterId = $activeCharacter;
$system->updatedCharacterId = $activeCharacter;
$system->save();
$tempSystemIdMapping[$oldId] = $system->id;
$system->reset();
if(
isset($mapData['data']['systems']) &&
isset($mapData['data']['connections'])
){
if(isset($mapData['config']['id'])){
unset($mapData['config']['id']);
}
}
foreach($mapData['data']['connections'] as $connectionData){
// check if source and target IDs match with new system ID
if(
isset( $tempSystemIdMapping[$connectionData['source']] ) &&
isset( $tempSystemIdMapping[$connectionData['target']] )
){
if(isset($connectionData['id'])){
unset($connectionData['id']);
$systemCount = count($mapData['data']['systems']);
if( $systemCount <= $defaultConfig['max_systems']){
$map->setData($mapData['config']);
$map->typeId = (int)$importData['typeId'];
$map->save();
// new system IDs will be generated
// therefore we need to temp store a mapping between IDs
$tempSystemIdMapping = [];
foreach($mapData['data']['systems'] as $systemData){
if(isset($systemData['id'])){
$oldId = (int)$systemData['id'];
unset($systemData['id']);
$system->setData($systemData);
$system->mapId = $map;
$system->createdCharacterId = $activeCharacter;
$system->updatedCharacterId = $activeCharacter;
$system->save();
$tempSystemIdMapping[$oldId] = $system->id;
$system->reset();
}
}
$connection->setData($connectionData);
$connection->mapId = $map;
$connection->source = $tempSystemIdMapping[$connectionData['source']];
$connection->target = $tempSystemIdMapping[$connectionData['target']];
$connection->save();
foreach($mapData['data']['connections'] as $connectionData){
// check if source and target IDs match with new system ID
if(
isset( $tempSystemIdMapping[$connectionData['source']] ) &&
isset( $tempSystemIdMapping[$connectionData['target']] )
){
if(isset($connectionData['id'])){
unset($connectionData['id']);
}
$connection->reset();
$connection->setData($connectionData);
$connection->mapId = $map;
$connection->source = $tempSystemIdMapping[$connectionData['source']];
$connection->target = $tempSystemIdMapping[$connectionData['target']];
$connection->save();
$connection->reset();
}
}
// map access info should not automatically imported
if($map->isPrivate()){
$map->setAccess($activeCharacter);
}elseif($map->isCorporation()){
if($corporation = $activeCharacter->getCorporation()){
$map->setAccess($corporation);
}
}elseif($map->isAlliance()){
if($alliance = $activeCharacter->getAlliance()){
$map->setAccess($alliance);
}
}
}else{
$maxSystemsError = (object) [];
$maxSystemsError->type = 'error';
$maxSystemsError->message = 'Map has to many systems (' . $systemCount . ').'
.' Max system count is ' . $defaultConfig['max_systems'] . ' for ' . $mapTypeModel->name . ' maps.';
$return->error[] = $maxSystemsError;
}
}else{
// systems || connections missing
$missingConfigError = (object) [];
$missingConfigError->type = 'error';
$missingConfigError->message = 'Map data not valid (systems || connections) missing';
$return->error[] = $missingConfigError;
}
// map access info should not automatically imported
if($map->isPrivate()){
$map->setAccess($activeCharacter);
}elseif($map->isCorporation()){
if($corporation = $activeCharacter->getCorporation()){
$map->setAccess($corporation);
}
}elseif($map->isAlliance()){
if($alliance = $activeCharacter->getAlliance()){
$map->setAccess($alliance);
}
}
}else{
// systems || connections missing
// map config || systems/connections missing
$missingConfigError = (object) [];
$missingConfigError->type = 'error';
$missingConfigError->message = 'Map data not valid (systems || connections) missing';
$missingConfigError->message = 'Map data not valid (config || data) missing';
$return->error[] = $missingConfigError;
}
}else{
// map config || systems/connections missing
$missingConfigError = (object) [];
$missingConfigError->type = 'error';
$missingConfigError->message = 'Map data not valid (config || data) missing';
$return->error[] = $missingConfigError;
$map->reset();
}
$map->reset();
}else{
$unknownMapType = (object) [];
$unknownMapType->type = 'error';
$unknownMapType->message = 'Map type unknown!';
$return->error[] = $unknownMapType;
}
}else{
// map data missing
@@ -345,7 +364,6 @@ class Map extends Controller\AccessController {
$return->error[] = $missingDataError;
}
echo json_encode($return);
}
@@ -388,10 +406,10 @@ class Map extends Controller\AccessController {
$maxShared = max($f3->get('PATHFINDER.MAP.PRIVATE.MAX_SHARED') - 1, 0);
$accessCharacters = array_slice($accessCharacters, 0, $maxShared);
if($accessCharacters){
// clear map access. In case something has removed from access list
$map->clearAccess();
// clear map access. In case something has removed from access list
$map->clearAccess();
if($accessCharacters){
/**
* @var $tempCharacter Model\CharacterModel
*/
@@ -431,10 +449,10 @@ class Map extends Controller\AccessController {
$maxShared = max($f3->get('PATHFINDER.MAP.CORPORATION.MAX_SHARED') - 1, 0);
$accessCorporations = array_slice($accessCorporations, 0, $maxShared);
if($accessCorporations){
// clear map access. In case something has removed from access list
$map->clearAccess();
// clear map access. In case something has removed from access list
$map->clearAccess();
if($accessCorporations){
/**
* @var $tempCorporation Model\CorporationModel
*/
@@ -474,10 +492,10 @@ class Map extends Controller\AccessController {
$maxShared = max($f3->get('PATHFINDER.MAP.ALLIANCE.MAX_SHARED') - 1, 0);
$accessAlliances = array_slice($accessAlliances, 0, $maxShared);
if($accessAlliances){
// clear map access. In case something has removed from access list
$map->clearAccess();
// clear map access. In case something has removed from access list
$map->clearAccess();
if($accessAlliances){
/**
* @var $tempAlliance Model\AllianceModel
*/
@@ -504,7 +522,16 @@ class Map extends Controller\AccessController {
}
// reload the same map model (refresh)
// this makes sure all data is up2date
$map->getById( $map->id, 0 );
$map->getById( $map->_id, 0 );
$charactersData = $map->getCharactersData();
$characterIds = array_map(function ($data){
return $data->id;
}, $charactersData);
// broadcast map Access -> and send map Data
$this->broadcastMapAccess($map, $characterIds);
$return->mapData = $map->getData();
}else{
@@ -538,11 +565,79 @@ class Map extends Controller\AccessController {
*/
$map = Model\BasicModel::getNew('MapModel');
$map->getById($mapData['id']);
$map->delete( $activeCharacter );
$map->delete( $activeCharacter, function($mapId){
$this->broadcastMapDeleted($mapId);
});
echo json_encode([]);
}
/**
* broadcast characters with map access rights to WebSocket server
* -> if characters with map access found -> broadcast mapData to them
* @param Model\MapModel $map
* @param array $characterIds
* @return int
*/
protected function broadcastMapAccess($map, $characterIds){
$connectionCount = 0;
$mapAccess = [
'id' => $map->_id,
'characterIds' => $characterIds
];
$charCount = (int)(new Socket( Config::getSocketUri() ))->sendData('mapAccess', $mapAccess);
if($charCount > 0){
// map has active connections that should receive map Data
$connectionCount = $this->broadcastMapData($map);
}
return $connectionCount;
}
/**
* broadcast map delete information to clients
* @param int $mapId
* @return bool|string
*/
protected function broadcastMapDeleted($mapId){
return (new Socket( Config::getSocketUri() ))->sendData('mapDeleted', $mapId);
}
/**
* get map access tokens for current character
* -> send access tokens via TCP Socket for WebSocket auth
* @param \Base $f3
*/
public function getAccessData(\Base $f3){
$return = (object) [];
$activeCharacter = $this->getCharacter();
$maps = $activeCharacter->getMaps();
$return->data = [
'id' => $activeCharacter->_id,
'token' => bin2hex(random_bytes(16)), // token for character access
'mapData' => []
];
if($maps){
foreach($maps as $map){
$return->data['mapData'][] = [
'id' => $map->_id,
'token' => bin2hex(random_bytes(16)) // token for map access
];
}
}
// send Access Data to WebSocket Server and get response (status)
// if 'OK' -> Socket exists
$return->status = (new Socket( Config::getSocketUri() ))->sendData('mapConnectionAccess', $return->data);
echo json_encode( $return );
}
/**
* update map data
* -> function is called continuously (trigger) by any active client
@@ -554,17 +649,16 @@ class Map extends Controller\AccessController {
$activeCharacter = $this->getCharacter();
$return = (object) [];
$return->error = [];
$cacheKey = $this->getMapDataCacheKey($activeCharacter);
// if there is any system/connection change data submitted -> save new data
if(
!empty($mapData) ||
!$f3->exists($cacheKey)
!$f3->exists($cacheKey, $return)
){
$return = (object) [];
$return->error = [];
// get current map data ===============================================================================
$maps = $activeCharacter->getMaps();
@@ -595,6 +689,7 @@ class Map extends Controller\AccessController {
// loop current user maps and check for changes
foreach($maps as $map){
$mapChanged = false;
// update system data ---------------------------------------------------------------------
foreach($systems as $i => $systemData){
@@ -623,6 +718,8 @@ class Map extends Controller\AccessController {
$system->updatedCharacterId = $activeCharacter;
$system->save();
$mapChanged = true;
// a system belongs to ONE map -> speed up for multiple maps
unset($systemData[$i]);
}
@@ -655,26 +752,29 @@ class Map extends Controller\AccessController {
$connection->setData($connectionData);
$connection->save();
$mapChanged = true;
// a connection belongs to ONE map -> speed up for multiple maps
unset($connectionData[$i]);
}
}
}
if($mapChanged){
$this->broadcastMapData($map);
}
}
}
}
// format map Data for return
$return->mapData = self::getFormattedMapData($maps);
$return->mapData = $this->getFormattedMapsData($maps);
// cache time(s) per user should be equal or less than this function is called
// prevent request flooding
$responseTTL = (int)$f3->get('PATHFINDER.TIMER.UPDATE_SERVER_MAP.DELAY') / 1000;
$f3->set($cacheKey, $return, $responseTTL);
}else{
// get from cache
$return = $f3->get($cacheKey);
}
// if userData is requested -> add it as well
@@ -688,23 +788,13 @@ class Map extends Controller\AccessController {
/**
* get formatted map data
* @param $mapModels
* @param Model\MapModel[] $mapModels
* @return array
*/
public static function getFormattedMapData($mapModels){
protected function getFormattedMapsData($mapModels){
$mapData = [];
foreach($mapModels as &$mapModel){
/**
* @var $mapModel Model\MapModel
*/
$allMapData = $mapModel->getData();
$mapData[] = [
'config' => $allMapData->mapData,
'data' => [
'systems' => $allMapData->systems,
'connections' => $allMapData->connections,
]
];
foreach($mapModels as $mapModel){
$mapData[] = $this->getFormattedMapData($mapModel);
}
return $mapData;
@@ -717,7 +807,6 @@ class Map extends Controller\AccessController {
*/
public function updateUserData(\Base $f3){
$return = (object) [];
$return->error = [];
$activeCharacter = $this->getCharacter(0);
@@ -747,8 +836,12 @@ class Map extends Controller\AccessController {
$map = $this->updateMapData($activeCharacter, $map);
}
// get from cache
// this should happen if a user has multiple program instances running
// with the same main char
$cacheKey = $this->getUserDataCacheKey($mapId, $requestSystemData->systemId);
if( !$f3->exists($cacheKey) ){
if( !$f3->exists($cacheKey, $return) ){
$return = (object) [];
$return->mapUserData[] = $map->getUserData();
// request signature data for a system if user has map access!
@@ -768,11 +861,6 @@ class Map extends Controller\AccessController {
// cache response
$f3->set($cacheKey, $return, $responseTTL);
}else{
// get from cache
// this should happen if a user has multiple program instances running
// with the same main char
$return = $f3->get($cacheKey);
}
}
}
@@ -781,6 +869,9 @@ class Map extends Controller\AccessController {
// even if they have multiple characters using the same map!
$return->userData = $activeCharacter->getUser()->getData();
// add error (if exists)
$return->error = [];
echo json_encode( $return );
}
@@ -793,8 +884,8 @@ class Map extends Controller\AccessController {
*/
protected function updateMapData(Model\CharacterModel $character, Model\MapModel $map){
// update "map data" cache in case of map (system/connection) changes
$clearMapDataCache = false;
// map changed. update cache (system/connection) changed
$mapDataChanged = false;
if(
( $mapScope = $map->getScope() ) &&
@@ -928,7 +1019,7 @@ class Map extends Controller\AccessController {
if($sourceSystem){
$map = $sourceSystem->mapId;
$sourceExists = true;
$clearMapDataCache = true;
$mapDataChanged = true;
// increase system position (prevent overlapping)
$systemPosX = $sourceSystem->posX + $systemOffsetX;
$systemPosY = $sourceSystem->posY + $systemOffsetY;
@@ -945,7 +1036,7 @@ class Map extends Controller\AccessController {
// get updated maps object
if($targetSystem){
$map = $targetSystem->mapId;
$clearMapDataCache = true;
$mapDataChanged = true;
$targetExists = true;
}
}
@@ -964,14 +1055,15 @@ class Map extends Controller\AccessController {
// get updated maps object
if($connection){
$map = $connection->mapId;
$clearMapDataCache = true;
$mapDataChanged = true;
}
}
}
}
if($clearMapDataCache){
if($mapDataChanged){
$this->clearMapDataCache($character);
$this->broadcastMapData($map);
}
return $map;

View File

@@ -53,13 +53,17 @@ class Route extends Controller\AccessController {
* -> this function is required for route search! (Don´t forget)
* @param array $mapIds
* @param array $filterData
* @param array $keepSystems
*/
public function initJumpData($mapIds = [], $filterData = []){
public function initJumpData($mapIds = [], $filterData = [], $keepSystems = []){
// add static data (e.g. K-Space stargates,..)
$this->setStaticJumpData();
// add map specific data
$this->setDynamicJumpData($mapIds, $filterData);
// filter jump data (e.g. remove some systems (0.0, LS)
$this->filterJumpData($filterData, $keepSystems);
}
/**
@@ -78,16 +82,10 @@ class Route extends Controller\AccessController {
$cacheKeyIdArray = $cacheKey . '.idArray';
if(
$f3->exists($cacheKeyNamedArray) &&
$f3->exists($cacheKeyJumpArray) &&
$f3->exists($cacheKeyIdArray)
!$f3->exists($cacheKeyNamedArray, $this->nameArray) ||
!$f3->exists($cacheKeyJumpArray, $this->jumpArray) ||
!$f3->exists($cacheKeyIdArray, $this->idArray)
){
// get cached values
$this->nameArray = $f3->get($cacheKeyNamedArray);
$this->jumpArray = $f3->get($cacheKeyJumpArray);
$this->idArray = $f3->get($cacheKeyIdArray);
}else{
// nothing cached
$query = "SELECT * FROM system_neighbour";
$rows = $this->getDB()->exec($query, null, $this->staticJumpDataCacheTime);
@@ -256,6 +254,35 @@ class Route extends Controller\AccessController {
}
}
/**
* filter systems (remove some systems) e.g. WH,LS,0.0 for "safer search"
* @param array $filterData
* @param array $keepSystems
*/
private function filterJumpData($filterData = [], $keepSystems = []){
if($filterData['safer']){
// remove all systems (TrueSec < 0.5) from search arrays
$this->jumpArray = array_filter($this->jumpArray, function($jumpData) use($keepSystems) {
// systemId is always last entry
$systemId = end($jumpData);
$systemNameData = $this->nameArray[$systemId];
$systemSec = $systemNameData[3];
if($systemSec < 0.45 && !in_array($systemId, $keepSystems)){
// remove system from nameArray as well
unset($this->nameArray[$systemId]);
// remove system from idArray as well
$systemName = $systemNameData[0];
unset($this->idArray[$systemName]);
return false;
}else{
return true;
}
});
}
}
/**
* get system data by systemId and dataName
* @param $systemId
@@ -504,7 +531,7 @@ class Route extends Controller\AccessController {
array_walk($mapData, function(&$item, &$key, $data){
if( isset($data[1][$key]) ){
// character has mas access -> do not check again
// character has map access -> do not check again
$item = $data[1][$key];
}else{
// check map access for current character
@@ -534,7 +561,8 @@ class Route extends Controller\AccessController {
'wormholes' => (bool) $routeData['wormholes'],
'wormholesReduced' => (bool) $routeData['wormholesReduced'],
'wormholesCritical' => (bool) $routeData['wormholesCritical'],
'wormholesEOL' => (bool) $routeData['wormholesEOL']
'wormholesEOL' => (bool) $routeData['wormholesEOL'],
'safer' => (bool) $routeData['safer']
];
$returnRoutData = [
@@ -553,8 +581,9 @@ class Route extends Controller\AccessController {
count($mapIds) > 0
){
$systemFrom = $routeData['systemFromData']['name'];
$systemFromId = (int)$routeData['systemFromData']['systemId'];
$systemTo = $routeData['systemToData']['name'];
$systemToId = (int)$routeData['systemToData']['systemId'];
$cacheKey = $this->getRouteCacheKey(
$mapIds,
@@ -563,15 +592,17 @@ class Route extends Controller\AccessController {
$filterData
);
if($f3->exists($cacheKey)){
if($f3->exists($cacheKey, $cachedData)){
// get data from cache
$returnRoutData = $f3->get($cacheKey);
$returnRoutData = $cachedData;
}else{
// max search depth for search
$searchDepth = $f3->get('PATHFINDER.ROUTE.SEARCH_DEPTH');
// set jump data for following route search
$this->initJumpData($mapIds, $filterData);
// --> don´t filter some systems (e.g. systemFrom, systemTo) even if they are are WH,LS,0.0
$keepSystems = [$systemFromId, $systemToId];
$this->initJumpData($mapIds, $filterData, $keepSystems);
// no cached route data found
$foundRoutData = $this->findRoute($systemFrom, $systemTo, $searchDepth);

View File

@@ -169,7 +169,7 @@ class Statistic extends Controller\AccessController {
];
// date offset condition ----------------------------------------------------------------------------------
$sqlDateOffset = " AND CONCAT(`log`.`year`, `log`.`week`) BETWEEN :yearWeekStart AND :yearWeekEnd ";
$sqlDateOffset = " AND CONCAT(`log`.`year`, LPAD(`log`.`week`, 2, 0) ) BETWEEN :yearWeekStart AND :yearWeekEnd ";
$queryData[':yearWeekStart'] = $this->concatYearWeek($yearStart, $weekStart);
$queryData[':yearWeekEnd'] = $this->concatYearWeek($yearEnd, $weekEnd);

View File

@@ -273,6 +273,9 @@ class System extends Controller\AccessController {
$newSystemModel->getById( $systemModel->id, 0);
$newSystemModel->clearCacheData();
$newSystemData = $newSystemModel->getData();
// broadcast map changes
$this->broadcastMapData($newSystemModel->mapId);
}
}
@@ -348,8 +351,8 @@ class System extends Controller\AccessController {
}
$cacheKey = 'CACHE_CONSTELLATION_SYSTEMS_' . self::formatHiveKey($constellationId);
if($f3->exists($cacheKey)){
$return->systemData = $f3->get($cacheKey);
if($f3->exists($cacheKey, $cachedData)){
$return->systemData = $cachedData;
}else{
if($constellationId > 0){
$systemModels = $this->getSystemModelByIds([$constellationId], 'constellationID');
@@ -408,29 +411,41 @@ class System extends Controller\AccessController {
* @param \Base $f3
*/
public function delete(\Base $f3){
$mapId = (int)$f3->get('POST.mapId');
$systemIds = (array)$f3->get('POST.systemIds');
$activeCharacter = $this->getCharacter();
/**
* @var Model\SystemModel $system
*/
$system = Model\BasicModel::getNew('SystemModel');
foreach($systemIds as $systemId){
$system->getById($systemId);
if( $system->hasAccess($activeCharacter) ){
// check whether system should be deleted OR set "inactive"
if(
empty($system->alias) &&
empty($system->description)
){
$system->erase();
}else{
// keep data -> set "inactive"
$system->setActive(false);
$system->save();
if($mapId){
$activeCharacter = $this->getCharacter();
/**
* @var Model\MapModel $map
*/
$map = Model\BasicModel::getNew('MapModel');
$map->getById($mapId);
if( $map->hasAccess($activeCharacter) ){
foreach($systemIds as $systemId){
if( $system = $map->getSystemById($systemId) ){
// check whether system should be deleted OR set "inactive"
if(
empty($system->alias) &&
empty($system->description)
){
$system->erase();
}else{
// keep data -> set "inactive"
$system->setActive(false);
$system->save();
}
$system->reset();
}
}
$system->reset();
// broadcast map changes
$this->broadcastMapData($map);
}
}
echo json_encode([]);

View File

@@ -130,7 +130,7 @@ class Sso extends Api\User{
// redirect to CCP SSO ----------------------------------------------------------------------
// used for "state" check between request and callback
$state = bin2hex(mcrypt_create_iv(12, MCRYPT_DEV_URANDOM));
$state = bin2hex( openssl_random_pseudo_bytes(12) );
$f3->set(self::SESSION_KEY_SSO_STATE, $state);
$urlParams = [
@@ -756,7 +756,7 @@ class Sso extends Api\User{
* @var Model\CorporationModel $corporationModel
*/
$corporationModel = Model\BasicModel::getNew('CorporationModel');
$corporationModel->getById($characterData->corporation['id'], 0);
$corporationModel->getById((int)$characterData->corporation['id'], 0);
$corporationModel->copyfrom($characterData->corporation);
$corporationModel->save();
}
@@ -766,7 +766,7 @@ class Sso extends Api\User{
* @var Model\AllianceModel $allianceModel
*/
$allianceModel = Model\BasicModel::getNew('AllianceModel');
$allianceModel->getById($characterData->alliance['id'], 0);
$allianceModel->getById((int)$characterData->alliance['id'], 0);
$allianceModel->copyfrom($characterData->alliance);
$allianceModel->save();
}
@@ -776,7 +776,7 @@ class Sso extends Api\User{
* @var Model\CharacterModel $characterModel
*/
$characterModel = Model\BasicModel::getNew('CharacterModel');
$characterModel->getById($characterData->character['id'], 0);
$characterModel->getById((int)$characterData->character['id'], 0);
$characterModel->copyfrom($characterData->character);
$characterModel->corporationId = $corporationModel;
$characterModel->allianceId = $allianceModel;

View File

@@ -10,6 +10,8 @@ namespace Controller;
use Controller\Api as Api;
use Controller\Ccp\Sso as Sso;
use lib\Config;
use lib\Socket;
use Lib\Util;
use Model;
use DB;
@@ -203,12 +205,12 @@ class Controller {
// unique "selector" -> to facilitate database look-ups (small size)
// -> This is preferable to simply using the database id field,
// which leaks the number of active users on the application
$selector = bin2hex(mcrypt_create_iv(12, MCRYPT_DEV_URANDOM));
$selector = bin2hex( openssl_random_pseudo_bytes(12) );
// generate unique "validator" (strong encryption)
// -> plaintext set to user (cookie), hashed version of this in DB
$size = mcrypt_get_iv_size(MCRYPT_CAST_256, MCRYPT_MODE_CFB);
$validator = bin2hex(mcrypt_create_iv($size, MCRYPT_DEV_URANDOM));
$size = openssl_cipher_iv_length('aes-256-cbc');
$validator = bin2hex(openssl_random_pseudo_bytes($size) );
// generate unique cookie token
$token = hash('sha256', $validator);
@@ -447,10 +449,6 @@ class Controller {
return $user;
}
public function getCharacterSessionData(){
}
/**
* log out current character
* @param \Base $f3
@@ -458,14 +456,16 @@ class Controller {
public function logout(\Base $f3){
$params = (array)$f3->get('POST');
// ----------------------------------------------------------
// delete server side cookie validation data
// for the active character
if(
$params['clearCookies'] === '1' &&
( $activeCharacter = $this->getCharacter())
){
$activeCharacter->logout();
if( $activeCharacter = $this->getCharacter() ){
if($params['clearCookies'] === '1'){
// delete server side cookie validation data
// for the active character
$activeCharacter->logout();
}
// broadcast logout information to webSocket server
(new Socket( Config::getSocketUri() ))->sendData('characterLogout', $activeCharacter->_id);
}
// destroy session login data -------------------------------
@@ -477,28 +477,23 @@ class Controller {
* @param \Base $f3
*/
public function getEveServerStatus(\Base $f3){
$return = (object) [];
$return->error = [];
// server status can be cached for some seconds
$cacheKey = 'eve_server_status';
if( !$f3->exists($cacheKey) ){
if( !$f3->exists($cacheKey, $return) ){
$return = (object) [];
$return->error = [];
$sso = new Sso();
$return->status = $sso->getCrestServerStatus();
if( !$return->status->crestOffline ){
$f3->set($cacheKey, $return, 60);
}
}else{
// get from cache
$return = $f3->get($cacheKey);
}
echo json_encode($return);
}
/**
* get error object is a user is not found/logged of
* @return \stdClass
@@ -808,6 +803,16 @@ class Controller {
return Config::getEnvironmentData($key);
}
/**
* health check for ICP socket -> ping request
* @param $ttl
* @param $load
*/
static function checkTcpSocket($ttl, $load){
(new Socket( Config::getSocketUri(), $ttl ))->sendData('healthCheck', $load);
}
/**
* get required MySQL variable value
* @param $key

View File

@@ -138,11 +138,10 @@ class LogController extends \Prefab {
public static function getLogger($type){
$f3 = \Base::instance();
$logFiles = $f3->get('PATHFINDER.LOGFILES');
$logger = null;
if( !empty($logFiles[$type]) ){
$logFile = $logFiles[$type] . '.log';
$logger = new \Log($logFile);
}
$logFileName = empty($logFiles[$type]) ? 'error' : $logFiles[$type];
$logFile = $logFileName . '.log';
$logger = new \Log($logFile);
return $logger;
}

View File

@@ -140,6 +140,15 @@ class Setup extends Controller {
// js view (file)
$f3->set('jsView', 'setup');
// set render functions (called within template)
$f3->set('cacheType', function(){
$cacheType = $this->getF3()->get('CACHE');
if( strpos($cacheType, 'redis') !== false ){
$cacheType = 'redis';
}
return $cacheType;
});
// render view
echo \Template::instance()->render( $f3->get('PATHFINDER.VIEW.INDEX') );
}
@@ -189,6 +198,9 @@ class Setup extends Controller {
// set database connection information
$f3->set('checkDatabase', $this->checkDatabase($f3, $fixColumns));
// set socket information
$f3->set('socketInformation', $this->getSocketInformation());
// set index information
$f3->set('indexInformation', $this->getIndexData());
@@ -421,46 +433,40 @@ class Setup extends Controller {
'version' => strstr(PCRE_VERSION, ' ', true),
'check' => version_compare( strstr(PCRE_VERSION, ' ', true), $f3->get('REQUIREMENTS.PHP.PCRE_VERSION'), '>=')
],
'pdo' => [
'ext_pdo' => [
'label' => 'PDO',
'required' => 'installed',
'version' => extension_loaded('pdo') ? 'installed' : 'not installed',
'version' => extension_loaded('pdo') ? 'installed' : 'missing',
'check' => extension_loaded('pdo')
],
'pdoMysql' => [
'ext_pdoMysql' => [
'label' => 'PDO_MYSQL',
'required' => 'installed',
'version' => extension_loaded('pdo_mysql') ? 'installed' : 'not installed',
'version' => extension_loaded('pdo_mysql') ? 'installed' : 'missing',
'check' => extension_loaded('pdo_mysql')
],
'openssl' => [
'ext_openssl' => [
'label' => 'OpenSSL',
'required' => 'installed',
'version' => extension_loaded('openssl') ? 'installed' : 'not installed',
'version' => extension_loaded('openssl') ? 'installed' : 'missing',
'check' => extension_loaded('openssl')
],
'mcrypt' => [
'label' => 'Mcrypt',
'required' => 'installed',
'version' => (extension_loaded('mcrypt')) ? 'installed' : 'not installed',
'check' => (extension_loaded('mcrypt'))
],
'xml' => [
'ext_xml' => [
'label' => 'XML',
'required' => 'installed',
'version' => (extension_loaded('xml')) ? 'installed' : 'not installed',
'check' => (extension_loaded('xml'))
'version' => extension_loaded('xml') ? 'installed' : 'missing',
'check' => extension_loaded('xml')
],
'gd' => [
'ext_gd' => [
'label' => 'GD Library (for Image plugin)',
'required' => 'installed',
'version' => (extension_loaded('gd') && function_exists('gd_info')) ? 'installed' : 'not installed',
'version' => (extension_loaded('gd') && function_exists('gd_info')) ? 'installed' : 'missing',
'check' => (extension_loaded('gd') && function_exists('gd_info'))
],
'curl' => [
'ext_curl' => [
'label' => 'cURL (for Web plugin)',
'required' => 'installed',
'version' => (extension_loaded('curl') && function_exists('curl_version')) ? 'installed' : 'not installed',
'version' => (extension_loaded('curl') && function_exists('curl_version')) ? 'installed' : 'missing',
'check' => (extension_loaded('curl') && function_exists('curl_version'))
],
'maxInputVars' => [
@@ -476,6 +482,33 @@ class Setup extends Controller {
'version' => ini_get('max_execution_time'),
'check' => ini_get('max_execution_time') >= $f3->get('REQUIREMENTS.PHP.MAX_EXECUTION_TIME'),
'tooltip' => 'PHP default = 30. Max execution time for PHP scripts.'
],
[
'label' => 'Redis Server [optional]'
],
'ext_redis' => [
'label' => 'Redis',
'required' => $f3->get('REQUIREMENTS.PHP.REDIS'),
'version' => extension_loaded('redis') ? phpversion('redis') : 'missing',
'check' => version_compare( phpversion('redis'), $f3->get('REQUIREMENTS.PHP.REDIS'), '>='),
'tooltip' => 'Redis can replace the default file-caching mechanic. It is much faster!'
],
[
'label' => 'ØMQ TCP sockets [optional]'
],
'ext_zmq' => [
'label' => 'ZeroMQ extension',
'required' => $f3->get('REQUIREMENTS.PHP.ZMQ'),
'version' => extension_loaded('zmq') ? phpversion('zmq') : 'missing',
'check' => version_compare( phpversion('zmq'), $f3->get('REQUIREMENTS.PHP.ZMQ'), '>='),
'tooltip' => 'ØMQ PHP extension. Required for WebSocket configuration.'
],
'lib_zmq' => [
'label' => 'ZeroMQ installation',
'required' => $f3->get('REQUIREMENTS.LIBS.ZMQ'),
'version' => (class_exists('ZMQ') && defined('ZMQ::LIBZMQ_VER')) ? \ZMQ::LIBZMQ_VER : 'unknown',
'check' => version_compare( (class_exists('ZMQ') && defined('ZMQ::LIBZMQ_VER')) ? \ZMQ::LIBZMQ_VER : 0, $f3->get('REQUIREMENTS.LIBS.ZMQ'), '>='),
'tooltip' => 'ØMQ version. Required for WebSocket configuration.'
]
];
@@ -905,6 +938,60 @@ class Setup extends Controller {
return $checkTables;
}
/**
* get Socket information (TCP (internal)), (WebSocket (clients))
* @return array
*/
protected function getSocketInformation(){
// $ttl for health check
$ttl = 600;
$heachCheckToken = microtime(true);
// ping TCP Socket with checkToken
self::checkTcpSocket($ttl, $heachCheckToken);
$socketInformation = [
'tcpSocket' => [
'label' => 'Socket (intern) [TCP]',
'online' => true,
'data' => [
[
'label' => 'HOST',
'value' => Config::getEnvironmentData('SOCKET_HOST'),
'check' => !empty( Config::getEnvironmentData('SOCKET_HOST') )
],[
'label' => 'PORT',
'value' => Config::getEnvironmentData('SOCKET_PORT'),
'check' => !empty( Config::getEnvironmentData('SOCKET_PORT') )
],[
'label' => 'URI',
'value' => Config::getSocketUri(),
'check' => !empty( Config::getSocketUri() )
],[
'label' => 'timeout (ms)',
'value' => $ttl,
'check' => !empty( $ttl )
]
],
'token' => $heachCheckToken
],
'webSocket' => [
'label' => 'WebSocket (clients) [HTTP]',
'online' => false,
'data' => [
[
'label' => 'URI',
'value' => '',
'check' => false
]
]
]
];
return $socketInformation;
}
/** get indexed (cache) data information
* @return array
*/

View File

@@ -24,14 +24,13 @@ class Database extends \Prefab {
* @return SQL
*/
public function setDB($database = 'PF'){
$f3 = \Base::instance();
// "Hive" Key for DB storage
$dbHiveKey = $this->getDbHiveKey($database);
// check if DB connection already exists
if( !$f3->exists( $dbHiveKey ) ){
if( !$f3->exists($dbHiveKey, $db) ){
if($database === 'CCP'){
// CCP DB
$dns = Controller\Controller::getEnvironmentData('DB_CCP_DNS');
@@ -59,11 +58,9 @@ class Database extends \Prefab {
// store DB object
$f3->set($dbHiveKey, $db);
}
return $db;
}else{
return $f3->get( $dbHiveKey );
}
return $db;
}
/**
@@ -72,15 +69,13 @@ class Database extends \Prefab {
* @return SQL
*/
public function getDB($database = 'PF'){
$f3 = \Base::instance();
$dbHiveKey = $this->getDbHiveKey($database);
if( $f3->exists( $dbHiveKey ) ){
return $f3->get( $dbHiveKey );
}else{
return $this->setDB($database);
if( !$f3->exists($dbHiveKey, $db) ){
$db = $this->setDB($database);
}
return $db;
}
/**

View File

@@ -51,16 +51,20 @@ class Config extends \Prefab {
}
/**
* set some global framework variables
* set/overwrite some global framework variables original set in config.ini
* -> can be overwritten in environments.ini OR ENV-Vars
* -> see: https://github.com/exodus4d/pathfinder/issues/175
* that depend on environment settings
*/
protected function setHiveVariables(){
$f3 = \Base::instance();
// hive keys that can be overwritten
$hiveKeys = ['BASE', 'URL', 'DEBUG', 'CACHE'];
// hive keys that should be overwritten by environment config
$hiveKeys = ['BASE', 'URL', 'DEBUG'];
foreach($hiveKeys as $key){
$f3->set($key, self::getEnvironmentData($key));
if( !is_null( $var = self::getEnvironmentData($key)) ){
$f3->set($key,$var);
}
}
}
@@ -138,12 +142,8 @@ class Config extends \Prefab {
* @return string|null
*/
static function getEnvironmentData($key){
$f3 = \Base::instance();
$hiveKey = self::HIVE_KEY_ENVIRONMENT . '.' . $key;
$data = null;
if( $f3->exists($hiveKey) ){
$data = $f3->get($hiveKey);
}
\Base::instance()->exists($hiveKey, $data);
return $data;
}
@@ -157,11 +157,44 @@ class Config extends \Prefab {
$f3 = \Base::instance();
$hiveKey = self::HIVE_KEY_PATHFINDER . '.NOTIFICATION.' . $key;
$mail = false;
if( $f3->exists($hiveKey) ){
$mail = $f3->get($hiveKey);
if( $f3->exists($hiveKey, $cachedMail) ){
$mail = $cachedMail;
}
return $mail;
}
/**
* get map default config values for map types (private/corp/ally)
* -> read from pathfinder.ini
* @param string $mapType
* @return array
*/
static function getMapsDefaultConfig($mapType = ''){
$f3 = \Base::instance();
$hiveKey = 'PATHFINDER.MAP';
if( !empty($mapType) ){
$hiveKey .= '.' . strtoupper($mapType);
}
return Util::arrayChangeKeyCaseRecursive( $f3->get($hiveKey) );
}
/**
* get URI for TCP socket
* @return bool|string
*/
static function getSocketUri(){
$uri = false;
if(
( $ip = self::getEnvironmentData('SOCKET_HOST') ) &&
( $port = self::getEnvironmentData('SOCKET_PORT') )
){
$uri = 'tcp://' . $ip . ':' . $port;
}
return $uri;
}
}

250
app/main/lib/socket.php Normal file
View File

@@ -0,0 +1,250 @@
<?php
/**
* Created by PhpStorm.
* User: Exodus
* Date: 09.12.2016
* Time: 16:21
*/
namespace lib;
use controller\LogController;
class Socket {
// max TTL time (ms)
const DEFAULT_TTL_MAX = 3000;
// max retry count
const DEFAULT_RETRY_MAX = 3;
// max processing time for remote WebServer to send response (ms)
const DEFAULT_RESPONSE_MAX = 1000;
const ERROR_OFFLINE = 'Server seems to be offline. uri: "%s" | retries: %s | timeout: %sms';
const ERROR_POLLING = 'Error polling object: %s';
const ERROR_POLLING_FAILED = 'Polling failed: %s';
const ERROR_RECV_FAILED = 'Receive failed: %s';
const ERROR_SEND_FAILED = 'No Response within: %sms. Socket took to long for processing request (or is offline)';
/**
* TCP Socket object
* @var \ZMQSocket
*/
protected $socket;
/**
* TCP URI for connections
* @var
*/
protected $socketUri;
/**
* Socket timeout (ms)
* -> The total timeout for a request is ($tll * $maxRetries)
* @var int
*/
protected $ttl = (self::DEFAULT_TTL_MAX / self::DEFAULT_RETRY_MAX);
/**
* max retry count for message send
* @var int
*/
protected $maxRetries = self::DEFAULT_RETRY_MAX;
public function __construct($uri, $ttl = self::DEFAULT_TTL_MAX, $maxRetries = self::DEFAULT_RETRY_MAX){
$this->setTtl($ttl, $maxRetries);
$this->setSocketUri($uri);
}
/**
* @param mixed $socketUri
*/
public function setSocketUri($socketUri){
$this->socketUri = $socketUri;
}
/**
* @param int $ttl
* @param int $maxRetries
*/
public function setTtl(int $ttl, int $maxRetries){
if(
$ttl > 0 &&
$maxRetries > 0
){
$this->maxRetries = $maxRetries;
$this->ttl = round($ttl / $maxRetries);
}
}
/**
* init new socket
*/
/*
public function initSocket(){
if(self::checkRequirements()){
$context = new \ZMQContext();
$this->socket = $context->getSocket(\ZMQ::SOCKET_REQ);
// The linger value of the socket. Specifies how long the socket blocks trying flush messages after it has been closed
$this->socket->setSockOpt(\ZMQ::SOCKOPT_LINGER, 0);
}
} */
/**
* init new socket
*/
public function initSocket(){
if(self::checkRequirements()){
$context = new \ZMQContext();
$this->socket = $context->getSocket(\ZMQ::SOCKET_PUSH);
}
}
public function sendData($task, $load = ''){
$response = false;
$this->initSocket();
if( !$this->socket ){
// Socket not active (e.g. URI missing)
return $response;
}
// add task, and wrap data
$send = [
'task' => $task,
'load' => $load
];
$this->socket->connect($this->socketUri);
$this->socket->send(json_encode($send));
$this->socket->disconnect($this->socketUri);
$response = 'OK';
return $response;
}
/**
* send data to socket and listen for response
* -> "Request" => "Response" setup
* @param $task
* @param $load
* @return bool|string
*/
/*
public function sendData($task, $load = ''){
$response = false;
$this->initSocket();
if( !$this->socket ){
// Socket not active (e.g. URI missing)
return $response;
}
// add task, and wrap data
$send = [
'task' => $task,
'load' => $load
];
$retriesLeft = $this->maxRetries;
// try sending data
while($retriesLeft){
// Get list of connected endpoints
$endpoints = $this->socket->getEndpoints();
if (in_array($this->socketUri, $endpoints['connect'])) {
// disconnect e.g. there was no proper response yet
$this->socket->disconnect($this->socketUri);
// try new socket connection
$this->initSocket();
}
$this->socket->connect($this->socketUri);
$this->socket->send(json_encode($send));
$readable = [];
$writable = [];
$poller = new \ZMQPoll();
$poller->add($this->socket, \ZMQ::POLL_IN);
$startTime = microtime(true);
// infinite loop until we get a proper answer
while(true){
// Amount of events retrieved
$events = 0;
try{
// Poll until there is something to do
$events = $poller->poll($readable, $writable, $this->ttl);
$errors = $poller->getLastErrors();
if(count($errors) > 0){
// log errors
foreach($errors as $error){
LogController::getLogger('SOCKET_ERROR')->write(sprintf(self::ERROR_POLLING, $error));
}
// break infinite loop
break;
}
}catch(\ZMQPollException $e){
LogController::getLogger('SOCKET_ERROR')->write(sprintf(self::ERROR_POLLING_FAILED, $e->getMessage() ));
}
if($events > 0){
try{
$response = $this->socket->recv();
// everything OK -> stop infinite loop AND retry loop!
break 2;
}catch(\ZMQException $e){
LogController::getLogger('SOCKET_ERROR')->write(sprintf(self::ERROR_RECV_FAILED, $e->getMessage() ));
}
}
if((microtime(true) - $startTime) > (self::DEFAULT_RESPONSE_MAX / 1000)){
// max time for response exceeded
LogController::getLogger('SOCKET_ERROR')->write(sprintf(self::ERROR_SEND_FAILED, self::DEFAULT_RESPONSE_MAX));
break;
}
// start inf loop again, no proper answer :(
}
if(--$retriesLeft <= 0){
// retry limit exceeded
LogController::getLogger('SOCKET_ERROR')->write(sprintf(self::ERROR_OFFLINE, $this->socketUri, $this->maxRetries, $this->ttl));
break;
}
}
$this->socket->disconnect($this->socketUri);
return $response;
}*/
/**
* check whether this installation fulfills all requirements
* -> check for ZMQ PHP extension and installed ZQM version
* -> this does NOT check versions! -> those can be verified on /setup page
* @return bool
*/
static function checkRequirements(){
$check = false;
if(
extension_loaded('zmq') &&
class_exists('ZMQ')
){
$check = true;
}
return $check;
}
}

27
app/main/lib/util.php Normal file
View File

@@ -0,0 +1,27 @@
<?php
/**
* Created by PhpStorm.
* User: Exodus
* Date: 26.11.2016
* Time: 17:32
*/
namespace Lib;
class Util {
/**
* convert array keys to upper/lowercase -> recursive
* @param $arr
* @param int $case
* @return array
*/
static function arrayChangeKeyCaseRecursive($arr, $case = CASE_LOWER){
return array_map( function($item){
if( is_array($item) )
$item = self::arrayChangeKeyCaseRecursive($item);
return $item;
},array_change_key_case($arr, $case));
}
}

View File

@@ -27,7 +27,7 @@ abstract class BasicModel extends \DB\Cortex {
* -> leave this at a higher value
* @var int
*/
//protected $ttl = 86400;
protected $ttl = 120;
/**
* caching for relational data
@@ -367,12 +367,8 @@ abstract class BasicModel extends \DB\Cortex {
// table cache exists
// -> check cache for this row data
$cacheKey = $this->getCacheKey($dataCacheKeyPrefix);
if( !is_null($cacheKey) ){
$f3 = self::getF3();
if( $f3->exists($cacheKey) ){
$cacheData = $f3->get( $cacheKey );
}
self::getF3()->exists($cacheKey, $cacheData);
}
return $cacheData;

View File

@@ -384,7 +384,7 @@ class CharacterModel extends BasicModel {
if(
!empty($whitelistCorporations) &&
$this->hasCorporation() &&
in_array($this->get('corporationId', true), $whitelistCorporations)
in_array((int)$this->get('corporationId', true), $whitelistCorporations)
){
$isAuthorized = true;
}
@@ -394,7 +394,7 @@ class CharacterModel extends BasicModel {
!$isAuthorized &&
!empty($whitelistAlliance) &&
$this->hasAlliance() &&
in_array($this->get('allianceId', true), $whitelistAlliance)
in_array((int)$this->get('allianceId', true), $whitelistAlliance)
){
$isAuthorized = true;
}

View File

@@ -15,6 +15,11 @@ class MapModel extends BasicModel {
protected $table = 'map';
/**
* cache key prefix for getCharactersData();
*/
const DATA_CACHE_KEY_CHARACTER = 'CHARACTERS';
protected $fieldConf = [
'active' => [
'type' => Schema::DT_BOOL,
@@ -157,7 +162,7 @@ class MapModel extends BasicModel {
// map access
$mapData->access = (object) [];
$mapData->access->user = [];
$mapData->access->character = [];
$mapData->access->corporation = [];
$mapData->access->alliance = [];
@@ -233,6 +238,16 @@ class MapModel extends BasicModel {
$self->clearCacheData();
}
/**
* see parent
*/
public function clearCacheData(){
parent::clearCacheData();
// clear character data with map access as well!
parent::clearCacheDataWithPrefix(self::DATA_CACHE_KEY_CHARACTER);
}
/**
* get blank system model pre-filled with default SDE data
* -> check for "inactive" systems on this map first!
@@ -356,6 +371,24 @@ class MapModel extends BasicModel {
return $systemData;
}
/**
* search for a connection by id
* @param int $id
* @return null|ConnectionModel
*/
public function getConnectionById($id){
/**
* @var $connection ConnectionModel
*/
$connection = $this->rel('connections');
$result = $connection->findone([
'active = 1 AND mapId = :mapId AND id = :id',
':mapId' => $this->id,
':id' => $id
]);
return is_object($result) ? $result : null;
}
/**
* get all connections in this map
* @return ConnectionModel[]
@@ -535,7 +568,7 @@ class MapModel extends BasicModel {
* get all character models that are currently online "viewing" this map
* @return CharacterModel[]
*/
private function getActiveCharacters(){
private function getAllCharacters(){
$characters = [];
if($this->isPrivate()){
@@ -563,17 +596,17 @@ class MapModel extends BasicModel {
}
/**
* get data for all characters that are currently online "viewing" this map
* get data for ALL characters with map access
* -> The result of this function is cached!
* @return \stdClass[]
*/
private function getCharactersData(){
public function getCharactersData(){
// check if there is cached data
$charactersData = $this->getCacheData('CHARACTERS');
$charactersData = $this->getCacheData(self::DATA_CACHE_KEY_CHARACTER);
if(is_null($charactersData)){
$charactersData = [];
$characters = $this->getActiveCharacters();
$characters = $this->getAllCharacters();
foreach($characters as $character){
$charactersData[] = $character->getData(true);
@@ -581,7 +614,7 @@ class MapModel extends BasicModel {
// cache active characters (if found)
if(!empty($charactersData)){
$this->updateCacheData($charactersData, 'CHARACTERS', 5);
$this->updateCacheData($charactersData, self::DATA_CACHE_KEY_CHARACTER, 5);
}
}
@@ -631,13 +664,20 @@ class MapModel extends BasicModel {
/**
* delete this map and all dependencies
* @param CharacterModel $characterModel
* @param null $callback
*/
public function delete(CharacterModel $characterModel){
public function delete(CharacterModel $characterModel, $callback = null){
if( !$this->dry() ){
// check if character has access
if($this->hasAccess($characterModel)){
// all map related tables will be deleted on cascade
$this->erase();
if(
$this->erase() &&
is_callable($callback)
){
$callback($this->_id);
}
}
}
}

View File

@@ -448,7 +448,7 @@ class SystemModel extends BasicModel {
* @return bool
*/
public function hasAccess(CharacterModel $characterModel){
return $this->mapId->hasAccess($characterModel);
return ($this->mapId) ? $this->mapId->hasAccess($characterModel) : false;
}
/**

View File

@@ -3,7 +3,7 @@
[PATHFINDER]
NAME = Pathfinder
; installed version (used for CSS/JS cache busting)
VERSION = v1.1.7
VERSION = v1.2.0
; contact information [optional]
CONTACT = https://github.com/exodus4d
; public contact email [optional]
@@ -26,6 +26,9 @@ MSG_DISABLED = User registration is currently not allowed
; expire time (in days) for login cookies
COOKIE_EXPIRE = 30
; shows "scheduled maintenance" warning to users (default: 0)
MODE_MAINTENANCE = 0
; restrict login to specific corporations/alliances by id (e.g. 1000166,1000080)
CORPORATION =
ALLIANCE =
@@ -51,6 +54,8 @@ LOGIN = templates/view/login.html
; - 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
; ACTIVITY_LOGGING (0: disable, 1: enable):
; - Whether user activity should be logged for a map type
; - E.g. create/update/delete of systems/connections/signatures
@@ -58,18 +63,21 @@ LOGIN = templates/view/login.html
LIFETIME = 14
MAX_COUNT = 3
MAX_SHARED = 10
MAX_SYSTEMS = 50
ACTIVITY_LOGGING = 1
[PATHFINDER.MAP.CORPORATION]
LIFETIME = 99999
MAX_COUNT = 3
MAX_SHARED = 3
MAX_SYSTEMS = 100
ACTIVITY_LOGGING = 1
[PATHFINDER.MAP.ALLIANCE]
LIFETIME = 99999
MAX_COUNT = 3
MAX_SHARED = 2
MAX_SYSTEMS = 100
ACTIVITY_LOGGING = 0
; Route search ====================================================================================
@@ -111,7 +119,7 @@ EXECUTION_LIMIT = 50
; map user update ping (ajax) (ms)
[PATHFINDER.TIMER.UPDATE_SERVER_USER_DATA]
DELAY = 5000
EXECUTION_LIMIT = 300
EXECUTION_LIMIT = 500
; update client user data (ms)
[PATHFINDER.TIMER.UPDATE_CLIENT_USER_DATA]
@@ -144,6 +152,8 @@ SESSION_SUSPECT = session_suspect
DELETE_ACCOUNT = account_delete
; unauthorized request (HTTP 401)
UNAUTHORIZED = unauthorized
; TCP socket errors
SOCKET_ERROR = socket_error
; debug log for development
DEBUG = debug

View File

@@ -17,6 +17,12 @@ VERSION = 5.6
; but needs to be additionally updated on CentOS or Red Hat systems
PCRE_VERSION = 8.02
; Redis extension (optional), required if you want to use Redis as caching Engine (recommended)
REDIS = 3.0.0
; ZeroMQ (ØMQ) extension (optional) required for WebSocket Server extension (recommended)
ZMQ = 1.1.3
; max execution time for requests
MAX_EXECUTION_TIME = 10
@@ -26,6 +32,10 @@ MAX_EXECUTION_TIME = 10
; PHP default = 1000
MAX_INPUT_VARS = 3000
[REQUIREMENTS.LIBS]
ZMQ = 4.1.3
[REQUIREMENTS.MYSQL]
; min MySQL Version
; newer "deviation" of MySQL like "MariaDB" > 10.1 are recommended
@@ -44,6 +54,5 @@ CHARACTER_SET_CONNECTION = utf8
COLLATION_DATABASE = utf8_general_ci
COLLATION_CONNECTION = utf8_general_ci
FOREIGN_KEY_CHECKS = ON
LOWER_CASE_TABLE_NAMES = 2

22
composer.json Normal file
View File

@@ -0,0 +1,22 @@
{
"name": "exodus4d/pathfinder",
"description": "Mapping tool for EVE ONLINE",
"minimum-stability": "stable",
"license": "MIT",
"authors": [
{
"name": "Mark Friedrich",
"email": "pathfinder@exodus4d.de"
}
],
"autoload": {
"psr-4": {
"Exodus4D\\Pathfinder\\": "app/main"
}
},
"require": {
"php-64bit": ">=7.0",
"ext-zmq": "1.1.*",
"react/zmq": "0.3.*"
}
}

View File

@@ -26,8 +26,8 @@ requirejs.config({
text: 'lib/requirejs/text', // v2.0.12 A RequireJS/AMD loader plugin for loading text resources.
mustache: 'lib/mustache.min', // v1.0.0 Javascript template engine - http://mustache.github.io
localForage: 'lib/localforage.min', // v1.4.2 localStorage library - https://mozilla.github.io/localForage
velocity: 'lib/velocity.min', // v1.2.2 animation engine - http://julian.com/research/velocity
velocityUI: 'lib/velocity.ui.min', // v5.0.4 plugin for velocity - http://julian.com/research/velocity/#uiPack
velocity: 'lib/velocity.min', // v1.4.1 animation engine - http://julian.com/research/velocity
velocityUI: 'lib/velocity.ui.min', // v5.2.0 plugin for velocity - http://julian.com/research/velocity/#uiPack
slidebars: 'lib/slidebars', // v0.10 Slidebars - side menu plugin http://plugins.adchsm.me/slidebars
jsPlumb: 'lib/dom.jsPlumb-1.7.6', // v1.7.6 jsPlumb (Vanilla)- main map draw plugin https://jsplumbtoolkit.com
farahey: 'lib/farahey-0.5', // v0.5 jsPlumb "magnetizing" extension - https://github.com/jsplumb/farahey

View File

@@ -5,7 +5,7 @@ define([
], function($, Init, Util) {
'use strict';
var config = {
let config = {
counterDigitSmallClass: 'pf-digit-counter-small',
counterDigitLargeClass: 'pf-digit-counter-large'
};
@@ -15,13 +15,13 @@ define([
* @param element
* @param tempDate
*/
var updateDateDiff = function(element, tempDate){
var diff = Util.getTimeDiffParts(tempDate, new Date());
var days = diff.days;
var hrs = diff.hours;
var min = diff.min;
var leftSec = diff.sec;
var value = [];
let updateDateDiff = function(element, tempDate){
let diff = Util.getTimeDiffParts(tempDate, new Date());
let days = diff.days;
let hrs = diff.hours;
let min = diff.min;
let leftSec = diff.sec;
let value = [];
if(
days > 0 ||
@@ -58,19 +58,22 @@ define([
*/
$.fn.initTimestampCounter = function(){
return this.each(function(){
var element = $(this);
var timestamp = parseInt( element.text() );
let element = $(this);
let timestamp = parseInt( element.text() );
// do not init twice
if(timestamp > 0){
// mark as init
element.attr('data-counter', 'init');
var date = new Date( timestamp * 1000);
let date = new Date( timestamp * 1000);
updateDateDiff(element, date);
var refreshIntervalId = window.setInterval(function(){
// show element (if invisible) after first update
element.css({'visibility': 'initial'});
let refreshIntervalId = window.setInterval(function(){
// update element with current time
if( !element.hasClass('stopCounter')){

View File

@@ -6,7 +6,7 @@ define(['jquery'], function($) {
'use strict';
var Config = {
let Config = {
path: {
img: 'public/img/', // path for images
// user API
@@ -22,6 +22,7 @@ define(['jquery'], function($) {
searchAccess: 'api/access/search', // ajax URL - search user/corporation/ally by name
// main config/map ping API
initMap: 'api/map/init', // ajax URL - get static data
getAccessData: 'api/map/getAccessData', // ajax URL - get map access tokens (WebSocket)
updateMapData: 'api/map/updateData', // ajax URL - main map update trigger
updateUserData: 'api/map/updateUserData', // ajax URL - main map user data trigger
// map API
@@ -63,11 +64,35 @@ define(['jquery'], function($) {
splashOverlay: 300, // "splash" loading overlay
headerLink: 100, // links in head bar
mapOverlay: 200, // show/hide duration for map overlays
mapMoveSystem: 300, // system position has changed animation
mapMoveSystem: 180, // system position has changed animation
mapDeleteSystem: 200, // remove system from map
mapModule: 200, // show/hide of an map module
dialogEvents: 180 // dialog events /slide/show/...
},
syncStatus: {
type: 'ajax',
webSocket: {
status: 'closed',
class: 'txt-color-danger',
timestamp: undefined
},
sharedWorker: {
status: 'offline', // SharedWorker status
class: 'txt-color-danger',
timestamp: undefined
},
ajax: {
status: 'enabled',
class: 'txt-color-success',
timestamp: undefined
}
},
performanceLogging: {
keyServerMapData: 'UPDATE_SERVER_MAP', // ajax request update map data
keyClientMapData: 'UPDATE_CLIENT_MAP', // update client map data
keyServerUserData: 'UPDATE_SERVER_USER_DATA', // ajax request update map user data
keyClientUserData: 'UPDATE_CLIENT_USER_DATA', // update client map user data
},
mapIcons: [ // map tab-icons
{
class: 'fa-desktop',

View File

@@ -11,288 +11,312 @@ define([
'use strict';
var logData = []; // cache object for all log entries
var logDataTable = null; // "Datatables" Object
let logData = []; // cache object for all log entries
let logDataTable = null; // "Datatables" Object
// Morris charts data
var maxGraphDataCount = 30; // max date entries for a graph
var chartData = {}; // chart Data object for all Morris Log graphs
let maxGraphDataCount = 30; // max date entries for a graph
let chartData = {}; // chart Data object for all Morris Log graphs
var config = {
dialogDynamicAreaClass: 'pf-dynamic-area', // class for dynamic areas
logGraphClass: 'pf-log-graph', // class for all log Morris graphs
tableToolsClass: 'pf-table-tools' // class for table tools
let config = {
taskDialogId: 'pf-task-dialog', // id for map "task manager" dialog
dialogDynamicAreaClass: 'pf-dynamic-area', // class for dynamic areas
timestampCounterClass: 'pf-timestamp-counter', // class for "timestamp" counter
taskDialogStatusAreaClass: 'pf-task-dialog-status', // class for "status" dynamic area
taskDialogLogTableAreaClass: 'pf-task-dialog-table', // class for "log table" dynamic area
logGraphClass: 'pf-log-graph', // class for all log Morris graphs
tableToolsClass: 'pf-table-tools' // class for table tools
};
/**
* get log time string
* @returns {string}
*/
var getLogTime = function(){
var serverTime = Util.getServerTime();
var logTime = serverTime.toLocaleTimeString('en-US', { hour12: false });
let getLogTime = function(){
let serverTime = Util.getServerTime();
let logTime = serverTime.toLocaleTimeString('en-US', { hour12: false });
return logTime;
};
/**
* updated "sync status" dynamic dialog area
*/
let updateSyncStatus = function(){
// check if task manager dialog is open
let logDialog = $('#' + config.taskDialogId);
if(logDialog.length){
// dialog is open
requirejs(['text!templates/modules/sync_status.html', 'mustache'], function(templateSyncStatus, Mustache) {
let data = {
timestampCounterClass: config.timestampCounterClass,
syncStatus: Init.syncStatus,
isWebSocket: () => {
return (Util.getSyncType() === 'webSocket');
},
isAjax: () => {
return (Util.getSyncType() === 'ajax');
}
};
let syncStatusElement = $( Mustache.render(templateSyncStatus, data ) );
logDialog.find('.' + config.taskDialogStatusAreaClass).html( syncStatusElement );
logDialog.find('.' + config.timestampCounterClass).initTimestampCounter();
syncStatusElement.initTooltips({
placement: 'right'
});
});
}
};
/**
* shows the logging dialog
*/
var showDialog = function(){
let showDialog = function(){
// dialog content
var content = $('<div>');
// content row for log graphs
var rowElementGraphs = $('<div>', {
class: 'row'
});
content.append(rowElementGraphs);
var tableHeadline = $('<h4>', {
text: ' Processes'
}).prepend( $('<i>', {
class: ['fa', 'fa-fw', 'fa-lg', 'fa-microchip'].join(' ')
}));
// add content Structure to dome before table initialization
content.append(tableHeadline);
// log table area --------------------------------------------------
var logTableArea = $('<div>', {
class: config.dialogDynamicAreaClass
});
var logTableActionBar = $('<div>', {
class: config.tableToolsClass
});
logTableArea.append(logTableActionBar);
var logTable = $('<table>', {
class: ['compact', 'stripe', 'order-column', 'row-border'].join(' ')
});
logTableArea.append(logTable);
content.append(logTableArea);
// init log table
logDataTable = logTable.DataTable({
paging: true,
ordering: true,
order: [ 1, 'desc' ],
autoWidth: false,
hover: false,
pageLength: 15,
lengthMenu: [[10, 15, 25, 50, 50], [10, 15, 25, 50, 50]],
data: logData, // load cached logs (if available)
language: {
emptyTable: 'No entries',
zeroRecords: 'No entries found',
lengthMenu: 'Show _MENU_ entries',
info: 'Showing _START_ to _END_ of _TOTAL_ entries'
},
columnDefs: [
{
targets: 0,
title: '<i class="fa fa-lg fa-tag"></i>',
width: '18px',
searchable: false,
class: ['text-center'].join(' '),
data: 'status'
},{
targets: 1,
title: '<i class="fa fa-lg fa-fw fa-clock-o"></i>&nbsp;&nbsp;',
width: '50px',
searchable: true,
class: 'text-right',
data: 'time'
},{
targets: 2,
title: '<i class="fa fa-lg fa-fw fa-history"></i>&nbsp;&nbsp;',
width: '35px',
searchable: false,
class: 'text-right',
sType: 'html',
data: 'duration'
},{
targets: 3,
title: 'description',
searchable: true,
data: 'description'
},{
targets: 4,
title: 'type',
width: '40px',
searchable: true,
class: ['text-center'].join(' '),
data: 'type'
},{
targets: 5,
title: 'Prozess-ID&nbsp;&nbsp;&nbsp;',
width: '80px',
searchable: false,
class: 'text-right',
data: 'key'
}
]
});
// open dialog
var logDialog = bootbox.dialog({
title: 'Task-Manager',
message: content,
size: 'large',
buttons: {
close: {
label: 'close',
className: 'btn-default'
}
}
});
// modal dialog is shown
logDialog.on('shown.bs.modal', function(e) {
// show Morris graphs ----------------------------------------------------------
// function for chart label formation
var labelYFormat = function(y){
return Math.round(y) + 'ms';
requirejs(['text!templates/dialog/task_manager.html', 'mustache'], function(templateTaskManagerDialog, Mustache) {
let data = {
id: config.taskDialogId,
dialogDynamicAreaClass: config.dialogDynamicAreaClass,
taskDialogStatusAreaClass: config.taskDialogStatusAreaClass,
taskDialogLogTableAreaClass: config.taskDialogLogTableAreaClass,
tableActionBarClass: config.tableToolsClass
};
for(var key in chartData) {
if(chartData.hasOwnProperty(key)) {
// create a chart for each key
let contentTaskManager = $( Mustache.render(templateTaskManagerDialog, data) );
var colElementGraph = $('<div>', {
class: ['col-md-6'].join(' ')
});
let rowElementGraphs = contentTaskManager.find('.row');
let taskDialogLogTableAreaElement = contentTaskManager.find('.' + config.taskDialogLogTableAreaClass);
let logTable = $('<table>', {
class: ['compact', 'stripe', 'order-column', 'row-border'].join(' ')
});
// graph element
var graphElement = $('<div>', {
class: config.logGraphClass
});
taskDialogLogTableAreaElement.append(logTable);
var graphArea = $('<div>', {
class: config.dialogDynamicAreaClass
}).append( graphElement );
// headline
var headline = $('<h4>', {
text: key
}).prepend(
$('<span>', {
class: ['txt-color', 'txt-color-grayLight'].join(' '),
text: 'Prozess-ID: '
})
);
// show update ping between function calls
var updateElement = $('<small>', {
class: ['txt-color', 'txt-color-blue', 'pull-right'].join(' ')
});
headline.append(updateElement).append('<br>');
// show average execution time
var averageElement = $('<small>', {
class: 'pull-right'
});
headline.append(averageElement);
colElementGraph.append( headline );
colElementGraph.append( graphArea );
graphArea.showLoadingAnimation();
rowElementGraphs.append( colElementGraph );
// cache DOM Elements that will be updated frequently
chartData[key].averageElement = averageElement;
chartData[key].updateElement = updateElement;
chartData[key].graph = Morris.Area({
element: graphElement,
data: [],
xkey: 'x',
ykeys: ['y'],
labels: [key],
units: 'ms',
parseTime: false,
ymin: 0,
yLabelFormat: labelYFormat,
padding: 10,
hideHover: true,
pointSize: 3,
lineColors: ['#375959'],
pointFillColors: ['#477372'],
pointStrokeColors: ['#313335'],
lineWidth: 2,
grid: false,
gridStrokeWidth: 0.3,
gridTextSize: 9,
gridTextFamily: 'Oxygen Bold',
gridTextColor: '#63676a',
behaveLikeLine: true,
goals: [],
goalLineColors: ['#66c84f'],
smooth: false,
fillOpacity: 0.3,
resize: true
});
graphArea.hideLoadingAnimation();
}
}
// ------------------------------------------------------------------------------
// add dataTable buttons (extension)
var buttons = new $.fn.dataTable.Buttons( logDataTable, {
buttons: [
// init log table
logDataTable = logTable.DataTable({
paging: true,
ordering: true,
order: [ 1, 'desc' ],
autoWidth: false,
hover: false,
pageLength: 10,
lengthMenu: [[5, 10, 25, 50, 100, -1], [5, 10, 25, 50, 100, 'All']],
data: logData, // load cached logs (if available)
language: {
emptyTable: 'No entries',
zeroRecords: 'No entries found',
lengthMenu: 'Show _MENU_ entries',
info: 'Showing _START_ to _END_ of _TOTAL_ entries'
},
columnDefs: [
{
extend: 'copy',
className: 'btn btn-sm btn-default',
text: '<i class="fa fa-fw fa-clipboard"></i> copy'
targets: 0,
title: '<i class="fa fa-lg fa-tag"></i>',
width: '18px',
searchable: false,
class: ['text-center'].join(' '),
data: 'status'
},{
extend: 'csv',
className: 'btn btn-sm btn-default',
text: '<i class="fa fa-fw fa-download"></i> csv'
targets: 1,
title: '<i class="fa fa-lg fa-fw fa-clock-o"></i>&nbsp;&nbsp;',
width: '50px',
searchable: true,
class: 'text-right',
data: 'time'
},{
targets: 2,
title: '<i class="fa fa-lg fa-fw fa-history"></i>&nbsp;&nbsp;',
width: '35px',
searchable: false,
class: 'text-right',
sType: 'html',
data: 'duration'
},{
targets: 3,
title: 'description',
searchable: true,
data: 'description'
},{
targets: 4,
title: 'type',
width: '40px',
searchable: true,
class: ['text-center'].join(' '),
data: 'type'
},{
targets: 5,
title: 'Prozess-ID&nbsp;&nbsp;&nbsp;',
width: '80px',
searchable: false,
class: 'text-right',
data: 'key'
}
]
} );
logDataTable.buttons().container().appendTo( $(this).find('.' + config.tableToolsClass));
});
});
// modal dialog is closed
logDialog.on('hidden.bs.modal', function(e) {
// clear memory -> destroy all charts
for (var key in chartData) {
if (chartData.hasOwnProperty(key)) {
chartData[key].graph = null;
// open dialog
let logDialog = bootbox.dialog({
title: 'Task-Manager',
message: contentTaskManager,
size: 'large',
buttons: {
close: {
label: 'close',
className: 'btn-default'
}
}
}
});
});
// modal dialog before hide
logDialog.on('hide.bs.modal', function(e) {
// modal dialog is shown
logDialog.on('shown.bs.modal', function(e) {
updateSyncStatus();
// destroy logTable
logDataTable.destroy(true);
logDataTable= null;
// show Morris graphs ----------------------------------------------------------
// function for chart label formation
let labelYFormat = function(y){
return Math.round(y) + 'ms';
};
for(let key in chartData) {
if(chartData.hasOwnProperty(key)) {
// create a chart for each key
let colElementGraph = $('<div>', {
class: ['col-md-6'].join(' ')
});
// graph element
let graphElement = $('<div>', {
class: config.logGraphClass
});
let graphArea = $('<div>', {
class: config.dialogDynamicAreaClass
}).append( graphElement );
// headline
let headline = $('<h4>', {
text: key
}).prepend(
$('<span>', {
class: ['txt-color', 'txt-color-grayLight'].join(' '),
text: 'Prozess-ID: '
})
);
// show update ping between function calls
let updateElement = $('<small>', {
class: ['txt-color', 'txt-color-blue', 'pull-right'].join(' ')
});
headline.append(updateElement).append('<br>');
// show average execution time
let averageElement = $('<small>', {
class: 'pull-right'
});
headline.append(averageElement);
colElementGraph.append( headline );
colElementGraph.append( graphArea );
graphArea.showLoadingAnimation();
rowElementGraphs.append( colElementGraph );
// cache DOM Elements that will be updated frequently
chartData[key].averageElement = averageElement;
chartData[key].updateElement = updateElement;
chartData[key].graph = Morris.Area({
element: graphElement,
data: [],
xkey: 'x',
ykeys: ['y'],
labels: [key],
units: 'ms',
parseTime: false,
ymin: 0,
yLabelFormat: labelYFormat,
padding: 10,
hideHover: true,
pointSize: 3,
lineColors: ['#375959'],
pointFillColors: ['#477372'],
pointStrokeColors: ['#313335'],
lineWidth: 2,
grid: false,
gridStrokeWidth: 0.3,
gridTextSize: 9,
gridTextFamily: 'Oxygen Bold',
gridTextColor: '#63676a',
behaveLikeLine: true,
goals: [],
goalLineColors: ['#66c84f'],
smooth: false,
fillOpacity: 0.3,
resize: true
});
updateLogGraph(key);
graphArea.hideLoadingAnimation();
}
}
// ------------------------------------------------------------------------------
// add dataTable buttons (extension)
let buttons = new $.fn.dataTable.Buttons( logDataTable, {
buttons: [
{
extend: 'copy',
className: 'btn btn-sm btn-default',
text: '<i class="fa fa-fw fa-clipboard"></i> copy'
},{
extend: 'csv',
className: 'btn btn-sm btn-default',
text: '<i class="fa fa-fw fa-download"></i> csv'
}
]
} );
logDataTable.buttons().container().appendTo( $(this).find('.' + config.tableToolsClass));
});
// modal dialog is closed
logDialog.on('hidden.bs.modal', function(e) {
// clear memory -> destroy all charts
for (let key in chartData) {
if (chartData.hasOwnProperty(key)) {
chartData[key].graph = null;
}
}
});
// modal dialog before hide
logDialog.on('hide.bs.modal', function(e) {
// destroy logTable
logDataTable.destroy(true);
logDataTable= null;
// remove event -> prevent calling this multiple times
$(this).off('hide.bs.modal');
});
// remove event -> prevent calling this multiple times
$(this).off('hide.bs.modal');
});
};
@@ -300,9 +324,9 @@ define([
/**
* updates the log graph for a log key
* @param key
* @param duration
* @param duration (if undefined -> just update graph with current data)
*/
var updateLogGraph = function(key, duration){
let updateLogGraph = function(key, duration){
// check if graph data already exist
if( !(chartData.hasOwnProperty(key))){
@@ -314,21 +338,23 @@ define([
}
// add new value
chartData[key].data.unshift(duration);
if(duration !== undefined){
chartData[key].data.unshift(duration);
}
if(chartData[key].data.length > maxGraphDataCount){
chartData[key].data = chartData[key].data.slice(0, maxGraphDataCount);
}
function getGraphData(data) {
var tempChartData = {
let tempChartData = {
data: [],
dataSum: 0,
average: 0
};
for(var x = 0; x < maxGraphDataCount; x++){
var value = 0;
for(let x = 0; x < maxGraphDataCount; x++){
let value = 0;
if(data[x]){
value = data[x];
tempChartData.dataSum = Number( (tempChartData.dataSum + value).toFixed(2) );
@@ -346,14 +372,14 @@ define([
return tempChartData;
}
var tempChartData = getGraphData(chartData[key].data);
let tempChartData = getGraphData(chartData[key].data);
// add new data to graph (Morris chart) - if is already initialized
if(chartData[key].graph !== null){
var avgElement = chartData[key].averageElement;
var updateElement = chartData[key].updateElement;
let avgElement = chartData[key].averageElement;
let updateElement = chartData[key].updateElement;
var delay = Util.getCurrentTriggerDelay( key, 0 );
let delay = Util.getCurrentTriggerDelay( key, 0 );
if(delay){
updateElement[0].textContent = ' delay: ' + delay + 'ms ';
@@ -365,8 +391,8 @@ define([
// change avg. display
avgElement[0].textContent = 'Avg. ' + tempChartData.average + 'ms';
var avgStatus = getLogStatusByDuration(key, tempChartData.average);
var avgStatusClass = Util.getLogInfo( avgStatus, 'class' );
let avgStatus = getLogStatusByDuration(key, tempChartData.average);
let avgStatusClass = Util.getLogInfo( avgStatus, 'class' );
//change avg. display class
if( !avgElement.hasClass(avgStatusClass) ){
@@ -376,7 +402,7 @@ define([
// change goals line color
if(avgStatus === 'warning'){
chartData[key].graph.options.goalLineColors = ['#e28a0d'];
$(document).setProgramStatus('problem');
$(document).setProgramStatus('slow connection');
}else{
chartData[key].graph.options.goalLineColors = ['#5cb85c'];
}
@@ -396,9 +422,8 @@ define([
* @param logDuration
* @returns {string}
*/
var getLogStatusByDuration = function(logKey, logDuration){
var logStatus = 'info';
let getLogStatusByDuration = function(logKey, logDuration){
let logStatus = 'info';
if( logDuration > Init.timer[logKey].EXECUTION_LIMIT ){
logStatus = 'warning';
}
@@ -410,9 +435,9 @@ define([
* @param logType
* @returns {string}
*/
var getLogTypeIconClass = function(logType){
let getLogTypeIconClass = function(logType){
var logIconClass = '';
let logIconClass = '';
switch(logType){
case 'client':
@@ -427,11 +452,15 @@ define([
};
/**
* init logging -> set global log event
* init logging -> set global log events
*/
var init = function(){
let init = function(){
var maxEntries = 150;
let maxEntries = 150;
$(window).on('pf:syncStatus', function(){
updateSyncStatus();
});
// set global logging listener
$(window).on('pf:log', function(e, logKey, options){
@@ -442,21 +471,19 @@ define([
options.duration &&
options.description
){
var logDescription = options.description;
var logDuration = options.duration;
var logType = options.type;
let logDescription = options.description;
let logDuration = options.duration;
let logType = options.type;
// check log status by duration
var logStatus = getLogStatusByDuration(logKey, logDuration);
var statusClass = Util.getLogInfo( logStatus, 'class' );
var typeIconClass = getLogTypeIconClass(logType);
let logStatus = getLogStatusByDuration(logKey, logDuration);
let statusClass = Util.getLogInfo( logStatus, 'class' );
let typeIconClass = getLogTypeIconClass(logType);
// update graph data
updateLogGraph(logKey, logDuration);
var logRowData = {
let logRowData = {
status: '<i class="fa fa-fw fa-circle txt-color ' + statusClass + '"></i>',
time: getLogTime(),
duration: '<span class="txt-color ' + statusClass + '">' + logDuration + '<small>ms</small></span>',
@@ -476,7 +503,7 @@ define([
}
// delete old log entries from table ---------------------------------
var rowCount = logData.length;
let rowCount = logData.length;
if( rowCount >= maxEntries ){

View File

@@ -22,7 +22,7 @@ define([
'use strict';
var config = {
let config = {
splashOverlayClass: 'pf-splash', // class for "splash" overlay
// header
@@ -75,11 +75,11 @@ define([
* @param cvalue
* @param exdays
*/
var setCookie = function(cname, cvalue, exdays) {
var d = new Date();
let setCookie = function(cname, cvalue, exdays) {
let d = new Date();
d.setTime(d.getTime() + (exdays*24*60*60*1000));
var expires = 'expires=' + d.toUTCString();
var path = 'path=' + Util.getDocumentPath();
let expires = 'expires=' + d.toUTCString();
let path = 'path=' + Util.getDocumentPath();
document.cookie = cname + '=' + cvalue + '; ' + expires + '; ' + path;
};
@@ -88,12 +88,12 @@ define([
* @param cname
* @returns {*}
*/
var getCookie = function(cname) {
var name = cname + '=';
var ca = document.cookie.split(';');
let getCookie = function(cname) {
let name = cname + '=';
let ca = document.cookie.split(';');
for(var i = 0; i <ca.length; i++) {
var c = ca[i];
for(let i = 0; i <ca.length; i++) {
let c = ca[i];
while (c.charAt(0) === ' ') {
c = c.substring(1);
}
@@ -108,7 +108,7 @@ define([
/**
* set link observer for "version info" dialog
*/
var setVersionLinkObserver = function(){
let setVersionLinkObserver = function(){
$('.' + config.navigationVersionLinkClass).off('click').on('click', function(e){
$.fn.releasesDialog();
});
@@ -117,7 +117,7 @@ define([
/**
* set page observer
*/
var setPageObserver = function(){
let setPageObserver = function(){
// cookie hint --------------------------------------------------------
if(getCookie('cookie') !== '1'){
@@ -145,7 +145,7 @@ define([
setVersionLinkObserver();
// tooltips -----------------------------------------------------------
var mapTooltipOptions = {
let mapTooltipOptions = {
toggle: 'tooltip',
container: 'body',
delay: 150
@@ -157,7 +157,7 @@ define([
/**
* init image carousel
*/
var initCarousel = function(){
let initCarousel = function(){
// check if carousel exists
if($('#' + config.galleryCarouselId).length === 0){
@@ -166,11 +166,11 @@ define([
// extent "blueimp" gallery for a textFactory method to show HTML templates
Gallery.prototype.textFactory = function (obj, callback) {
var newSlideContent = $('<div>')
let newSlideContent = $('<div>')
.addClass('text-content')
.attr('imgTitle', obj.title);
var moduleConfig = {
let moduleConfig = {
name: obj.href, // template name
position: newSlideContent,
functions: {
@@ -185,7 +185,7 @@ define([
};
// render HTML file (template)
var moduleData = {
let moduleData = {
id: config.headHeaderMapId,
bgId: config.headMapBgId,
neocomId: config.mapNeocomId,
@@ -199,7 +199,7 @@ define([
};
// initialize carousel ------------------------------------------------
var carousel = new Gallery([
let carousel = new Gallery([
{
imgTitle: 'Browser',
href: 'ui/map',
@@ -291,7 +291,7 @@ define([
* get all thumbnail elements
* @returns {*|jQuery|HTMLElement}
*/
var getThumbnailElements = function(){
let getThumbnailElements = function(){
return $('a[data-gallery="#' + config.galleryId + '"]');
};
@@ -299,18 +299,18 @@ define([
* init gallery for thumbnail elements
* @param newElements
*/
var initGallery = function(newElements){
let initGallery = function(newElements){
if( newElements.length > 0){
// We have to add ALL thumbnail elements to the gallery!
// -> even those wthat are invisible (not lazyLoaded) now!
// -> This is required for "swipe" through all images
var allThumbLinks = getThumbnailElements();
let allThumbLinks = getThumbnailElements();
requirejs(['blueImpGalleryBootstrap'], function() {
$(newElements).each(function() {
var borderless = false;
let borderless = false;
var galleryElement = $('#' + config.galleryId);
let galleryElement = $('#' + config.galleryId);
galleryElement.data('useBootstrapModal', !borderless);
galleryElement.toggleClass('blueimp-gallery-controls', borderless);
@@ -318,10 +318,10 @@ define([
e.preventDefault();
e = e || window.event;
var target = e.target || e.srcElement;
var link = target.src ? target.parentNode : target;
let target = e.target || e.srcElement;
let link = target.src ? target.parentNode : target;
var options = {
let options = {
index: link,
event: e,
container: '#' + config.galleryId,
@@ -338,7 +338,7 @@ define([
/**
* init "YouTube" video preview
*/
var initYoutube = function(){
let initYoutube = function(){
$('.youtube').each(function() {
// Based on the YouTube ID, we can easily find the thumbnail image
@@ -349,13 +349,13 @@ define([
$(document).delegate('#' + this.id, 'click', function() {
// Create an iFrame with autoplay set to true
var iFrameUrl = 'https://www.youtube.com/embed/' + this.id + '?autoplay=1&autohide=1';
let iFrameUrl = 'https://www.youtube.com/embed/' + this.id + '?autoplay=1&autohide=1';
if ( $(this).data('params') ){
iFrameUrl += '&'+$(this).data('params');
}
// The height and width of the iFrame should be the same as parent
var iFrame = $('<iframe/>', {
let iFrame = $('<iframe/>', {
frameborder: '0',
src: iFrameUrl,
width: $(this).width(),
@@ -373,13 +373,13 @@ define([
/**
* init scrollspy for navigation bar
*/
var initScrollspy = function(){
let initScrollspy = function(){
// init scrollspy
// show elements that are currently in the viewport
var showVisibleElements = function(){
let showVisibleElements = function(){
// find all elements that should be animated
var visibleElements = $('.' + config.animateElementClass).isInViewport();
let visibleElements = $('.' + config.animateElementClass).isInViewport();
$(visibleElements).removeClass( config.animateElementClass );
@@ -392,7 +392,7 @@ define([
$(element).find('.fade').addClass('in');
// init gallery for "now" visible elements
var newGalleryElements = $(element).filter('[data-gallery="#' + config.galleryId + '"]');
let newGalleryElements = $(element).filter('[data-gallery="#' + config.galleryId + '"]');
initGallery(newGalleryElements);
},
visibility: 'visible'
@@ -410,7 +410,7 @@ define([
// event listener for navigation links
$('.page-scroll').on('click', function(){
// get element to scroll
var anchorTag = $(this).attr('data-anchor');
let anchorTag = $(this).attr('data-anchor');
// scroll to container
$(anchorTag).velocity('scroll', {
@@ -424,7 +424,7 @@ define([
* get current EVE-Online server status
* -> show "server panel"
*/
var initServerStatus = function(){
let initServerStatus = function(){
$.ajax({
type: 'POST',
url: Init.path.getServerStatus,
@@ -432,10 +432,10 @@ define([
}).done(function(responseData, textStatus, request){
if(responseData.hasOwnProperty('status')){
var data = responseData.status;
let data = responseData.status;
data.serverPanelId = config.serverPanelId;
var statusClass = '';
let statusClass = '';
switch(data.serviceStatus.toLowerCase()){
case 'online': statusClass = 'txt-color-green'; break;
case 'vip': statusClass = 'txt-color-orange'; break;
@@ -447,7 +447,7 @@ define([
};
requirejs(['text!templates/ui/server_panel.html', 'mustache'], function(template, Mustache) {
var content = Mustache.render(template, data);
let content = Mustache.render(template, data);
$('#' + config.headerId).prepend(content);
$('#' + config.serverPanelId).velocity('transition.slideLeftBigIn', {
duration: 240
@@ -462,19 +462,19 @@ define([
* show "notification panel" to user
* -> checks if panel not already shown
*/
var initNotificationPanel = function(){
var storageKey = 'notification_panel';
var currentVersion = Util.getVersion();
let initNotificationPanel = function(){
let storageKey = 'notification_panel';
let currentVersion = Util.getVersion();
var showNotificationPanel = function(){
var data = {
let showNotificationPanel = function(){
let data = {
version: Util.getVersion()
};
requirejs(['text!templates/ui/notice.html', 'mustache'], function(template, Mustache) {
var content = Mustache.render(template, data);
let content = Mustache.render(template, data);
var notificationPanel = $('#' + config.notificationPanelId);
let notificationPanel = $('#' + config.notificationPanelId);
notificationPanel.html(content);
notificationPanel.velocity('transition.slideUpIn', {
duration: 300,
@@ -508,13 +508,13 @@ define([
* load character data from cookie information
* -> all validation is done server side!
*/
var initCharacterSelect = function(){
let initCharacterSelect = function(){
/**
* init panel animation for an element
* @param imageWrapperElement
*/
var initCharacterAnimation = function(imageWrapperElement){
let initCharacterAnimation = function(imageWrapperElement){
imageWrapperElement.velocity('stop').velocity('transition.flipBounceXIn', {
display: 'inline-block',
@@ -524,7 +524,7 @@ define([
// Hover effect for character info layer
imageWrapperElement.hoverIntent(function(e){
var characterInfoElement = $(this).find('.' + config.characterImageInfoClass);
let characterInfoElement = $(this).find('.' + config.characterImageInfoClass);
characterInfoElement.velocity('finish').velocity({
width: ['100%', [ 400, 15 ] ]
@@ -532,7 +532,7 @@ define([
easing: 'easeOutSine'
});
}, function(e){
var characterInfoElement = $(this).find('.' + config.characterImageInfoClass);
let characterInfoElement = $(this).find('.' + config.characterImageInfoClass);
characterInfoElement.velocity('finish').velocity({
width: 0
@@ -548,15 +548,15 @@ define([
/**
* update all character panels -> set CSS class (e.g. after some panels were added/removed,..)
*/
var updateCharacterPanels = function(){
var characterRows = $('.' + config.characterSelectionClass + ' .pf-dynamic-area').parent();
var rowClassIdentifier = ((12 / characterRows.length ) <= 3) ? 3 : (12 / characterRows.length);
let updateCharacterPanels = function(){
let characterRows = $('.' + config.characterSelectionClass + ' .pf-dynamic-area').parent();
let rowClassIdentifier = ((12 / characterRows.length ) <= 3) ? 3 : (12 / characterRows.length);
$(characterRows).removeClass().addClass('col-sm-' + rowClassIdentifier);
};
// --------------------------------------------------------------------
var removeCharacterPanel = function(panelElement){
let removeCharacterPanel = function(panelElement){
$(panelElement).velocity('transition.expandOut', {
duration: 250,
complete: function () {
@@ -583,11 +583,11 @@ define([
requirejs(['text!templates/ui/character_panel.html', 'mustache'], function(template, Mustache){
$('.' + config.characterSelectionClass + ' .pf-dynamic-area').each(function(){
var characterElement = $(this);
let characterElement = $(this);
characterElement.showLoadingAnimation();
var requestData = {
let requestData = {
cookie: characterElement.data('cookie')
};
@@ -616,15 +616,20 @@ define([
if(responseData.hasOwnProperty('character')){
var data = {
let data = {
link: this.characterElement.data('href'),
cookieName: this.cookieName,
character: responseData.character
};
var content = Mustache.render(template, data);
let content = Mustache.render(template, data);
this.characterElement.html(content);
// lock character selection on click (prevent click spamming)
this.characterElement.find('a').on('click', function(){
$('.' + config.splashOverlayClass).showSplashOverlay();
});
// show character panel (animation settings)
initCharacterAnimation(this.characterElement.find('.' + config.characterImageWrapperClass));
}else{
@@ -632,7 +637,7 @@ define([
removeCharacterPanel(this.characterElement);
}
}).fail(function( jqXHR, status, error) {
var characterElement = this.characterElement;
let characterElement = this.characterElement;
characterElement.hideLoadingAnimation();
// character data not available -> remove panel
@@ -649,21 +654,21 @@ define([
* @param status
* @param error
*/
var handleAjaxErrorResponse = function(jqXHR, status, error){
let handleAjaxErrorResponse = function(jqXHR, status, error){
var type = status;
var title = 'Status ' + jqXHR.status + ': ' + error;
var message = '';
let type = status;
let title = 'Status ' + jqXHR.status + ': ' + error;
let message = '';
if(jqXHR.responseText){
var errorObj = $.parseJSON(jqXHR.responseText);
let errorObj = $.parseJSON(jqXHR.responseText);
if(
errorObj.error &&
errorObj.error.length > 0
){
for(var i = 0; i < errorObj.error.length; i++){
var errorData = errorObj.error[i];
for(let i = 0; i < errorObj.error.length; i++){
let errorData = errorObj.error[i];
type = errorData.type;
title = 'Status ' + errorData.code + ': ' + errorData.status;
message = errorData.message;
@@ -687,11 +692,11 @@ define([
Util.showVersionInfo();
// show log off message
var isLogOut = location.search.split('logout')[1];
let isLogOut = location.search.split('logout')[1];
if(isLogOut !== undefined){
// show logout dialog
var options = {
let options = {
buttons: {
close: {
label: 'close',

View File

@@ -11,10 +11,10 @@ define([
$.fn.contextMenu = function (settings) {
// animation
var animationInType = 'transition.flipXIn';
var animationInDuration = 150;
var animationOutType = 'transition.flipXOut';
var animationOutDuration = 150;
let animationInType = 'transition.flipXIn';
let animationInDuration = 150;
let animationOutType = 'transition.flipXOut';
let animationOutDuration = 150;
return this.each(function () {
@@ -24,15 +24,15 @@ define([
// hide all other open context menus
$('#pf-dialog-wrapper > .dropdown-menu').hide();
var contextMenu = $(settings.menuSelector);
let contextMenu = $(settings.menuSelector);
var menuLiElements = contextMenu.find('li');
let menuLiElements = contextMenu.find('li');
// show all menu entries
menuLiElements.show();
// disable specific menu entries
for(var i = 0; i < hiddenOptions.length; i++){
for(let i = 0; i < hiddenOptions.length; i++){
contextMenu.find('li[data-action="' + hiddenOptions[i] + '"]').hide();
}
@@ -40,7 +40,7 @@ define([
menuLiElements.removeClass('active');
//set active specific menu entries
for(var j = 0; j < activeOptions.length; j++){
for(let j = 0; j < activeOptions.length; j++){
contextMenu.find('li[data-action="' + activeOptions[j] + '"]').addClass('active');
}
@@ -52,12 +52,33 @@ define([
}).velocity(animationInType, {
duration: animationInDuration,
complete: function(){
// set context menu "click" observer
$(this).off('click').one('click', {component: component, position:{x: originalEvent.offsetX, y: originalEvent.offsetY}}, function (e) {
let posX = 0;
let posY = 0;
if(
originalEvent.offsetX &&
originalEvent.offsetY
){
// Chrome
posX = originalEvent.offsetX;
posY = originalEvent.offsetY ;
}else if(originalEvent.originalEvent){
// Firefox -> #415
posX = originalEvent.originalEvent.layerX;
posY = originalEvent.originalEvent.layerY ;
}
let position = {
x: posX,
y: posY
};
$(this).off('click').one('click', {component: component, position: position}, function (e) {
// hide contextmenu
$(this).hide();
var params = {
let params = {
selectedMenu: $(e.target),
component: e.data.component,
position: e.data.position
@@ -81,9 +102,9 @@ define([
});
function getLeftLocation(e) {
var mouseWidth = e.pageX;
var pageWidth = $(window).width();
var menuWidth = $(settings.menuSelector).width();
let mouseWidth = e.pageX;
let pageWidth = $(window).width();
let menuWidth = $(settings.menuSelector).width();
// opening menu would pass the side of the page
if (mouseWidth + menuWidth > pageWidth &&
@@ -94,9 +115,9 @@ define([
}
function getTopLocation(e) {
var mouseHeight = e.pageY;
var pageHeight = $(window).height();
var menuHeight = $(settings.menuSelector).height();
let mouseHeight = e.pageY;
let pageHeight = $(window).height();
let menuHeight = $(settings.menuSelector).height();
// opening menu would pass the bottom of the page
if (mouseHeight + menuHeight > pageHeight &&

File diff suppressed because it is too large Load Diff

View File

@@ -10,7 +10,7 @@ define([
'use strict';
var config = {
let config = {
logTimerCount: 3, // map log timer in seconds
// map
@@ -29,6 +29,16 @@ define([
connectionOverlayEolId: 'overlayEol' // connection overlay ID (jsPlumb)
};
/**
* get mapElement from overlay or any child of that
* @param mapOverlay
* @returns {JQuery}
*/
let getMapFromOverlay = function(mapOverlay){
return $(mapOverlay).parents('.' + config.mapWrapperClass).find('.' + config.mapClass);
};
/**
* Overlay options (all available map options shown in overlay)
* "active": (active || hover) indicated whether an icon/option
@@ -36,7 +46,7 @@ define([
* "active": Makes icon active when visible
* "hover": Make icon active on hover
*/
var options = {
let options = {
filter: {
title: 'active filter',
trigger: 'active',
@@ -62,9 +72,9 @@ define([
iconClass: ['fa', 'fa-fw', 'fa-tags'],
hoverIntent: {
over: function(e){
var mapElement = getMapFromOverlay(this);
let mapElement = getMapFromOverlay(this);
mapElement.find('.' + config.systemHeadClass).each(function(){
var system = $(this);
let system = $(this);
// init tooltip if not already exists
if ( !system.data('bs.tooltip') ){
system.tooltip({
@@ -80,7 +90,7 @@ define([
});
},
out: function(e){
var mapElement = getMapFromOverlay(this);
let mapElement = getMapFromOverlay(this);
mapElement.find('.' + config.systemHeadClass).tooltip('hide');
}
}
@@ -92,20 +102,20 @@ define([
iconClass: ['fa', 'fa-fw', 'fa-clock-o'],
hoverIntent: {
over: function(e){
var mapElement = getMapFromOverlay(this);
var MapUtil = require('app/map/util');
var Map = require('app/map/map');
var map = Map.getMapInstance( mapElement.data('id') );
var connections = MapUtil.searchConnectionsByScopeAndType(map, 'wh', ['wh_eol']);
var serverDate = Util.getServerTime();
let mapElement = getMapFromOverlay(this);
let MapUtil = require('app/map/util');
let Map = require('app/map/map');
let map = Map.getMapInstance( mapElement.data('id') );
let connections = MapUtil.searchConnectionsByScopeAndType(map, 'wh', ['wh_eol']);
let serverDate = Util.getServerTime();
for (let connection of connections) {
var eolTimestamp = connection.getParameter('eolUpdated');
var eolDate = Util.convertTimestampToServerTime(eolTimestamp);
var diff = Util.getTimeDiffParts(eolDate, serverDate);
let eolTimestamp = connection.getParameter('eolUpdated');
let eolDate = Util.convertTimestampToServerTime(eolTimestamp);
let diff = Util.getTimeDiffParts(eolDate, serverDate);
// format overlay label
var label = '';
let label = '';
if(diff.days){
label += diff.days + 'd ';
}
@@ -124,11 +134,11 @@ define([
}
},
out: function(e){
var mapElement = getMapFromOverlay(this);
var MapUtil = require('app/map/util');
var Map = require('app/map/map');
var map = Map.getMapInstance( mapElement.data('id') );
var connections = MapUtil.searchConnectionsByScopeAndType(map, 'wh', ['wh_eol']);
let mapElement = getMapFromOverlay(this);
let MapUtil = require('app/map/util');
let Map = require('app/map/map');
let map = Map.getMapInstance( mapElement.data('id') );
let connections = MapUtil.searchConnectionsByScopeAndType(map, 'wh', ['wh_eol']);
for (let connection of connections) {
connection.removeOverlay(config.connectionOverlayEolId);
@@ -138,15 +148,6 @@ define([
}
};
/**
* get mapElement from overlay or any child of that
* @param mapOverlay
* @returns {JQuery}
*/
var getMapFromOverlay = function(mapOverlay){
return $(mapOverlay).parents('.' + config.mapWrapperClass).find('.' + config.mapClass);
};
/**
* get map overlay element by type e.g. timer/counter, info - overlay
* @param overlayType
@@ -154,9 +155,9 @@ define([
*/
$.fn.getMapOverlay = function(overlayType){
var mapWrapperElement = $(this).parents('.' + config.mapWrapperClass);
let mapWrapperElement = $(this).parents('.' + config.mapWrapperClass);
var mapOverlay = null;
let mapOverlay = null;
switch(overlayType){
case 'timer':
mapOverlay = mapWrapperElement.find('.' + config.mapOverlayTimerClass);
@@ -172,14 +173,15 @@ define([
/**
* draws the map update counter to the map overlay timer
* @param percent
* @param value
* @returns {*}
*/
$.fn.setMapUpdateCounter = function(percent, value){
var mapOverlayTimer = $(this);
let mapOverlayTimer = $(this);
// check if counter already exists
var counterChart = mapOverlayTimer.getMapCounter();
let counterChart = mapOverlayTimer.getMapCounter();
if(counterChart.length === 0){
// create new counter
@@ -212,7 +214,7 @@ define([
*/
$.fn.getMapCounter = function(){
var mapOverlayTimer = $(this);
let mapOverlayTimer = $(this);
return mapOverlayTimer.find('.' + Init.classes.pieChart.pieChartMapCounterClass);
};
@@ -222,18 +224,18 @@ define([
*/
$.fn.startMapUpdateCounter = function(){
var mapOverlayTimer = $(this);
var counterChart = mapOverlayTimer.getMapCounter();
let mapOverlayTimer = $(this);
let counterChart = mapOverlayTimer.getMapCounter();
var maxSeconds = config.logTimerCount;
let maxSeconds = config.logTimerCount;
var counterChartLabel = counterChart.find('span');
let counterChartLabel = counterChart.find('span');
var percentPerCount = 100 / maxSeconds;
let percentPerCount = 100 / maxSeconds;
// update counter
var updateChart = function(tempSeconds){
var pieChart = counterChart.data('easyPieChart');
let updateChart = function(tempSeconds){
let pieChart = counterChart.data('easyPieChart');
if(pieChart !== undefined){
counterChart.data('easyPieChart').update( percentPerCount * tempSeconds);
@@ -242,13 +244,12 @@ define([
};
// main timer function is called on any counter update
var timer = function(){
let timer = function(mapUpdateCounter){
// decrease timer
var currentSeconds = counterChart.data('currentSeconds');
let currentSeconds = counterChart.data('currentSeconds');
currentSeconds--;
counterChart.data('currentSeconds', currentSeconds);
if(currentSeconds >= 0){
// update counter
updateChart(currentSeconds);
@@ -266,7 +267,7 @@ define([
};
// get current seconds (in case the timer is already running)
var currentSeconds = counterChart.data('currentSeconds');
let currentSeconds = counterChart.data('currentSeconds');
// start values for timer and chart
counterChart.data('currentSeconds', maxSeconds);
@@ -277,7 +278,9 @@ define([
currentSeconds < 0
){
// start timer
var mapUpdateCounter = setInterval(timer, 1000);
let mapUpdateCounter = setInterval(() => {
timer(mapUpdateCounter);
}, 1000);
// store counter interval
counterChart.data('interval', mapUpdateCounter);
@@ -296,14 +299,14 @@ define([
* @param viewType
*/
$.fn.updateOverlayIcon = function(option, viewType){
var mapOverlayInfo = $(this);
let mapOverlayInfo = $(this);
var showOverlay = false;
let showOverlay = false;
var mapOverlayIconClass = options[option].class;
let mapOverlayIconClass = options[option].class;
// look for the overlay icon that should be updated
var iconElement = mapOverlayInfo.find('.' + mapOverlayIconClass);
let iconElement = mapOverlayInfo.find('.' + mapOverlayIconClass);
if(iconElement){
if(viewType === 'show'){
@@ -335,7 +338,7 @@ define([
iconElement.data('visible', false);
// check if there is any visible icon remaining
var visibleIcons = mapOverlayInfo.find('i:visible');
let visibleIcons = mapOverlayInfo.find('i:visible');
if(visibleIcons.length > 0){
showOverlay = true;
}
@@ -364,23 +367,23 @@ define([
*/
$.fn.initMapOverlays = function(){
return this.each(function(){
var parentElement = $(this);
let parentElement = $(this);
var mapOverlayTimer = $('<div>', {
let mapOverlayTimer = $('<div>', {
class: [config.mapOverlayClass, config.mapOverlayTimerClass].join(' ')
});
parentElement.append(mapOverlayTimer);
// ---------------------------------------------------------------------------
// add map overlay info. after scrollbar is initialized
var mapOverlayInfo = $('<div>', {
let mapOverlayInfo = $('<div>', {
class: [config.mapOverlayClass, config.mapOverlayInfoClass].join(' ')
});
// add all overlay elements
for (var prop in options) {
for (let prop in options) {
if(options.hasOwnProperty(prop)){
var icon = $('<i>', {
let icon = $('<i>', {
class: options[prop].iconClass.concat( ['pull-right', options[prop].class] ).join(' ')
}).attr('title', options[prop].title).tooltip({
placement: 'bottom',

View File

@@ -12,7 +12,7 @@ define([
], ($, Init, Util, bootbox, MapUtil) => {
'use strict';
var config = {
let config = {
systemActiveClass: 'pf-system-active' // class for an active system in a map
};
@@ -22,13 +22,13 @@ define([
*/
$.fn.showRallyPointDialog = (system) => {
requirejs(['text!templates/dialog/system_rally.html', 'mustache'], function(template, Mustache) {
var data = {
let data = {
notificationStatus: Init.notificationStatus.rallySet
};
var content = Mustache.render(template, data);
let content = Mustache.render(template, data);
var rallyDialog = bootbox.dialog({
let rallyDialog = bootbox.dialog({
message: content,
title: 'Set rally point for "' + system.getSystemInfo( ['alias'] ) + '"',
buttons: {
@@ -66,9 +66,9 @@ define([
* @returns {*}
*/
$.fn.showDeleteSystemDialog = (map, systems = []) => {
var mapContainer = $( map.getContainer() );
var validDeleteSystems = [];
var activeCharacters = 0;
let mapContainer = $( map.getContainer() );
let validDeleteSystems = [];
let activeCharacters = 0;
// check if systems belong to map -> security check
for (let system of systems) {
let systemElement = $(system);
@@ -84,13 +84,13 @@ define([
}
if(validDeleteSystems.length){
var msg = '';
let msg = '';
if(validDeleteSystems.length === 1){
var deleteSystem = $(validDeleteSystems[0]);
var systemName = deleteSystem.data('name');
var systemAlias = deleteSystem.getSystemInfo( ['alias'] );
let deleteSystem = $(validDeleteSystems[0]);
let systemName = deleteSystem.data('name');
let systemAlias = deleteSystem.getSystemInfo( ['alias'] );
var systemNameStr = (systemName === systemAlias) ? '"' + systemName + '"' : '"' + systemAlias + '" (' + systemName + ')';
let systemNameStr = (systemName === systemAlias) ? '"' + systemName + '"' : '"' + systemAlias + '" (' + systemName + ')';
systemNameStr = '<span class="txt-color txt-color-warning">' + systemNameStr + '</span>';
msg = 'Delete system ' + systemNameStr + ' and all its connections?';
}else{
@@ -102,7 +102,7 @@ define([
msg += ' <span class="txt-color txt-color-warning">Warning: ' + activeCharacters + ' active characters</span>';
}
var systemDeleteDialog = bootbox.confirm(msg, result => {
let systemDeleteDialog = bootbox.confirm(msg, result => {
if(result){
deleteSystems(map, validDeleteSystems, (systems) => {
// callback function after deleted -> close dialog
@@ -130,14 +130,14 @@ define([
* @param systems
* @param callback function
*/
var deleteSystems = (map, systems = [], callback = (systems) => {}) => {
var mapContainer = $( map.getContainer() );
mapContainer.getMapOverlay('timer').startMapUpdateCounter();
let deleteSystems = (map, systems = [], callback = (systems) => {}) => {
let mapContainer = $( map.getContainer() );
$.ajax({
type: 'POST',
url: Init.path.deleteSystem,
data: {
mapId: mapContainer.data('id'),
systemIds: systems.map( system => $(system).data('id') )
},
dataType: 'json',
@@ -151,7 +151,7 @@ define([
callback(this.systems);
}).fail(function(jqXHR, status, error) {
var reason = status + ' ' + error;
let reason = status + ' ' + error;
Util.showNotify({title: jqXHR.status + ': deleteSystem', text: reason, type: 'warning'});
$(document).setProgramStatus('problem');
});
@@ -162,9 +162,9 @@ define([
* @param map
* @param systems
*/
var removeSystems = (map, systems) => {
let removeSystems = (map, systems) => {
var removeSystemCallbak = function(deleteSystem){
let removeSystemCallbak = function(deleteSystem){
map.remove(deleteSystem);
};
@@ -174,7 +174,7 @@ define([
// check if system is "active"
if( system.hasClass(config.systemActiveClass) ){
// get parent Tab Content and fire clear modules event
var tabContentElement = MapUtil.getTabContentElementByMapElement( system );
let tabContentElement = MapUtil.getTabContentElementByMapElement( system );
$(tabContentElement).trigger('pf:removeSystemModules');
}

View File

@@ -9,7 +9,7 @@ define([
], function($, Init, Util) {
'use strict';
var config = {
let config = {
mapSnapToGridDimension: 20, // px for grid snapping (grid YxY)
// local storage
@@ -22,7 +22,7 @@ define([
};
// map menu options
var mapOptions = {
let mapOptions = {
mapMagnetizer: {
buttonId: Util.config.menuButtonMagnetizerId,
description: 'Magnetizer',
@@ -42,23 +42,23 @@ define([
* @param {bool} filterByUser
* @returns {Array}
*/
var getMapTypes = function(filterByUser){
var mapTypes = [];
let getMapTypes = function(filterByUser){
let mapTypes = [];
$.each(Init.mapTypes, function(prop, data){
// skip "default" type -> just for 'add' icon
if(data.label.length > 0){
var tempData = data;
let tempData = data;
tempData.name = prop;
mapTypes.push(tempData);
}
});
if(filterByUser === true){
var corporationId = Util.getCurrentUserInfo('corporationId');
var allianceId = Util.getCurrentUserInfo('allianceId');
let corporationId = Util.getCurrentUserInfo('corporationId');
let allianceId = Util.getCurrentUserInfo('allianceId');
var authorizedMapTypes = [];
let authorizedMapTypes = [];
// check if character data exists
if(corporationId > 0) {
authorizedMapTypes.push('corporation');
@@ -71,9 +71,9 @@ define([
authorizedMapTypes.push('private');
// compare "all" map types with "authorized" types
var tempMapTypes = [];
for(var i = 0; i < mapTypes.length; i++){
for(var j = 0; j < authorizedMapTypes.length; j++){
let tempMapTypes = [];
for(let i = 0; i < mapTypes.length; i++){
for(let j = 0; j < authorizedMapTypes.length; j++){
if(mapTypes[i].name === authorizedMapTypes[j]){
tempMapTypes.push(mapTypes[i]);
break;
@@ -91,10 +91,10 @@ define([
* get all available scopes for a map
* @returns {Array}
*/
var getMapScopes = function(){
var scopes = [];
let getMapScopes = function(){
let scopes = [];
$.each(Init.mapScopes, function(prop, data){
var tempData = data;
let tempData = data;
tempData.name = prop;
scopes.push(tempData);
});
@@ -108,8 +108,8 @@ define([
* @param {string} option
* @returns {string}
*/
var getScopeInfoForMap = function(info, option){
var scopeInfo = '';
let getScopeInfoForMap = function(info, option){
let scopeInfo = '';
if(Init.mapScopes.hasOwnProperty(info)){
scopeInfo = Init.mapScopes[info][option];
}
@@ -120,7 +120,7 @@ define([
* get all available map icons
* @returns {Object[]}
*/
var getMapIcons = function(){
let getMapIcons = function(){
return Init.mapIcons;
};
@@ -130,8 +130,8 @@ define([
* @param {string} option
* @returns {string}
*/
var getInfoForMap = function(mapType, option){
var mapInfo = '';
let getInfoForMap = function(mapType, option){
let mapInfo = '';
if(Init.mapTypes.hasOwnProperty(mapType)){
mapInfo = Init.mapTypes[mapType][option];
}
@@ -144,8 +144,8 @@ define([
* @param {string} option
* @returns {string}
*/
var getInfoForSystem = function(info, option){
var systemInfo = '';
let getInfoForSystem = function(info, option){
let systemInfo = '';
if(Init.classes.systemInfo.hasOwnProperty(info)){
systemInfo = Init.classes.systemInfo[info][option];
}
@@ -158,8 +158,8 @@ define([
* @param {string} option
* @returns {string}
*/
var getSystemTypeInfo = function(systemTypeId, option){
var systemTypeInfo = '';
let getSystemTypeInfo = function(systemTypeId, option){
let systemTypeInfo = '';
$.each(Init.systemType, function(prop, data){
if(systemTypeId === data.id){
systemTypeInfo = data[option];
@@ -175,8 +175,8 @@ define([
* @param option
* @returns {string}
*/
var getEffectInfoForSystem = function(effect, option){
var effectInfo = '';
let getEffectInfoForSystem = function(effect, option){
let effectInfo = '';
if( Init.classes.systemEffects.hasOwnProperty(effect) ){
effectInfo = Init.classes.systemEffects[effect][option];
}
@@ -198,9 +198,9 @@ define([
* @param {JQuery[]} systems - system DOM elements
* @returns {Array} connections - found connection, DOM elements
*/
var searchConnectionsBySystems = function(map, systems){
var connections = [];
var withBackConnection = false;
let searchConnectionsBySystems = function(map, systems){
let connections = [];
let withBackConnection = false;
$.each(systems, function(i, system){
// get connections where system is source
@@ -221,15 +221,15 @@ define([
* @param {string|string[]} type
* @returns {Array}
*/
var searchConnectionsByScopeAndType = function(map, scope, type){
var connections = [];
var scopeArray = (scope === undefined) ? ['*'] : ((Array.isArray(scope)) ? scope : [scope]);
var typeArray = (type === undefined) ? [] : ((Array.isArray(type)) ? type : [type]);
let searchConnectionsByScopeAndType = function(map, scope, type){
let connections = [];
let scopeArray = (scope === undefined) ? ['*'] : ((Array.isArray(scope)) ? scope : [scope]);
let typeArray = (type === undefined) ? [] : ((Array.isArray(type)) ? type : [type]);
map.select({scope: scopeArray}).each(function(connection){
if(typeArray.length > 0){
// filter by connection type as well...
for(var i = 0; i < typeArray.length; i++){
for(let i = 0; i < typeArray.length; i++){
if( connection.hasType(typeArray[i]) ){
connections.push(connection);
break; // don´t add same connection multiple times
@@ -250,8 +250,8 @@ define([
* @param {string} option
* @returns {string}
*/
var getConnectionInfo = function(connectionTyp, option){
var connectionInfo = '';
let getConnectionInfo = function(connectionTyp, option){
let connectionInfo = '';
if(Init.connectionTypes.hasOwnProperty(connectionTyp)){
connectionInfo = Init.connectionTypes[connectionTyp][option];
}
@@ -265,8 +265,8 @@ define([
* @param {JQuery} systemB
* @returns {Array}
*/
var checkForConnection = function(map, systemA, systemB){
var connections = [];
let checkForConnection = function(map, systemA, systemB){
let connections = [];
connections = connections.concat( map.getConnections({scope: '*', source: systemA, target: systemB}) );
// get connections where system is target
connections = connections.concat( map.getConnections({scope: '*', source: systemB, target: systemA}) );
@@ -279,8 +279,8 @@ define([
* @param {string} scope
* @returns {string}
*/
var getDefaultConnectionTypeByScope = function(scope){
var type = '';
let getDefaultConnectionTypeByScope = function(scope){
let type = '';
switch(scope){
case 'wh':
type = 'wh_fresh';
@@ -303,7 +303,7 @@ define([
* @param {Object} connection - jsPlumb object
* @param {string} status
*/
var setConnectionWHStatus = function(connection, status){
let setConnectionWHStatus = function(connection, status){
if(
status === 'wh_fresh' &&
connection.hasType('wh_fresh') !== true
@@ -344,13 +344,13 @@ define([
* @param {string} option
* @returns {string}
*/
var getScopeInfoForConnection = function(info, option){
var scopeInfo = '';
let getScopeInfoForConnection = function(info, option){
let scopeInfo = '';
if(Init.connectionScopes.hasOwnProperty(info)){
switch(option){
case 'connectorDefinition':
// json data in DB
var temp = '{ "data": ' + Init.connectionScopes[info][option] + '}';
let temp = '{ "data": ' + Init.connectionScopes[info][option] + '}';
scopeInfo = $.parseJSON( temp).data;
break;
default:
@@ -367,11 +367,114 @@ define([
* @param element
* @returns {*}
*/
var getTabContentElementByMapElement = function(element){
var tabContentElement = $(element).parents('.' + config.mapTabContentClass);
let getTabContentElementByMapElement = function(element){
let tabContentElement = $(element).parents('.' + config.mapTabContentClass);
return tabContentElement;
};
/**
* store mapId for current user (IndexedDB)
* @param mapId
*/
let storeDefaultMapId = function(mapId){
if(mapId > 0){
let userData = Util.getCurrentUserData();
if(
userData &&
userData.character
){
storeLocalData('character', userData.character.id, 'defaultMapId', mapId);
}
}
};
/**
* get key prefix for local storage data
* @param type
* @returns {boolean}
*/
let getLocalStoragePrefixByType = function(type){
let prefix = false;
switch(type){
case 'character': prefix = config.characterLocalStoragePrefix; break;
case 'map': prefix = config.mapLocalStoragePrefix; break;
default: prefix = config.mapLocalStoragePrefix;
}
return prefix;
};
/**
* get stored local data from client cache (IndexedDB)
* @param type
* @param objectId
* @returns {*}
*/
let getLocaleData = function(type, objectId){
if(objectId > 0){
let storageKey = getLocalStoragePrefixByType(type) + objectId;
return Util.getLocalStorage().getItem(storageKey);
}else{
console.warn('Local storage requires object id > 0');
}
};
/**
* store local config data to client cache (IndexedDB)
* @param type
* @param objectId
* @param key
* @param value
*/
let storeLocalData = function(type, objectId, key, value){
if(objectId > 0){
// get current map config
let storageKey = getLocalStoragePrefixByType(type) + objectId;
Util.getLocalStorage().getItem(storageKey).then(function(data) {
// This code runs once the value has been loaded
// from the offline store.
data = (data === null) ? {} : data;
// set/update value
data[this.key] = this.value;
Util.getLocalStorage().setItem(this.storageKey, data);
}.bind({
key: key,
value: value,
storageKey: storageKey
})).catch(function(err) {
// This code runs if there were any errors
console.error('Map local storage can not be accessed!');
});
}else{
console.warn('storeLocalData(): Local storage requires object id > 0');
}
};
/**
* delete local map configuration by key (IndexedDB)
* @param type
* @param objectId
* @param key
*/
let deleteLocalData = function(type, objectId, key){
if(objectId > 0){
// get current map config
let storageKey = getLocalStoragePrefixByType(type) + objectId;
Util.getLocalStorage().getItem(storageKey).then(function(data) {
if(
data &&
data.hasOwnProperty(key)
){
delete data[key];
Util.getLocalStorage().setItem(this.storageKey, data);
}
}.bind({
storageKey: storageKey
}));
}else{
console.warn('deleteLocalData(): Local storage requires object id > 0');
}
};
/**
* set or change rallyPoint for systems
* @param rallyUpdated
@@ -380,9 +483,9 @@ define([
*/
$.fn.setSystemRally = function(rallyUpdated, options){
rallyUpdated = rallyUpdated || 0;
var rallyPoke = false;
let rallyPoke = false;
var defaultOptions = {
let defaultOptions = {
poke: false,
hideNotification: false,
hideCounter: false,
@@ -390,8 +493,8 @@ define([
options = $.extend({}, defaultOptions, options);
return this.each(function(){
var system = $(this);
var rally = system.data('rallyUpdated') || 0;
let system = $(this);
let rally = system.data('rallyUpdated') || 0;
if(rallyUpdated !== rally){
// rally status changed
@@ -399,7 +502,7 @@ define([
system.getMapOverlay('timer').startMapUpdateCounter();
}
var rallyClass = getInfoForSystem('rally', 'class');
let rallyClass = getInfoForSystem('rally', 'class');
if(rallyUpdated > 0){
// new rally point set OR update system with rally information
@@ -408,7 +511,7 @@ define([
// rallyUpdated > 0 is required for poke!
rallyPoke = options.poke;
var notificationOptions = {
let notificationOptions = {
title: 'Rally Point',
text: 'System: ' + system.data('name')
};
@@ -421,13 +524,13 @@ define([
// rally saved AND poke option active
// check if desktop notification was already send
var mapId = system.data('mapid');
var systemId = system.data('id');
var promiseStore = getLocaleData('map', mapId);
let mapId = system.data('mapid');
let systemId = system.data('id');
let promiseStore = getLocaleData('map', mapId);
promiseStore.then(function(data) {
// This code runs once the value has been loaded
// from the offline store.
var rallyPokeData = {};
let rallyPokeData = {};
if(
data &&
@@ -468,109 +571,6 @@ define([
});
};
/**
* store mapId for current user (IndexedDB)
* @param mapId
*/
var storeDefaultMapId = function(mapId){
if(mapId > 0){
var userData = Util.getCurrentUserData();
if(
userData &&
userData.character
){
storeLocalData('character', userData.character.id, 'defaultMapId', mapId);
}
}
};
/**
* get key prefix for local storage data
* @param type
* @returns {boolean}
*/
var getLocalStoragePrefixByType = function(type){
var prefix = false;
switch(type){
case 'character': prefix = config.characterLocalStoragePrefix; break;
case 'map': prefix = config.mapLocalStoragePrefix; break;
default: prefix = config.mapLocalStoragePrefix;
}
return prefix;
};
/**
* get stored local data from client cache (IndexedDB)
* @param type
* @param objectId
* @returns {*}
*/
var getLocaleData = function(type, objectId){
if(objectId > 0){
var storageKey = getLocalStoragePrefixByType(type) + objectId;
return Util.getLocalStorage().getItem(storageKey);
}else{
console.warn('Local storage requires object id > 0');
}
};
/**
* store local config data to client cache (IndexedDB)
* @param type
* @param objectId
* @param key
* @param value
*/
var storeLocalData = function(type, objectId, key, value){
if(objectId > 0){
// get current map config
var storageKey = getLocalStoragePrefixByType(type) + objectId;
Util.getLocalStorage().getItem(storageKey).then(function(data) {
// This code runs once the value has been loaded
// from the offline store.
data = (data === null) ? {} : data;
// set/update value
data[this.key] = this.value;
Util.getLocalStorage().setItem(this.storageKey, data);
}.bind({
key: key,
value: value,
storageKey: storageKey
})).catch(function(err) {
// This code runs if there were any errors
console.error('Map local storage can not be accessed!');
});
}else{
console.warn('storeLocalData(): Local storage requires object id > 0');
}
};
/**
* delete local map configuration by key (IndexedDB)
* @param type
* @param objectId
* @param key
*/
var deleteLocalData = function(type, objectId, key){
if(objectId > 0){
// get current map config
var storageKey = getLocalStoragePrefixByType(type) + objectId;
Util.getLocalStorage().getItem(storageKey).then(function(data) {
if(
data &&
data.hasOwnProperty(key)
){
delete data[key];
Util.getLocalStorage().setItem(this.storageKey, data);
}
}.bind({
storageKey: storageKey
}));
}else{
console.warn('deleteLocalData(): Local storage requires object id > 0');
}
};
return {
config: config,
mapOptions: mapOptions,

127
js/app/map/worker.js Normal file
View File

@@ -0,0 +1,127 @@
/**
* SharedWorker config for map
*/
define([
'app/util'
], function(Util) {
'use strict';
let config = {
};
let sharedWorker = null;
let MsgWorker = null;
let characterId = null;
/**
* get WebSocket URL for SharedWorker script
* @returns {string}
*/
let getWebSocketURL = () => {
let domain = location.host;
let workerProtocol = (window.location.protocol === 'https:') ? 'wss:' : 'ws:';
return workerProtocol + '//' + domain + '/ws/map/update';
};
/**
* get SharedWorker Script path
* @returns {string}
*/
let getWorkerScript = () => {
return '/public/js/' + Util.getVersion() + '/app/worker/map.js';
};
/**
* get path to message object
* @returns {string}
*/
let getMessageWorkerObjectPath = () => {
return '/public/js/' + Util.getVersion() + '/app/worker/message.js';
};
/**
* init (connect) WebSocket within SharedWorker
*/
let initSocket = () => {
let MsgWorkerInit = new MsgWorker('ws:init');
MsgWorkerInit.data({
uri: getWebSocketURL(),
characterId: characterId,
});
sharedWorker.port.postMessage(MsgWorkerInit);
};
/**
* init (start/connect) to "SharedWorker"
* -> set worker events
*/
let init = (config) => {
// set characterId that is connected with this SharedWorker PORT
characterId = parseInt(config.characterId);
// get message Class for App <=> SharedWorker MessageEvent communication
requirejs([getMessageWorkerObjectPath()], () => {
MsgWorker = window.MsgWorker;
// start/connect to "SharedWorker"
sharedWorker = new SharedWorker( getWorkerScript(), getMessageWorkerObjectPath() );
sharedWorker.port.addEventListener('message', (e) => {
let MsgWorkerMessage = e.data;
Object.setPrototypeOf(MsgWorkerMessage, MsgWorker.prototype);
switch(MsgWorkerMessage.command){
case 'ws:open':
config.callbacks.onOpen(MsgWorkerMessage);
break;
case 'ws:send':
config.callbacks.onGet(MsgWorkerMessage);
break;
case 'ws:closed':
config.callbacks.onClosed(MsgWorkerMessage);
break;
case 'ws:error':
config.callbacks.onError(MsgWorkerMessage);
break;
}
}, false);
sharedWorker.onerror = (e) => {
// could not connect to SharedWorker script -> send error back
let MsgWorkerError = new MsgWorker('sw:error');
MsgWorkerError.meta({
reason: 'Could not connect to SharedWorker: ' + getWorkerScript()
});
config.callbacks.onError(MsgWorkerError);
};
sharedWorker.port.start();
// SharedWorker initialized
let MsgWorkerInit = new MsgWorker('sw:init');
config.callbacks.onInit(MsgWorkerInit);
// startWebSocket
initSocket();
});
};
let send = (task, data) => {
let MsgWorkerSend = new MsgWorker('ws:send');
MsgWorkerSend.task(task);
MsgWorkerSend.data(data);
sharedWorker.port.postMessage(MsgWorkerSend);
};
return {
getWebSocketURL: getWebSocketURL,
init: init,
send: send
};
});

View File

@@ -9,16 +9,17 @@ define([
'app/render',
'app/logging',
'app/page',
'app/map/worker',
'app/ui/form_element',
'app/module_map'
], function($, Init, Util, Render, Logging, Page) {
], ($, Init, Util, Render, Logging, Page, MapWorker) => {
'use strict';
/**
* main init "map" page
*/
$(function(){
$(() => {
Util.initPrototypes();
// set default AJAX config
@@ -28,6 +29,7 @@ define([
Util.initDefaultBootboxConfig();
// load page
// load info (maintenance) info panel (if scheduled)
$('body').loadPageStructure();
// show app information in browser console
@@ -36,14 +38,14 @@ define([
// init logging
Logging.init();
var mapModule = $('#' + Util.config.mapModuleId);
let mapModule = $('#' + Util.config.mapModuleId);
// map init load static data =======================================================
$.getJSON( Init.path.initMap, function( initData ) {
$.getJSON( Init.path.initMap, (initData) => {
if( initData.error.length > 0 ){
for(var i = 0; i < initData.error.length; i++){
for(let i = 0; i < initData.error.length; i++){
Util.showNotify({
title: initData.error[i].title,
text: initData.error[i].message,
@@ -59,11 +61,10 @@ define([
Init.systemStatus = initData.systemStatus;
Init.systemType = initData.systemType;
Init.characterStatus = initData.characterStatus;
Init.maxSharedCount = initData.maxSharedCount;
Init.routes = initData.routes;
Init.notificationStatus = initData.notificationStatus;
Init.activityLogging = initData.activityLogging;
Init.routeSearch = initData.routeSearch;
Init.programMode = initData.programMode;
// init tab change observer, Once the timers are available
Page.initTabChangeObserver();
@@ -71,207 +72,82 @@ define([
// init map module
mapModule.initMapModule();
}).fail(function( jqXHR, status, error) {
var reason = status + ' ' + jqXHR.status + ': ' + error;
// load info (maintenance) info panel (if scheduled)
if(Init.programMode.maintenance){
$('body').showGlobalInfoPanel();
}
}).fail(( jqXHR, status, error) => {
let reason = status + ' ' + jqXHR.status + ': ' + error;
$(document).trigger('pf:shutdown', {status: jqXHR.status, reason: reason});
});
/**
* request all map access data (tokens) -> required wor WebSocket subscription
*/
let getMapAccessData = () => {
$.getJSON( Init.path.getAccessData, ( response ) => {
if(response.status === 'OK'){
// init SharedWorker for maps
MapWorker.init({
characterId: response.data.id,
callbacks: {
onInit: (MsgWorkerMessage) => {
Util.setSyncStatus(MsgWorkerMessage.command);
},
onOpen: (MsgWorkerMessage) => {
Util.setSyncStatus(MsgWorkerMessage.command, MsgWorkerMessage.meta());
MapWorker.send( 'subscribe', response.data);
},
onGet: (MsgWorkerMessage) => {
switch(MsgWorkerMessage.task()){
case 'mapUpdate':
Util.updateCurrentMapData( MsgWorkerMessage.data() );
mapModule.updateMapModule();
break;
case 'mapAccess':
case 'mapDeleted':
Util.deleteCurrentMapData( MsgWorkerMessage.data() );
mapModule.updateMapModule();
break;
}
Util.setSyncStatus('ws:get');
},
onClosed: (MsgWorkerMessage) => {
Util.setSyncStatus(MsgWorkerMessage.command, MsgWorkerMessage.meta());
},
onError: (MsgWorkerMessage) => {
Util.setSyncStatus(MsgWorkerMessage.command, MsgWorkerMessage.meta());
}
}
});
}
});
};
getMapAccessData();
/**
* main function for init all map relevant trigger calls
*/
$.fn.initMapModule = function(){
var mapModule = $(this);
let mapModule = $(this);
// log keys ------------------------------------------------------------------------
// ajax request update map data
var logKeyServerMapData = 'UPDATE_SERVER_MAP';
// update client map data
var logKeyClientMapData = 'UPDATE_CLIENT_MAP';
// ajax request update map user data
var logKeyServerUserData = 'UPDATE_SERVER_USER_DATA';
// update client map user data
var logKeyClientUserData = 'UPDATE_CLIENT_USER_DATA';
let logKeyServerMapData = Init.performanceLogging.keyServerMapData;
let logKeyServerUserData = Init.performanceLogging.keyServerUserData;
// main update intervals/trigger (heartbeat)
var updateTimeouts = {
let updateTimeouts = {
mapUpdate: 0,
userUpdate: 0
};
var locationToggle = $('#' + Util.config.headMapTrackingId);
// ping for main map update ========================================================
var triggerMapUpdatePing = function(){
// check each execution time if map module is still available
var check = $('#' + mapModule.attr('id')).length;
if(check === 0){
// program crash stop any update
return;
}
// get updated map data
var updatedMapData = {
mapData: mapModule.getMapModuleDataForUpdate(),
getUserData: ( Util.getCurrentUserData() ) ? 0 : 1
};
// start log
Util.timeStart(logKeyServerMapData);
// store updatedMapData
$.ajax({
type: 'POST',
url: Init.path.updateMapData,
data: updatedMapData,
dataType: 'json'
}).done(function(data){
// log request time
var duration = Util.timeStop(logKeyServerMapData);
Util.log(logKeyServerMapData, {duration: duration, type: 'server', description: 'request map data'});
if(
data.error &&
data.error.length > 0
){
// any error in the main trigger functions result in a user log-off
$(document).trigger('pf:menuLogout');
}else{
$(document).setProgramStatus('online');
if(data.userData !== undefined) {
// store current user data global (cache)
Util.setCurrentUserData(data.userData);
}
if(data.mapData.length === 0){
// clear all existing maps
mapModule.clearMapModule();
// no map data available -> show "new map" dialog
$(document).trigger('pf:menuShowMapSettings', {tab: 'new'});
}else{
// map data found
// start log
Util.timeStart(logKeyClientMapData);
// load/update main map module
mapModule.updateMapModule(data.mapData);
// log client map update time
duration = Util.timeStop(logKeyClientMapData);
Util.log(logKeyClientMapData, {duration: duration, type: 'client', description: 'update map'});
}
// get the current update delay (this can change if a user is inactive)
var mapUpdateDelay = Util.getCurrentTriggerDelay( logKeyServerMapData, 0 );
// init new trigger
updateTimeouts.mapUpdate = setTimeout(function(){
triggerMapUpdatePing();
}, mapUpdateDelay);
// initial start for the userUpdate trigger
// this should only be called at the first time!
if(updateTimeouts.userUpdate === 0){
// start user update trigger after map loaded
updateTimeouts.userUpdate = setTimeout(function(){
triggerUserUpdatePing();
}, 3000);
}
}
}).fail(handleAjaxErrorResponse);
};
// ping for user data update =======================================================
var triggerUserUpdatePing = function(){
// IMPORTANT: Get user data for ONE map that is currently visible
// On later releases this can be easy changed to "full update" all maps for a user
//
var mapIds = [];
var activeMap = Util.getMapModule().getActiveMap();
if(activeMap){
mapIds = [ activeMap.data('id') ];
}
var updatedUserData = {
mapIds: mapIds,
systemData: Util.getCurrentSystemData(),
characterMapData: {
mapTracking: (locationToggle.is(':checked') ? 1 : 0) // location tracking
}
};
Util.timeStart(logKeyServerUserData);
$.ajax({
type: 'POST',
url: Init.path.updateUserData,
data: updatedUserData,
dataType: 'json'
}).done(function(data){
// log request time
var duration = Util.timeStop(logKeyServerUserData);
Util.log(logKeyServerUserData, {duration: duration, type: 'server', description:'request user data'});
if(data.error.length > 0){
// any error in the main trigger functions result in a user log-off
$(document).trigger('pf:menuLogout');
}else{
$(document).setProgramStatus('online');
if(data.userData !== undefined){
// store current user data global (cache)
var userData = Util.setCurrentUserData(data.userData);
// store current map user data (cache)
if(data.mapUserData !== undefined){
Util.setCurrentMapUserData(data.mapUserData);
}
// start log
Util.timeStart(logKeyClientUserData);
// active character data found
mapModule.updateMapModuleData();
// log client user data update time
duration = Util.timeStop(logKeyClientUserData);
Util.log(logKeyClientUserData, {duration: duration, type: 'client', description:'update users'});
// update system info panels
if(data.system){
mapModule.updateSystemModuleData(data.system);
}
// get the current update delay (this can change if a user is inactive)
var mapUserUpdateDelay = Util.getCurrentTriggerDelay( logKeyServerUserData, 0 );
// init new trigger
updateTimeouts.userUpdate = setTimeout(function(){
triggerUserUpdatePing();
}, mapUserUpdateDelay);
}
}
}).fail(handleAjaxErrorResponse);
};
let locationToggle = $('#' + Util.config.headMapTrackingId);
/**
* Ajax error response handler function for main-ping functions
@@ -279,16 +155,16 @@ define([
* @param status
* @param error
*/
var handleAjaxErrorResponse = function(jqXHR, status, error){
let handleAjaxErrorResponse = (jqXHR, status, error) => {
// clear both main update request trigger timer
clearUpdateTimeouts();
var reason = status + ' ' + jqXHR.status + ': ' + error;
var errorData = [];
let reason = status + ' ' + jqXHR.status + ': ' + error;
let errorData = [];
if(jqXHR.responseJSON){
// handle JSON
var errorObj = $.parseJSON(jqXHR.responseText);
let errorObj = $.parseJSON(jqXHR.responseText);
if(
errorObj.error &&
@@ -308,12 +184,188 @@ define([
};
/**
* init (schedule) next MapUpdate Ping
*/
let initMapUpdatePing = (forceUpdateMapData) => {
// get the current update delay (this can change if a user is inactive)
let delay = Util.getCurrentTriggerDelay( logKeyServerMapData, 0 );
updateTimeouts.mapUpdate = setTimeout((forceUpdateMapData) => {
triggerMapUpdatePing(forceUpdateMapData);
}, delay, forceUpdateMapData);
};
// ping for main map update ========================================================
/**
* @param forceUpdateMapData // force request to be send
*/
let triggerMapUpdatePing = (forceUpdateMapData) => {
// check each interval if map module is still available
let check = $('#' + mapModule.attr('id')).length;
if(check === 0){
// program crash stop any update
return;
}
// get updated map data
let updatedMapData = {
mapData: mapModule.getMapModuleDataForUpdate(),
getUserData: ( Util.getCurrentUserData() ) ? 0 : 1
};
// check if mapUpdate trigger should be send
// -> if "syncType" === "ajax" -> send always
// -> if "syncType" === "webSocket" -> send initial AND on map changes
if(
forceUpdateMapData ||
Util.getSyncType() === 'ajax' ||
(
Util.getSyncType() === 'webSocket' &&
updatedMapData.mapData.length
)
){
// start log
Util.timeStart(logKeyServerMapData);
// store updatedMapData
$.ajax({
type: 'POST',
url: Init.path.updateMapData,
data: updatedMapData,
dataType: 'json'
}).done((data) => {
// log request time
let duration = Util.timeStop(logKeyServerMapData);
Util.log(logKeyServerMapData, {duration: duration, type: 'server', description: 'request map data'});
Util.setSyncStatus('ajax:get');
if(
data.error &&
data.error.length > 0
){
// any error in the main trigger functions result in a user log-off
$(document).trigger('pf:menuLogout');
}else{
$(document).setProgramStatus('online');
if(data.userData !== undefined) {
// store current user data global (cache)
Util.setCurrentUserData(data.userData);
}
// map data found
Util.setCurrentMapData(data.mapData);
// load/update main map module
mapModule.updateMapModule();
// get the current update delay (this can change if a user is inactive)
let mapUpdateDelay = Util.getCurrentTriggerDelay( logKeyServerMapData, 0 );
// init new trigger
initMapUpdatePing(false);
// initial start for the userUpdate trigger
// this should only be called at the first time!
if(updateTimeouts.userUpdate === 0){
// start user update trigger after map loaded
updateTimeouts.userUpdate = setTimeout(() => {
triggerUserUpdatePing();
}, 1000);
}
}
}).fail(handleAjaxErrorResponse);
}else{
// skip this mapUpdate trigger and init next one
initMapUpdatePing(false);
}
};
// ping for user data update =======================================================
let triggerUserUpdatePing = () => {
// IMPORTANT: Get user data for ONE map that is currently visible
// On later releases this can be easy changed to "full update" all maps for a user
//
let mapIds = [];
let activeMap = Util.getMapModule().getActiveMap();
if(activeMap){
mapIds = [ activeMap.data('id') ];
}
let updatedUserData = {
mapIds: mapIds,
systemData: Util.getCurrentSystemData(),
characterMapData: {
mapTracking: (locationToggle.is(':checked') ? 1 : 0) // location tracking
}
};
Util.timeStart(logKeyServerUserData);
$.ajax({
type: 'POST',
url: Init.path.updateUserData,
data: updatedUserData,
dataType: 'json'
}).done((data) => {
// log request time
let duration = Util.timeStop(logKeyServerUserData);
Util.log(logKeyServerUserData, {duration: duration, type: 'server', description:'request user data'});
if(data.error.length > 0){
// any error in the main trigger functions result in a user log-off
$(document).trigger('pf:menuLogout');
}else{
$(document).setProgramStatus('online');
if(data.userData !== undefined){
// store current user data global (cache)
let userData = Util.setCurrentUserData(data.userData);
// store current map user data (cache)
if(data.mapUserData !== undefined){
Util.setCurrentMapUserData(data.mapUserData);
}
// active character data found
mapModule.updateMapModuleData();
// update system info panels
if(data.system){
mapModule.updateSystemModuleData(data.system);
}
// get the current update delay (this can change if a user is inactive)
let mapUserUpdateDelay = Util.getCurrentTriggerDelay( logKeyServerUserData, 0 );
// init new trigger
updateTimeouts.userUpdate = setTimeout(() => {
triggerUserUpdatePing();
}, mapUserUpdateDelay);
}
}
}).fail(handleAjaxErrorResponse);
};
/**
* clear both main update timeouts
* -> stop program from working -> shutdown
*/
var clearUpdateTimeouts = function(){
for(var intervalKey in updateTimeouts) {
let clearUpdateTimeouts = () => {
for(let intervalKey in updateTimeouts) {
if(updateTimeouts.hasOwnProperty(intervalKey)){
clearTimeout( updateTimeouts[intervalKey] );
@@ -322,7 +374,7 @@ define([
};
// initial start of the map update function
triggerMapUpdatePing();
triggerMapUpdatePing(true);
};

View File

@@ -19,7 +19,7 @@ define([
'use strict';
var config = {
let config = {
dynamicElementWrapperId: 'pf-dialog-wrapper', // parent Element for dynamic content (dialogs,..)
mapTabElementId: 'pf-map-tab-element', // id for map tab element (tabs + content)
mapTabBarId: 'pf-map-tabs', // id for map tab bar
@@ -45,15 +45,14 @@ define([
};
var mapTabChangeBlocked = false; // flag for preventing map tab switch
let mapTabChangeBlocked = false; // flag for preventing map tab switch
/**
* get all maps for a maps module
* @returns {*}
*/
$.fn.getMaps = function(){
var maps = $(this).find('.' + config.mapClass);
return maps;
return $(this).find('.' + config.mapClass);
};
/**
@@ -61,8 +60,7 @@ define([
* @returns {*}
*/
$.fn.getActiveMap = function(){
var map = $(this).find('.active.' + config.mapTabContentClass + ' .' + config.mapClass);
let map = $(this).find('.active.' + config.mapTabContentClass + ' .' + config.mapClass);
if(map.length === 0){
map = false;
@@ -92,7 +90,7 @@ define([
* @param tabContentElement
* @param callback
*/
var removeSystemModules = function(tabContentElement, callback){
let removeSystemModules = function(tabContentElement, callback){
tabContentElement.find('.' + config.moduleClass).velocity('transition.slideDownOut', {
duration: Init.animationSpeed.mapModule,
complete: function(tempElement){
@@ -109,12 +107,12 @@ define([
* clears and updates the system info element (signature table, system info,...)
* @param tabContentElement
*/
var drawSystemModules = function(tabContentElement){
var currentSystemData = Util.getCurrentSystemData();
let drawSystemModules = function(tabContentElement){
let currentSystemData = Util.getCurrentSystemData();
// get Table cell for system Info
var firstCell = $(tabContentElement).find('.' + config.mapTabContentCellFirst);
var secondCell = $(tabContentElement).find('.' + config.mapTabContentCellSecond);
let firstCell = $(tabContentElement).find('.' + config.mapTabContentCellFirst);
let secondCell = $(tabContentElement).find('.' + config.mapTabContentCellSecond);
// draw system info module
firstCell.drawSystemInfoModule(currentSystemData.mapId, currentSystemData.systemData);
@@ -138,16 +136,16 @@ define([
/**
* set observer for each module
*/
var setModuleObserver = function(){
let setModuleObserver = function(){
// toggle height for a module
$(document).off('click.toggleModuleHeight').on('click.toggleModuleHeight', '.' + config.moduleClass, function(e){
var moduleElement = $(this);
let moduleElement = $(this);
// get click position
var posX = moduleElement.offset().left;
var posY = moduleElement.offset().top;
var clickX = e.pageX - posX;
var clickY = e.pageY - posY;
let posX = moduleElement.offset().left;
let posY = moduleElement.offset().top;
let clickX = e.pageX - posX;
let clickY = e.pageY - posY;
// check for top-left click
if(clickX <= 8 && clickY <= 8){
@@ -159,7 +157,7 @@ define([
}
if(moduleElement.hasClass( config.moduleClosedClass )){
var moduleHeight = moduleElement.data('origHeight');
let moduleHeight = moduleElement.data('origHeight');
moduleElement.velocity('finish').velocity({
height: [ moduleHeight + 'px', [ 400, 15 ] ]
},{
@@ -191,15 +189,19 @@ define([
* @returns {boolean}
*/
$.fn.updateMapModuleData = function(){
var mapModule = $(this);
let mapModule = $(this);
// performance logging (time measurement)
let logKeyClientUserData = Init.performanceLogging.keyClientUserData;
Util.timeStart(logKeyClientUserData);
// get all active map elements for module
var mapElement = mapModule.getActiveMap();
let mapElement = mapModule.getActiveMap();
if(mapElement !== false){
var mapId = mapElement.data('id');
let mapId = mapElement.data('id');
var currentMapUserData = Util.getCurrentMapUserData(mapId);
let currentMapUserData = Util.getCurrentMapUserData(mapId);
// update map with current user data
if(currentMapUserData){
@@ -207,6 +209,10 @@ define([
}
}
// log client user data update time
let duration = Util.timeStop(logKeyClientUserData);
Util.log(logKeyClientUserData, {duration: duration, type: 'client', description:'update users'});
return true;
};
@@ -215,11 +221,11 @@ define([
* @param systemData
*/
$.fn.updateSystemModuleData = function(systemData){
var mapModule = $(this);
let mapModule = $(this);
if(systemData){
// check if current open system is still the requested info system
var currentSystemData = Util.getCurrentSystemData();
let currentSystemData = Util.getCurrentSystemData();
if(currentSystemData){
if(systemData.id === currentSystemData.systemData.id){
@@ -237,7 +243,7 @@ define([
$.fn.initContentStructure = function(){
return this.each(function(){
// init bootstrap Grid
var contentStructure = $('<div>', {
let contentStructure = $('<div>', {
class: ['row', config.mapTabContentRow].join(' ')
}).append(
$('<div>', {
@@ -259,17 +265,17 @@ define([
* @param options
* @returns {*|jQuery|HTMLElement}
*/
var getTabElement = function(options){
var tabElement = $('<div>', {
let getTabElement = function(options){
let tabElement = $('<div>', {
id: config.mapTabElementId
});
var tabBar = $('<ul>', {
let tabBar = $('<ul>', {
class: ['nav', 'nav-tabs'].join(' '),
id: options.barId
}).attr('role', 'tablist');
var tabContent = $('<div>', {
let tabContent = $('<div>', {
class: 'tab-content'
}).attr('data-map-tabs', options.barId);
@@ -284,7 +290,7 @@ define([
* @param options
*/
$.fn.updateTabData = function(options){
var tabElement = $(this);
let tabElement = $(this);
// set "main" data
tabElement.data('map-id', options.id).data('updated', options.updated);
@@ -293,17 +299,17 @@ define([
tabElement.attr('href', '#' + config.mapTabIdPrefix + options.id);
// change "map" icon
var mapIconElement = tabElement.find('.' + config.mapTabIconClass);
let mapIconElement = tabElement.find('.' + config.mapTabIconClass);
mapIconElement.removeClass().addClass([config.mapTabIconClass, 'fa', 'fa-fw', options.icon].join(' '));
// change "shared" icon
var mapSharedIconElement = tabElement.find('.' + config.mapTabSharedIconClass);
let mapSharedIconElement = tabElement.find('.' + config.mapTabSharedIconClass);
mapSharedIconElement.hide();
// check if the map is a "shared" map
if(options.access){
if(
options.access.user.length > 1 ||
options.access.character.length > 1 ||
options.access.corporation.length > 1 ||
options.access.alliance.length > 1
){
@@ -312,14 +318,14 @@ define([
}
// change map name label
var tabLinkTextElement = tabElement.find('.' + config.mapTabLinkTextClass);
let tabLinkTextElement = tabElement.find('.' + config.mapTabLinkTextClass);
tabLinkTextElement.text(options.name);
// change tabClass
var listElement = tabElement.parent();
let listElement = tabElement.parent();
// new tab classes
var tabClasses = [config.mapTabClass, options.type.classTab ];
let tabClasses = [config.mapTabClass, options.type.classTab ];
// check if tab was "active" before
if( listElement.hasClass('active') ){
@@ -332,7 +338,7 @@ define([
tabLinkTextElement.attr('title', options.type.name + ' map');
}
var mapTooltipOptions = {
let mapTooltipOptions = {
placement: 'bottom',
container: 'body',
trigger: 'hover',
@@ -353,36 +359,36 @@ define([
* @returns {{listElement: (*|void), contentElement: (*|HTMLElement)}}
*/
$.fn.addTab = function(options){
var tabElement = $(this);
var tabBar = tabElement.find('ul.nav-tabs');
var tabContent = tabElement.find('div.tab-content');
let tabElement = $(this);
let tabBar = tabElement.find('ul.nav-tabs');
let tabContent = tabElement.find('div.tab-content');
var listElement = $('<li>').attr('role', 'presentation');
let listElement = $('<li>').attr('role', 'presentation');
if(options.right === true){
listElement.addClass('pull-right');
}
// link element
var linkElement = $('<a>').attr('role', 'tab');
let linkElement = $('<a>').attr('role', 'tab');
// map icon element
var mapIconElement = $('<i>', {
let mapIconElement = $('<i>', {
class: config.mapTabIconClass
});
// map shared icon element
var mapSharedIconElement = $('<i>', {
let mapSharedIconElement = $('<i>', {
class: [config.mapTabSharedIconClass, 'fa', 'fa-fw', 'fa-share-alt'].join(' '),
title: 'shared map'
});
// text element
var textElement = $('<span>', {
let textElement = $('<span>', {
class: config.mapTabLinkTextClass
});
var newListElement = listElement.append(
let newListElement = listElement.append(
linkElement.append(mapIconElement).append(textElement).append(mapSharedIconElement)
);
@@ -392,7 +398,7 @@ define([
linkElement.updateTabData(options);
// tabs content =======================================================
var contentElement = $('<div>', {
let contentElement = $('<div>', {
id: config.mapTabIdPrefix + parseInt( options.id ),
class: [config.mapTabContentClass].join(' ')
});
@@ -414,12 +420,12 @@ define([
}
if(mapTabChangeBlocked === false){
var tabLinkElement = $(this);
var mapId = tabLinkElement.data('map-id');
let tabLinkElement = $(this);
let mapId = tabLinkElement.data('map-id');
// ignore "add" tab. no need for map change
if(mapId > 0){
var mapElement = $('#' + config.mapTabElementId).getActiveMap();
let mapElement = $('#' + config.mapTabElementId).getActiveMap();
if(mapId !== mapElement.data('id')){
// block tabs until switch is done
@@ -451,17 +457,17 @@ define([
* @param mapId
*/
$.fn.deleteTab = function(mapId){
var tabElement = $(this);
var linkElement = tabElement.find('a[href="#' + config.mapTabIdPrefix + mapId + '"]');
var deletedTabName = '';
let tabElement = $(this);
let linkElement = tabElement.find('a[href="#' + config.mapTabIdPrefix + mapId + '"]');
let deletedTabName = '';
if(linkElement.length > 0){
deletedTabName = linkElement.find('.' + config.mapTabLinkTextClass).text();
var liElement = linkElement.parent();
var contentElement = tabElement.find('div[id="' + config.mapTabIdPrefix + mapId + '"]');
let liElement = linkElement.parent();
let contentElement = tabElement.find('div[id="' + config.mapTabIdPrefix + mapId + '"]');
var findNewActiveTab = false;
let findNewActiveTab = false;
// check if liElement was active
if(liElement.hasClass('active')){
// search any remaining li element and set active
@@ -486,15 +492,15 @@ define([
* clear all active maps
*/
$.fn.clearMapModule = function(){
var mapModuleElement = $(this);
var tabMapElement = $('#' + config.mapTabElementId);
let mapModuleElement = $(this);
let tabMapElement = $('#' + config.mapTabElementId);
if(tabMapElement.length > 0){
var tabElements = mapModuleElement.getMapTabElements();
let tabElements = mapModuleElement.getMapTabElements();
for(var i = 0; i < tabElements.length; i++){
var tabElement = $(tabElements[i]);
var mapId = tabElement.data('map-id');
for(let i = 0; i < tabElements.length; i++){
let tabElement = $(tabElements[i]);
let mapId = tabElement.data('map-id');
if(mapId > 0){
tabMapElement.deleteTab(mapId);
@@ -505,43 +511,52 @@ define([
/**
* load/update map module into element (all maps)
* @param mapData
* @returns {boolean}
*/
$.fn.updateMapModule = function(mapData){
if(mapData.length === 0){
return true;
}
$.fn.updateMapModule = function(){
let mapModuleElement = $(this);
// store current map data global (cache)
// temp store current map data to prevent data-change while function execution!
var tempMapData = Util.setCurrentMapData(mapData);
let tempMapData = Util.getCurrentMapData();
var mapModuleElement = $(this);
if(tempMapData.length === 0){
// clear all existing maps
mapModuleElement.clearMapModule();
// no map data available -> show "new map" dialog
$(document).trigger('pf:menuShowMapSettings', {tab: 'new'});
return true;
}
// performance logging (time measurement)
let logKeyClientMapData = Init.performanceLogging.keyClientMapData;
Util.timeStart(logKeyClientMapData);
// check if tabs module is already loaded
var tabMapElement = $('#' + config.mapTabElementId);
let tabMapElement = $('#' + config.mapTabElementId);
// check if tabs have changed
var tabsChanged = false;
let tabsChanged = false;
if(tabMapElement.length > 0){
// tab element already exists
var tabElements = mapModuleElement.getMapTabElements();
let tabElements = mapModuleElement.getMapTabElements();
// map ID that is currently active
var activeMapId = 0;
let activeMapId = 0;
// mapIds that are currently active
var activeMapIds = [];
let activeMapIds = [];
// check whether a tab/map is still active
for(var i = 0; i < tabElements.length; i++){
var tabElement = $(tabElements[i]);
var mapId = tabElement.data('map-id');
for(let i = 0; i < tabElements.length; i++){
let tabElement = $(tabElements[i]);
let mapId = tabElement.data('map-id');
if(mapId > 0){
var tabMapData = Util.getCurrentMapData(mapId);
let tabMapData = Util.getCurrentMapData(mapId);
if(tabMapData !== false){
// map data available ->
@@ -553,7 +568,7 @@ define([
}
}else{
// map data not available -> remove tab
var deletedTabName = tabMapElement.deleteTab(mapId);
let deletedTabName = tabMapElement.deleteTab(mapId);
tabsChanged = true;
@@ -569,7 +584,7 @@ define([
if( activeMapIds.indexOf( data.config.id ) === -1 ){
// add new map tab
var newTabElements = tabMapElement.addTab(data.config);
let newTabElements = tabMapElement.addTab(data.config);
// check if there is any active map yet (this is not the case
// when ALL maps are removed AND new maps are added in one call
@@ -596,11 +611,11 @@ define([
if(activeMapId === 0){
activeMapId = Util.getMapModule().getActiveMap().data('id');
}
var activeMapData = Util.getCurrentMapData(activeMapId);
let activeMapData = Util.getCurrentMapData(activeMapId);
if(activeMapData !== false){
// update active map with new mapData
var currentTabContentElement = $('#' + config.mapTabIdPrefix + activeMapId);
let currentTabContentElement = $('#' + config.mapTabIdPrefix + activeMapId);
$( currentTabContentElement).loadMap( activeMapData, {} );
}
@@ -608,21 +623,21 @@ define([
// create Tab Element
tabsChanged = true;
var options = {
let options = {
barId: config.mapTabBarId
};
tabMapElement = getTabElement(options);
// add new tab for each map
for(var j = 0; j < tempMapData.length; j++){
for(let j = 0; j < tempMapData.length; j++){
var data = tempMapData[j];
let data = tempMapData[j];
tabMapElement.addTab(data.config);
}
// add "add" button
var tabAddOptions = {
let tabAddOptions = {
id: 0,
type: {
classTab: MapUtil.getInfoForMap( 'standard', 'classTab')
@@ -636,14 +651,14 @@ define([
mapModuleElement.prepend(tabMapElement);
var currentUserData = Util.getCurrentUserData();
var promiseStore = MapUtil.getLocaleData('character', currentUserData.character.id);
let currentUserData = Util.getCurrentUserData();
let promiseStore = MapUtil.getLocaleData('character', currentUserData.character.id);
promiseStore.then(function(data) {
// array key where map data is available (0 == first map found)
var mapDataIndex = 0;
let mapDataIndex = 0;
// tab dom selector
var mapKeyTabSelector = 'first';
let mapKeyTabSelector = 'first';
if(
data &&
@@ -656,7 +671,7 @@ define([
// ==============================================================
// this new created module
var tabContentElements = tabMapElement.find('.' + config.mapTabContentClass);
let tabContentElements = tabMapElement.find('.' + config.mapTabContentClass);
// set observer for manually triggered map events
tabContentElements.setTabContentObserver();
@@ -673,7 +688,7 @@ define([
if(tabsChanged === true){
// remove previous event handlers
var allTabElements = mapModuleElement.getMapTabElements();
let allTabElements = mapModuleElement.getMapTabElements();
allTabElements.off('show.bs.tab');
allTabElements.off('shown.bs.tab');
allTabElements.off('hide.bs.tab');
@@ -681,11 +696,11 @@ define([
// check for "new map" action before tap-change
allTabElements.on('show.bs.tab', function (e) {
var mapId = $(e.target).data('map-id');
let mapId = $(e.target).data('map-id');
if(mapId > 0){
// save mapId as new "default" (local storage)
var userData = MapUtil.storeDefaultMapId(mapId);
let userData = MapUtil.storeDefaultMapId(mapId);
}else{
// add new Tab selected
$(document).trigger('pf:menuShowMapSettings', {tab: 'new'});
@@ -695,17 +710,17 @@ define([
// load new map right after tab-change
allTabElements.on('shown.bs.tab', function (e) {
var mapId = $(e.target).data('map-id');
var tabMapData = Util.getCurrentMapData(mapId);
let mapId = $(e.target).data('map-id');
let tabMapData = Util.getCurrentMapData(mapId);
if(tabMapData !== false){
// load map
var currentTabContentElement = $('#' + config.mapTabIdPrefix + mapId);
let currentTabContentElement = $('#' + config.mapTabIdPrefix + mapId);
$( currentTabContentElement).loadMap( tabMapData, {showAnimation: true} );
// "wake up" scrollbar for map and get previous state back
var scrollableElement = currentTabContentElement.find('.' + config.mapWrapperClass);
let scrollableElement = currentTabContentElement.find('.' + config.mapWrapperClass);
$(scrollableElement).mCustomScrollbar( 'update');
}else{
// no map data found -> remove tab
@@ -714,20 +729,24 @@ define([
});
allTabElements.on('hide.bs.tab', function (e) {
var newMapId = $(e.relatedTarget).data('map-id');
var oldMapId = $(e.target).data('map-id');
let newMapId = $(e.relatedTarget).data('map-id');
let oldMapId = $(e.target).data('map-id');
// skip "add button"
if(newMapId > 0){
var currentTabContentElement = $('#' + config.mapTabIdPrefix + oldMapId);
let currentTabContentElement = $('#' + config.mapTabIdPrefix + oldMapId);
// disable scrollbar for map that will be hidden. "freeze" current state
var scrollableElement = currentTabContentElement.find('.' + config.mapWrapperClass);
let scrollableElement = currentTabContentElement.find('.' + config.mapWrapperClass);
$(scrollableElement).mCustomScrollbar( 'disable' );
}
});
}
// log client map update time
let duration = Util.timeStop(logKeyClientMapData);
Util.log(logKeyClientMapData, {duration: duration, type: 'client', description: 'update map'});
return true;
};
@@ -737,14 +756,13 @@ define([
* @returns {Array}
*/
$.fn.getMapModuleDataForUpdate = function(){
// get all active map elements for module
var mapElements = $(this).getMaps();
let mapElements = $(this).getMaps();
var data = [];
for(var i = 0; i < mapElements.length; i++){
let data = [];
for(let i = 0; i < mapElements.length; i++){
// get all changed (system / connection) data from this map
var mapData = $(mapElements[i]).getMapDataFromClient({forceData: false, checkForChange: true});
let mapData = $(mapElements[i]).getMapDataFromClient({forceData: false, checkForChange: true});
if(mapData !== false){

View File

@@ -27,7 +27,7 @@ define([
'use strict';
var config = {
let config = {
// page structure slidebars-menu classes
pageId: 'sb-site',
pageSlidebarClass: 'sb-slidebar',
@@ -56,6 +56,7 @@ define([
// footer
pageFooterId: 'pf-footer', // id for page footer
footerLicenceLinkClass: 'pf-footer-licence', // class for "licence" link
globalInfoPanelId: 'pf-global-info', // id for "global info panel"
// menu
menuHeadMenuLogoClass: 'pf-head-menu-logo', // class for main menu logo
@@ -64,31 +65,33 @@ define([
dynamicElementWrapperId: 'pf-dialog-wrapper'
};
var programStatusCounter = 0; // current count down in s until next status change is possible
var programStatusInterval = false; // interval timer until next status change is possible
let programStatusCounter = 0; // current count down in s until next status change is possible
let programStatusInterval = false; // interval timer until next status change is possible
/**
* load main page structure elements and navigation container into body
* @returns {*|jQuery|HTMLElement}
*/
$.fn.loadPageStructure = function(){
let body = $(this);
// menu left
$(this).prepend(
body.prepend(
$('<div>', {
class: [config.pageSlidebarClass, config.pageSlidebarLeftClass, 'sb-style-push', 'sb-width-custom'].join(' ')
}).attr('data-sb-width', config.pageSlideLeftWidth)
);
// menu right
$(this).prepend(
body.prepend(
$('<div>', {
class: [config.pageSlidebarClass, config.pageSlidebarRightClass, 'sb-style-push', 'sb-width-custom'].join(' ')
}).attr('data-sb-width', config.pageSlideRightWidth)
);
// main page
$(this).prepend(
body.prepend(
$('<div>', {
id: config.pageId,
class: config.pageClass
@@ -112,6 +115,8 @@ define([
// set document observer for global events
setDocumentObserver();
return body;
};
/**
@@ -119,7 +124,7 @@ define([
* @param title
* @returns {JQuery|*|jQuery}
*/
var getMenuHeadline = function(title){
let getMenuHeadline = function(title){
return $('<div>', {
class: 'panel-heading'
}).prepend(
@@ -211,7 +216,7 @@ define([
css: {width: '1.23em'}
})
).on('click', function(){
var fullScreenElement = $('body');
let fullScreenElement = $('body');
requirejs(['jquery', 'fullScreen'], function($) {
if($.fullscreen.isFullScreen()){
@@ -385,10 +390,9 @@ define([
* load page header
*/
$.fn.loadHeader = function(){
let pageElement = $(this);
var pageElement = $(this);
var moduleData = {
let moduleData = {
id: config.pageHeaderId,
logo: function(){
// render svg logo
@@ -403,14 +407,14 @@ define([
mapTrackingId: Util.config.headMapTrackingId
};
var headRendered = Mustache.render(TplHead, moduleData);
let headRendered = Mustache.render(TplHead, moduleData);
pageElement.prepend(headRendered);
// init header =====================================================================
// init slide menus
var slideMenu = new $.slidebars({
let slideMenu = new $.slidebars({
scrollLock: false
});
@@ -445,7 +449,7 @@ define([
});
// tracking toggle
var mapTrackingCheckbox = $('#' + Util.config.headMapTrackingId);
let mapTrackingCheckbox = $('#' + Util.config.headMapTrackingId);
mapTrackingCheckbox.bootstrapToggle({
size: 'mini',
on: 'on',
@@ -461,10 +465,10 @@ define([
mapTrackingCheckbox.bootstrapToggle('on');
mapTrackingCheckbox.on('change', function(e) {
var value = $(this).is(':checked');
var tracking = 'off';
var trackingText = 'Your current location will not actually be added';
var trackingType = 'info';
let value = $(this).is(':checked');
let tracking = 'off';
let trackingText = 'Your current location will not actually be added';
let trackingType = 'info';
if(value){
tracking = 'on';
trackingText = 'New connections will actually be added';
@@ -476,7 +480,7 @@ define([
// init all tooltips
var tooltipElements = $('#' + config.pageHeaderId).find('[title]');
let tooltipElements = $('#' + config.pageHeaderId).find('[title]');
tooltipElements.tooltip({
placement: 'bottom',
delay: {
@@ -492,15 +496,15 @@ define([
* load page footer
*/
$.fn.loadFooter = function(){
var pageElement = $(this);
let pageElement = $(this);
var moduleData = {
let moduleData = {
id: config.pageFooterId,
footerLicenceLinkClass: config.footerLicenceLinkClass,
currentYear: new Date().getFullYear()
};
var headRendered = Mustache.render(TplFooter, moduleData);
let headRendered = Mustache.render(TplFooter, moduleData);
pageElement.prepend(headRendered);
@@ -517,12 +521,12 @@ define([
/**
* catch all global document events
*/
var setDocumentObserver = function(){
let setDocumentObserver = function(){
// on "full-screen" change event
$(document).on('fscreenchange', function(e, state, elem){
var menuButton = $('#' + Util.config.menuButtonFullScreenId);
let menuButton = $('#' + Util.config.menuButtonFullScreenId);
if(state === true){
// full screen active
@@ -586,9 +590,9 @@ define([
$(document).on('pf:menuShowMapSettings', function(e, data){
// show map edit dialog or edit map
var mapData = false;
let mapData = false;
var activeMap = Util.getMapModule().getActiveMap();
let activeMap = Util.getMapModule().getActiveMap();
if(activeMap){
mapData = Util.getCurrentMapData( activeMap.data('id') );
@@ -600,9 +604,9 @@ define([
$(document).on('pf:menuDeleteMap', function(e){
// delete current active map
var mapData = false;
let mapData = false;
var activeMap = Util.getMapModule().getActiveMap();
let activeMap = Util.getMapModule().getActiveMap();
if(activeMap){
mapData = activeMap.getMapDataFromClient({forceData: true});
@@ -620,7 +624,7 @@ define([
$(document).on('pf:menuLogout', function(e, data){
var clearCookies = false;
let clearCookies = false;
if(
typeof data === 'object' &&
data.hasOwnProperty('clearCookies')
@@ -642,10 +646,10 @@ define([
// update header links with current map data
$(document).on('pf:updateHeaderMapData', function(e, data){
var activeMap = Util.getMapModule().getActiveMap();
let activeMap = Util.getMapModule().getActiveMap();
var userCount = 0;
var currentLocationData = {};
let userCount = 0;
let currentLocationData = {};
// show active user just for the current active map
if(
@@ -662,7 +666,7 @@ define([
// shutdown the program -> show dialog
$(document).on('pf:shutdown', function(e, data){
// show shutdown dialog
var options = {
let options = {
buttons: {
logout: {
label: '<i class="fa fa-fw fa-refresh"></i> restart',
@@ -724,21 +728,21 @@ define([
* updates the header with current user data
*/
$.fn.updateHeaderUserData = function(){
var userData = Util.getCurrentUserData();
let userData = Util.getCurrentUserData();
var userInfoElement = $('.' + config.headUserCharacterClass);
var currentCharacterId = userInfoElement.data('characterId');
var currentCharactersOptionIds = userInfoElement.data('characterOptionIds') ? userInfoElement.data('characterOptionIds') : [];
var newCharacterId = 0;
var newCharacterName = '';
let userInfoElement = $('.' + config.headUserCharacterClass);
let currentCharacterId = userInfoElement.data('characterId');
let currentCharactersOptionIds = userInfoElement.data('characterOptionIds') ? userInfoElement.data('characterOptionIds') : [];
let newCharacterId = 0;
let newCharacterName = '';
var userShipElement = $('.' + config.headUserShipClass);
var currentShipId = userShipElement.data('shipId');
var newShipId = 0;
var newShipName = '';
let userShipElement = $('.' + config.headUserShipClass);
let currentShipId = userShipElement.data('shipId');
let newShipId = 0;
let newShipName = '';
// function for header element toggle animation
var animateHeaderElement = function(element, callback, triggerShow){
let animateHeaderElement = function(element, callback, triggerShow){
element.show().velocity('stop').velocity({
opacity: 0
@@ -784,14 +788,14 @@ define([
updateMapTrackingToggle(userData.character.logLocation);
}
var newCharactersOptionIds = userData.characters.map(function(data){
let newCharactersOptionIds = userData.characters.map(function(data){
return data.id;
});
// update user character data ---------------------------------------------------
if(currentCharactersOptionIds.toString() !== newCharactersOptionIds.toString()){
var currentCharacterChanged = false;
let currentCharacterChanged = false;
if(currentCharacterId !== newCharacterId){
currentCharacterChanged = true;
}
@@ -814,7 +818,7 @@ define([
// update user ship data --------------------------------------------------------
if(currentShipId !== newShipId){
var showShipElement = true;
let showShipElement = true;
if(newShipId === 0){
showShipElement = false;
}
@@ -834,8 +838,8 @@ define([
* update "map tracking" toggle in header
* @param status
*/
var updateMapTrackingToggle = function(status){
var mapTrackingCheckbox = $('#' + Util.config.headMapTrackingId);
let updateMapTrackingToggle = function(status){
let mapTrackingCheckbox = $('#' + Util.config.headMapTrackingId);
if(status === true){
mapTrackingCheckbox.bootstrapToggle('enable');
}else{
@@ -846,7 +850,7 @@ define([
/**
* delete active character log for the current user
*/
var deleteLog = function(){
let deleteLog = function(){
$.ajax({
type: 'POST',
@@ -862,9 +866,9 @@ define([
* update the "active user" badge in header
* @param userCount
*/
var updateHeaderActiveUserCount = function(userCount){
var activeUserElement = $('.' + config.headActiveUserClass);
var badge = activeUserElement.find('.badge');
let updateHeaderActiveUserCount = function(userCount){
let activeUserElement = $('.' + config.headActiveUserClass);
let badge = activeUserElement.find('.badge');
if(badge.data('userCount') !== userCount){
badge.data('userCount', userCount);
@@ -884,13 +888,13 @@ define([
* update the "current location" element in head
* @param locationData
*/
var updateHeaderCurrentLocation = function(locationData){
var currentLocationElement = $('#' + Util.config.headCurrentLocationId);
var linkElement = currentLocationElement.find('a');
var textElement = linkElement.find('span');
let updateHeaderCurrentLocation = function(locationData){
let currentLocationElement = $('#' + Util.config.headCurrentLocationId);
let linkElement = currentLocationElement.find('a');
let textElement = linkElement.find('span');
var tempSystemName = (locationData.currentSystemName) ? locationData.currentSystemName : false;
var tempSystemId = (locationData.currentSystemId) ? locationData.currentSystemId : 0;
let tempSystemName = (locationData.currentSystemName) ? locationData.currentSystemName : false;
let tempSystemId = (locationData.currentSystemId) ? locationData.currentSystemId : 0;
if(
linkElement.data('systemName') !== tempSystemName ||
@@ -915,7 +919,7 @@ define([
/**
* shows a test notification for desktop messages
*/
var notificationTest = function(){
let notificationTest = function(){
Util.showNotify({
title: 'Test Notification',
text: 'Accept browser security question'},
@@ -930,17 +934,17 @@ define([
* set event listener if the program tab is active or not
* this is used to lower the update ping cycle to reduce server load
*/
var initTabChangeObserver = function(){
let initTabChangeObserver = function(){
// increase the timer if a user is inactive
var increaseTimer = 10000;
let increaseTimer = 10000;
// timer keys
var mapUpdateKey = 'UPDATE_SERVER_MAP';
var mapUserUpdateKey = 'UPDATE_SERVER_USER_DATA';
let mapUpdateKey = 'UPDATE_SERVER_MAP';
let mapUserUpdateKey = 'UPDATE_SERVER_USER_DATA';
// Set the name of the hidden property and the change event for visibility
var hidden, visibilityChange;
let hidden, visibilityChange;
if (typeof document.hidden !== 'undefined') { // Opera 12.10 and Firefox 18 and later support
hidden = 'hidden';
visibilityChange = 'visibilitychange';
@@ -997,91 +1001,97 @@ define([
* @param status
*/
$.fn.setProgramStatus = function(status){
var statusElement = $('.' + config.headProgramStatusClass);
var icon = statusElement.find('i');
var textElement = statusElement.find('span');
let statusElement = $('.' + config.headProgramStatusClass);
let icon = statusElement.find('i');
let textElement = statusElement.find('span');
var iconClass = false;
var textClass = false;
var text = '';
let iconClass = false;
let textClass = false;
switch(status){
case 'online':
if( ! statusElement.hasClass('txt-color-green')){
iconClass = 'fa-wifi';
textClass = 'txt-color-green';
text = 'online';
}
iconClass = 'fa-wifi';
textClass = 'txt-color-green';
break;
case 'slow connection':
case 'problem':
if( ! statusElement.hasClass('txt-color-orange')){
iconClass = 'fa-warning';
textClass = 'txt-color-orange';
text = 'problem';
}
iconClass = 'fa-warning';
textClass = 'txt-color-orange';
break;
case 'offline':
if( ! statusElement.hasClass('txt-color-red')){
iconClass = 'fa-bolt';
textClass = 'txt-color-red';
text = 'offline';
}
iconClass = 'fa-bolt';
textClass = 'txt-color-red';
break;
}
// change status, on status changed
if(iconClass !== false){
// "warnings" and "errors" always have priority -> ignore/clear interval
if(
textClass === 'txt-color-orange' ||
textClass === 'txt-color-red'
){
clearInterval(programStatusInterval);
programStatusInterval = false;
}
// "problem" and "offline" always have priority -> ignore/clear interval
if(
status === 'problem' ||
status === 'offline'
){
clearInterval(programStatusInterval);
programStatusInterval = false;
}
if( statusElement.data('status') !== status ){
// status has changed
if(! programStatusInterval){
if(! statusElement.hasClass(textClass) ){
let timer = function(){
// change status on first timer iteration
if(programStatusCounter === Init.timer.PROGRAM_STATUS_VISIBLE){
statusElement.velocity('stop').velocity('fadeOut', {
duration: Init.animationSpeed.headerLink,
complete: function(){
// store current status
statusElement.data('status', status);
statusElement.removeClass('txt-color-green txt-color-orange txt-color-red');
icon.removeClass('fa-wifi fa-warning fa-bolt');
statusElement.addClass(textClass);
icon.addClass(iconClass);
textElement.text(status);
}
}).velocity('fadeIn', {
duration: Init.animationSpeed.headerLink
});
}
// decrement counter
programStatusCounter -= 1000;
if(programStatusCounter <= 0){
clearInterval(programStatusInterval);
programStatusInterval = false;
}
};
if(! programStatusInterval){
var timer = function(){
// change status on first timer iteration
if(programStatusCounter === Init.timer.PROGRAM_STATUS_VISIBLE){
statusElement.velocity('stop').velocity('fadeOut', {
duration: Init.animationSpeed.headerLink,
complete: function(){
statusElement.removeClass('txt-color-green txt-color-orange txt-color-red');
icon.removeClass('fa-wifi fa-warning fa-bolt');
statusElement.addClass(textClass);
icon.addClass(iconClass);
textElement.text(text);
}
}).velocity('fadeIn', {
duration: Init.animationSpeed.headerLink
});
}
// decrement counter
programStatusCounter -= 1000;
if(programStatusCounter <= 0){
clearInterval(programStatusInterval);
programStatusInterval = false;
}
};
if(! programStatusInterval){
programStatusCounter = Init.timer.PROGRAM_STATUS_VISIBLE;
programStatusInterval = setInterval(timer, 1000);
}
programStatusCounter = Init.timer.PROGRAM_STATUS_VISIBLE;
programStatusInterval = setInterval(timer, 1000);
}
}
}
};
/**
* show information panel to active users (on bottom)
* @returns {*|jQuery|HTMLElement}
*/
$.fn.showGlobalInfoPanel = function (){
let body = $(this);
let infoTemplate = 'text!templates/ui/info_panel.html';
requirejs([infoTemplate, 'mustache'], function(template, Mustache) {
let data = {
id: config.globalInfoPanelId
};
let content = $( Mustache.render(template, data) );
content.insertBefore( '#' + config.pageFooterId );
});
return body;
};
return {
initTabChangeObserver: initTabChangeObserver

View File

@@ -5,19 +5,20 @@
define([
'jquery',
'app/init',
'app/util'
], function($, Init, Util) {
'app/util',
'app/map/worker'
], function($, Init, Util, MapWorker) {
'use strict';
var config = {
let config = {
splashOverlayClass: 'pf-splash' // class for "splash" overlay
};
/**
* set page observer
*/
var setPageObserver = function(){
var body = $('body');
let setPageObserver = () => {
let body = $('body');
// collapse ---------------------------------------
body.find('[data-toggle="collapse"]').css({cursor: 'pointer'}).on('click', function(){
@@ -39,10 +40,127 @@ define([
}
};
/**
* perform a basic check if Clients (browser) can connect to the webSocket server
*/
let testWebSocket = () => {
let tcpSocketPanel = $('#pf-setup-tcpSocket');
let webSocketPanel = $('#pf-setup-webSocket');
let WebSocketURI = MapWorker.getWebSocketURL();
webSocketPanel.showLoadingAnimation();
let removeColorClasses = (el) => {
el.removeClass (function (index, css) {
return (css.match (/\btxt-color-\S+/g) || []).join(' ');
});
};
/**
* updates the WebSocket panel with new data
* @param data
*/
let updateWebSocketPanel = (data) => {
if(data.uri){
let uriRow = webSocketPanel.find('.panel-body table tr');
uriRow.find('td:nth-child(2) kbd').text(data.uri.value);
if(data.uri.status){
let statusIcon = uriRow.find('td:nth-child(3) i');
removeColorClasses(statusIcon);
statusIcon.toggleClass('fa-warning', false).toggleClass('fa-check', true).addClass('txt-color-success');
}
}
if(data.status){
let footer = webSocketPanel.find('.panel-footer h3');
removeColorClasses(footer);
footer.text(data.status.label).addClass(data.status.class);
}
};
// update initial
updateWebSocketPanel({
uri: {
value: WebSocketURI,
status: true
},
status: {
label: 'CONNECTING...',
class: 'txt-color-warning'
}
});
// try to connect to WebSocket server
let socket = new WebSocket(WebSocketURI);
socket.onopen = (e) => {
updateWebSocketPanel({
status: {
label: 'OPEN wait for response...',
class: 'txt-color-warning'
}
});
// sent token and check response
socket.send(JSON.stringify({
task: 'healthCheck',
load: tcpSocketPanel.data('token')
}));
webSocketPanel.hideLoadingAnimation();
};
socket.onmessage = (e) => {
let response = JSON.parse(e.data);
if(response === 1){
// SUCCESS
updateWebSocketPanel({
status: {
label: 'CONNECTED',
class: 'txt-color-success'
}
});
}else{
// Got response but INVALID
updateWebSocketPanel({
status: {
label: 'INVALID RESPONSE',
class: 'txt-color-warning'
}
});
}
};
socket.onerror = (e) => {
updateWebSocketPanel({
status: {
label: 'CONNECTION ERROR',
class: 'txt-color-danger'
}
});
webSocketPanel.hideLoadingAnimation();
};
socket.onclose = (closeEvent) => {
updateWebSocketPanel({
status: {
label: 'CONNECTION FAILED',
class: 'txt-color-danger'
}
});
webSocketPanel.hideLoadingAnimation();
};
};
/**
* main init "setup" page
*/
$(function(){
$(() => {
// show app information in browser console --------
Util.showVersionInfo();
@@ -51,5 +169,7 @@ define([
$('.' + config.splashOverlayClass).hideSplashOverlay();
setPageObserver();
testWebSocket();
});
});

View File

@@ -12,7 +12,7 @@ define([
'use strict';
var config = {
let config = {
// global dialog
dialogNavigationClass: 'pf-dialog-navigation-list', // class for dialog navigation bar
@@ -46,7 +46,7 @@ define([
};
// confirmation dialog settings (e.g. delete row)
var confirmationSettings = {
let confirmationSettings = {
container: 'body',
placement: 'left',
btnCancelClass: 'btn btn-sm btn-default',
@@ -62,28 +62,32 @@ define([
* @param mapData
*/
$.fn.loadMapInfoData = function(mapData){
var mapElement = $(this);
let mapElement = $(this);
mapElement.empty();
mapElement.showLoadingAnimation(config.loadingOptions);
var countSystems = mapData.data.systems.length;
var countConnections = mapData.data.connections.length;
let countSystems = mapData.data.systems.length;
let countConnections = mapData.data.connections.length;
// map type
var mapTypes = MapUtil.getMapTypes();
var mapTypeName = '';
var mapTypeClass = '';
for(var i = 0; i < mapTypes.length; i++){
let mapTypes = MapUtil.getMapTypes();
let mapType = null;
for(let i = 0; i < mapTypes.length; i++){
if(mapTypes[i].id === mapData.config.type.id){
mapTypeName = mapTypes[i].name;
mapTypeClass = mapTypes[i].class;
mapType = mapTypes[i];
break;
}
}
var dlElementLeft = $('<dl>', {
// check max map limits (e.g. max systems per map) ============================================================
let percentageSystems = (100 / mapType.defaultConfig.max_systems) * countSystems;
let maxSystemsClass = (percentageSystems < 90) ? 'txt-color-success' : (percentageSystems < 100) ? 'txt-color-warning' : 'txt-color-danger';
// build content ==============================================================================================
let dlElementLeft = $('<dl>', {
class: 'dl-horizontal',
css: {'float': 'left'}
}).append(
@@ -102,13 +106,13 @@ define([
$('<dt>').text( 'Type' )
).append(
$('<dd>', {
class: mapTypeClass
}).text( mapTypeName )
class: mapType.class
}).text( mapType.name )
);
mapElement.append(dlElementLeft);
var dlElementRight = $('<dl>', {
let dlElementRight = $('<dl>', {
class: 'dl-horizontal',
css: {'float': 'right'}
}).append(
@@ -121,7 +125,9 @@ define([
).append(
$('<dt>').text( 'Systems' )
).append(
$('<dd>').text( countSystems )
$('<dd>', {
class: ['txt-color', maxSystemsClass].join(' ')
}).text( countSystems + ' / ' + mapType.defaultConfig.max_systems )
).append(
$('<dt>').text( 'Connections' )
).append(
@@ -133,10 +139,7 @@ define([
// init map lifetime counter
$('.' + config.mapInfoLifetimeCounterClass).initTimestampCounter();
mapElement.hideLoadingAnimation();
};
/**
@@ -145,11 +148,11 @@ define([
*/
$.fn.loadSystemInfoTable = function(mapData){
var systemsElement = $(this);
let systemsElement = $(this);
systemsElement.empty();
var systemTable = $('<table>', {
let systemTable = $('<table>', {
class: ['compact', 'stripe', 'order-column', 'row-border', config.mapInfoTableClass].join(' ')
});
systemsElement.append(systemTable);
@@ -161,16 +164,16 @@ define([
systemsElement.hideLoadingAnimation();
// init table tooltips
var tooltipElements = systemsElement.find('[data-toggle="tooltip"]');
let tooltipElements = systemsElement.find('[data-toggle="tooltip"]');
tooltipElements.tooltip();
});
// prepare data for dataTables
var systemsData = [];
for(var i = 0; i < mapData.data.systems.length; i++){
var tempSystemData = mapData.data.systems[i];
let systemsData = [];
for(let i = 0; i < mapData.data.systems.length; i++){
let tempSystemData = mapData.data.systems[i];
var tempData = {};
let tempData = {};
// system id
tempData.id = tempSystemData.id;
@@ -202,7 +205,7 @@ define([
};
// security
var securityClass = Util.getSecurityClassForSystem(tempSystemData.security);
let securityClass = Util.getSecurityClassForSystem(tempSystemData.security);
tempData.security = {
security: '<span class="' + securityClass + '">' + tempSystemData.security + '</span>',
security_sort: tempSystemData.security
@@ -218,16 +221,16 @@ define([
tempData.region = tempSystemData.region.name;
// static
var statics = [];
for(var j = 0; j < tempSystemData.statics.length; j++){
var security = tempSystemData.statics[j].security;
var secClass = Util.getSecurityClassForSystem(security);
let statics = [];
for(let j = 0; j < tempSystemData.statics.length; j++){
let security = tempSystemData.statics[j].security;
let secClass = Util.getSecurityClassForSystem(security);
statics.push('<span class="' + secClass + '">' + security + '</span>');
}
tempData.static = statics.join('&nbsp;&nbsp;');
// status
var systemStatusClass = Util.getStatusInfoForSystem(tempSystemData.status.id, 'class');
let systemStatusClass = Util.getStatusInfoForSystem(tempSystemData.status.id, 'class');
if(systemStatusClass !== ''){
tempData.status = {
status: '<i class="fa fa fa-square-o fa-lg fa-fw ' + systemStatusClass + '"></i>',
@@ -241,7 +244,7 @@ define([
}
// effect
var systemEffectClass = MapUtil.getEffectInfoForSystem(tempSystemData.effect, 'class');
let systemEffectClass = MapUtil.getEffectInfoForSystem(tempSystemData.effect, 'class');
if(systemEffectClass !== ''){
tempData.effect = {
effect: '<i class="fa fa fa-square fa-lg fa-fw ' + systemEffectClass + '"></i>',
@@ -255,7 +258,7 @@ define([
}
// trueSec
var systemTrueSecClass = Util.getTrueSecClassForSystem(tempSystemData.trueSec);
let systemTrueSecClass = Util.getTrueSecClassForSystem(tempSystemData.trueSec);
if(systemTrueSecClass !== ''){
tempData.trueSec = {
trueSec: '<span class="' + systemTrueSecClass + '">' + tempSystemData.trueSec.toFixed(1) + '</span>',
@@ -290,7 +293,7 @@ define([
systemsData.push(tempData);
}
var systemsDataTable = systemTable.dataTable( {
let systemsDataTable = systemTable.dataTable( {
pageLength: 20,
paging: true,
lengthMenu: [[5, 10, 20, 50, -1], [5, 10, 20, 50, 'All']],
@@ -405,8 +408,8 @@ define([
$(cell).initTimestampCounter();
// highlight cell
var diff = new Date().getTime() - cellData * 1000;
var dateDiff = new Date(diff);
let diff = new Date().getTime() - cellData * 1000;
let dateDiff = new Date(diff);
if(dateDiff.getUTCDate() > 1){
$(cell).addClass('txt-color txt-color-warning');
}
@@ -419,15 +422,15 @@ define([
className: ['text-center', config.tableActionCellClass].join(' '),
data: 'clear',
createdCell: function(cell, cellData, rowData, rowIndex, colIndex) {
var tempTableElement = this;
let tempTableElement = this;
var tempConfirmationSettings = confirmationSettings;
let tempConfirmationSettings = confirmationSettings;
tempConfirmationSettings.title = 'Delete system';
tempConfirmationSettings.onConfirm = function(e, target){
var deleteRowElement = $(target).parents('tr');
let deleteRowElement = $(target).parents('tr');
var activeMap = Util.getMapModule().getActiveMap();
var systemElement = $('#' + config.systemIdPrefix + mapData.config.id + '-' + rowData.id);
let activeMap = Util.getMapModule().getActiveMap();
let systemElement = $('#' + config.systemIdPrefix + mapData.config.id + '-' + rowData.id);
if(systemElement){
// trigger system delete event
@@ -440,9 +443,9 @@ define([
Util.showNotify({title: 'System deleted', text: rowData.name, type: 'success'});
// refresh connection table (connections might have changed) ------------------------------
var connectionsElement = $('#' + config.mapInfoConnectionsId);
var mapDataNew = activeMap.getMapDataFromClient({forceData: true});
// refresh connection table (connections might have changed) ==================
let connectionsElement = $('#' + config.mapInfoConnectionsId);
let mapDataNew = activeMap.getMapDataFromClient({forceData: true});
connectionsElement.loadConnectionInfoTable(mapDataNew);
}
@@ -465,11 +468,11 @@ define([
* @param mapData
*/
$.fn.loadConnectionInfoTable = function(mapData){
var connectionsElement = $(this);
let connectionsElement = $(this);
connectionsElement.empty();
var connectionTable = $('<table>', {
let connectionTable = $('<table>', {
class: ['compact', 'stripe', 'order-column', 'row-border', config.mapInfoTableClass].join(' ')
});
connectionsElement.append(connectionTable);
@@ -481,14 +484,14 @@ define([
connectionsElement.hideLoadingAnimation();
});
// connections table ==================================================
// connections table ==========================================================================================
// prepare data for dataTables
var connectionData = [];
for(var j = 0; j < mapData.data.connections.length; j++){
var tempConnectionData = mapData.data.connections[j];
let connectionData = [];
for(let j = 0; j < mapData.data.connections.length; j++){
let tempConnectionData = mapData.data.connections[j];
var tempConData = {};
let tempConData = {};
tempConData.id = tempConnectionData.id;
@@ -501,8 +504,8 @@ define([
tempConData.source = tempConnectionData.sourceName;
// connection
var connectionClasses = [];
for(var k = 0; k < tempConnectionData.type.length; k++){
let connectionClasses = [];
for(let k = 0; k < tempConnectionData.type.length; k++){
connectionClasses.push( MapUtil.getConnectionInfo( tempConnectionData.type[k], 'cssClass') );
}
@@ -521,7 +524,7 @@ define([
connectionData.push(tempConData);
}
var connectionDataTable = connectionTable.dataTable( {
let connectionDataTable = connectionTable.dataTable( {
pageLength: 20,
paging: true,
lengthMenu: [[5, 10, 20, 50, -1], [5, 10, 20, 50, 'All']],
@@ -570,8 +573,8 @@ define([
$(cell).initTimestampCounter();
// highlight cell
var diff = new Date().getTime() - cellData * 1000;
var dateDiff = new Date(diff);
let diff = new Date().getTime() - cellData * 1000;
let dateDiff = new Date(diff);
if(dateDiff.getUTCDate() > 1){
$(cell).addClass('txt-color txt-color-warning');
}
@@ -584,15 +587,15 @@ define([
className: ['text-center', config.tableActionCellClass].join(' '),
data: 'clear',
createdCell: function(cell, cellData, rowData, rowIndex, colIndex) {
var tempTableElement = this;
let tempTableElement = this;
var tempConfirmationSettings = confirmationSettings;
let tempConfirmationSettings = confirmationSettings;
tempConfirmationSettings.title = 'Delete connection';
tempConfirmationSettings.onConfirm = function(e, target){
var deleteRowElement = $(target).parents('tr');
let deleteRowElement = $(target).parents('tr');
// deleteSignatures(row);
var connection = $().getConnectionById(mapData.config.id, rowData.id);
let connection = $().getConnectionById(mapData.config.id, rowData.id);
$().deleteConnections([connection], function(){
// callback function after ajax "delete" success
@@ -610,11 +613,11 @@ define([
};
$.fn.loadUsersInfoTable = function(mapData){
var usersElement = $(this);
let usersElement = $(this);
usersElement.empty();
var userTable = $('<table>', {
let userTable = $('<table>', {
class: ['compact', 'stripe', 'order-column', 'row-border', config.mapInfoTableClass].join(' ')
});
usersElement.append(userTable);
@@ -626,25 +629,25 @@ define([
usersElement.hideLoadingAnimation();
});
// users table ========================================================
// users table ================================================================================================
// prepare users data for dataTables
var currentMapUserData = Util.getCurrentMapUserData( mapData.config.id );
var usersData = [];
let currentMapUserData = Util.getCurrentMapUserData( mapData.config.id );
let usersData = [];
if(
currentMapUserData &&
currentMapUserData.data &&
currentMapUserData.data.systems
){
for(var i = 0; i < currentMapUserData.data.systems.length; i++){
var tempSystemUserData = currentMapUserData.data.systems[i];
for(var j = 0; j < tempSystemUserData.user.length; j++){
for(let i = 0; i < currentMapUserData.data.systems.length; i++){
let tempSystemUserData = currentMapUserData.data.systems[i];
for(let j = 0; j < tempSystemUserData.user.length; j++){
usersData.push( tempSystemUserData.user[j] );
}
}
}
var userDataTable = userTable.dataTable( {
let userDataTable = userTable.dataTable( {
pageLength: 20,
paging: true,
lengthMenu: [[5, 10, 20, 50, -1], [5, 10, 20, 50, 'All']],
@@ -755,13 +758,13 @@ define([
*/
$.fn.showMapInfoDialog = function(){
var activeMap = Util.getMapModule().getActiveMap();
var mapData = activeMap.getMapDataFromClient({forceData: true});
let activeMap = Util.getMapModule().getActiveMap();
let mapData = activeMap.getMapDataFromClient({forceData: true});
if(mapData !== false){
requirejs(['text!templates/dialog/map_info.html', 'mustache'], function(template, Mustache) {
var data = {
let data = {
dialogSummaryContainerId: config.dialogMapInfoSummaryId,
dialogUsersContainerId: config.dialogMapInfoUsersId,
dialogRefreshContainerId: config.dialogMapInfoRefreshId,
@@ -772,9 +775,9 @@ define([
mapInfoUsersId: config.mapInfoUsersId
};
var content = Mustache.render(template, data);
let content = Mustache.render(template, data);
var mapInfoDialog = bootbox.dialog({
let mapInfoDialog = bootbox.dialog({
title: 'Map information',
message: content,
size: 'large',
@@ -792,18 +795,18 @@ define([
mapInfoDialog.on('shown.bs.modal', function(e) {
// modal on open
var mapElement = $('#' + config.mapInfoId);
var systemsElement = $('#' + config.mapInfoSystemsId);
var connectionsElement = $('#' + config.mapInfoConnectionsId);
var usersElement = $('#' + config.mapInfoUsersId);
let mapElement = $('#' + config.mapInfoId);
let systemsElement = $('#' + config.mapInfoSystemsId);
let connectionsElement = $('#' + config.mapInfoConnectionsId);
let usersElement = $('#' + config.mapInfoUsersId);
// set refresh button observer
$('#' + config.dialogMapInfoRefreshId).on('click', function(){
var menuAction = $(this).attr('data-action');
let menuAction = $(this).attr('data-action');
if(menuAction === 'refresh'){
// get new map data
var mapData = activeMap.getMapDataFromClient({forceData: true});
let mapData = activeMap.getMapDataFromClient({forceData: true});
mapElement.loadMapInfoData(mapData);
systemsElement.loadSystemInfoTable(mapData);

View File

@@ -12,7 +12,7 @@ define([
], function($, Init, Util, Render, bootbox, MapUtil) {
'use strict';
var config = {
let config = {
// map dialog
newMapDialogId: 'pf-map-dialog', // id for map settings dialog
dialogMapCreateContainerId: 'pf-map-dialog-create', // id for the "new map" container
@@ -43,11 +43,11 @@ define([
* @param filename
* @returns {string}
*/
var formatFilename = function(filename){
let formatFilename = function(filename){
filename = filename.replace(/[^a-zA-Z0-9]/g,'_');
var nowDate = new Date();
var filenameDate = nowDate.toISOString().slice(0,10).replace(/-/g, '_');
let nowDate = new Date();
let filenameDate = nowDate.toISOString().slice(0,10).replace(/-/g, '_');
return (filename + '_' + filenameDate).replace(/__/g,'_');
};
@@ -60,7 +60,7 @@ define([
$.fn.showMapSettingsDialog = function(mapData, options){
// check if dialog is already open
var mapInfoDialogElement = $('#' + config.newMapDialogId);
let mapInfoDialogElement = $('#' + config.newMapDialogId);
if(!mapInfoDialogElement.is(':visible')){
requirejs([
@@ -69,12 +69,12 @@ define([
'mustache'
], function(templateMapDialog, templateMapSettings, Mustache) {
var dialogTitle = 'Map settings';
let dialogTitle = 'Map settings';
// if there are no maps -> hide settings tab
var hideSettingsTab = false;
var hideEditTab = false;
var hideDownloadTab = false;
let hideSettingsTab = false;
let hideEditTab = false;
let hideDownloadTab = false;
if(mapData === false){
hideSettingsTab = true;
@@ -83,9 +83,9 @@ define([
}
// available map "types" for a new or existing map
var mapTypes = MapUtil.getMapTypes(true);
let mapTypes = MapUtil.getMapTypes(true);
var data = {
let data = {
scope: MapUtil.getMapScopes(),
type: mapTypes,
icon: MapUtil.getMapIcons(),
@@ -95,17 +95,17 @@ define([
};
// render "new map" tab content -------------------------------------------
var contentNewMap = Mustache.render(templateMapSettings, data);
let contentNewMap = Mustache.render(templateMapSettings, data);
// render "edit map" tab content ------------------------------------------
var contentEditMap = Mustache.render(templateMapSettings, data);
let contentEditMap = Mustache.render(templateMapSettings, data);
contentEditMap = $(contentEditMap);
// current map access info
var accessCharacter = [];
var accessCorporation = [];
var accessAlliance = [];
var deleteExpiredConnections = true;
let accessCharacter = [];
let accessCorporation = [];
let accessAlliance = [];
let deleteExpiredConnections = true;
if(mapData !== false){
// set current map information
@@ -162,9 +162,9 @@ define([
accessAlliance: accessAlliance,
// access limitations --------
maxCharacter: Init.maxSharedCount.character,
maxCorporation: Init.maxSharedCount.corporation,
maxAlliance: Init.maxSharedCount.alliance,
maxCharacter: Init.mapTypes.private.defaultConfig.max_shared,
maxCorporation: Init.mapTypes.corporation.defaultConfig.max_shared,
maxAlliance: Init.mapTypes.alliance.defaultConfig.max_shared,
// download tab --------------
dialogMapExportFormId: config.dialogMapExportFormId,
@@ -178,20 +178,20 @@ define([
formatFilename: function(){
// format filename from "map name" (initial)
return function (mapName, render) {
var filename = render(mapName);
let filename = render(mapName);
return formatFilename(filename);
};
}
};
var contentDialog = Mustache.render(templateMapDialog, data);
let contentDialog = Mustache.render(templateMapDialog, data);
contentDialog = $(contentDialog);
// set tab content
$('#' + config.dialogMapCreateContainerId, contentDialog).html(contentNewMap);
$('#' + config.dialogMapEditContainerId, contentDialog).html(contentEditMap);
var mapInfoDialog = bootbox.dialog({
let mapInfoDialog = bootbox.dialog({
title: dialogTitle,
message: contentDialog,
buttons: {
@@ -205,15 +205,15 @@ define([
callback: function() {
// get the current active form
var form = $('#' + config.newMapDialogId).find('form').filter(':visible');
let form = $('#' + config.newMapDialogId).find('form').filter(':visible');
// validate form
form.validator('validate');
// validate select2 fields (settings tab)
form.find('select').each(function(){
var selectField = $(this);
var selectValues = selectField.val();
let selectField = $(this);
let selectValues = selectField.val();
if(selectValues.length > 0){
selectField.parents('.form-group').removeClass('has-error');
@@ -223,23 +223,23 @@ define([
});
// check whether the form is valid
var formValid = form.isValidForm();
let formValid = form.isValidForm();
if(formValid === true){
// lock dialog
var dialogContent = mapInfoDialog.find('.modal-content');
let dialogContent = mapInfoDialog.find('.modal-content');
dialogContent.showLoadingAnimation();
// get form data
var formData = form.getFormValues();
let formData = form.getFormValues();
// checkbox fix -> settings tab
if( form.find('#' + config.deleteExpiredConnectionsId).length ){
formData.deleteExpiredConnections = formData.hasOwnProperty('deleteExpiredConnections') ? parseInt( formData.deleteExpiredConnections ) : 0;
}
var requestData = {formData: formData};
let requestData = {formData: formData};
$.ajax({
type: 'POST',
@@ -257,7 +257,8 @@ define([
Util.showNotify({title: dialogTitle, text: 'Map: ' + responseData.mapData.mapData.name, type: 'success'});
// update map-tab Element
var tabLinkElement = Util.getMapModule().getMapTabElements(responseData.mapData.mapData.id);
let tabLinkElement = Util.getMapModule().getMapTabElements(responseData.mapData.mapData.id);
if(tabLinkElement.length === 1){
tabLinkElement.updateTabData(responseData.mapData.mapData);
}
@@ -266,7 +267,7 @@ define([
$(document).trigger('pf:closeMenu', [{}]);
}
}).fail(function( jqXHR, status, error) {
var reason = status + ' ' + error;
let reason = status + ' ' + error;
Util.showNotify({title: jqXHR.status + ': saveMap', text: reason, type: 'warning'});
$(document).setProgramStatus('problem');
@@ -297,7 +298,7 @@ define([
// show form messages -------------------------------------
// get current active form(tab)
var form = $('#' + config.newMapDialogId).find('form').filter(':visible');
let form = $('#' + config.newMapDialogId).find('form').filter(':visible');
form.showFormMessage([{type: 'info', message: 'Creating new maps or change settings may take a few seconds'}]);
@@ -312,24 +313,24 @@ define([
}
// init "download tab" ========================================================================
var downloadTabElement = mapInfoDialog.find('#' + config.dialogMapDownloadContainerId);
let downloadTabElement = mapInfoDialog.find('#' + config.dialogMapDownloadContainerId);
if(downloadTabElement.length){
// tab exists
// export map data ------------------------------------------------------------------------
downloadTabElement.find('#' + config.buttonExportId).on('click', { mapData: mapData }, function(e){
var exportForm = $('#' + config.dialogMapExportFormId);
var validExportForm = exportForm.isValidForm();
let exportForm = $('#' + config.dialogMapExportFormId);
let validExportForm = exportForm.isValidForm();
if(validExportForm){
var mapElement = Util.getMapModule().getActiveMap();
let mapElement = Util.getMapModule().getActiveMap();
if(mapElement){
// IMPORTANT: Get map data from client (NOT from global mapData which is available in here)
// -> This excludes some data (e.g. wh statics)
// -> Bring export inline with main map toggle requests
var exportMapData = mapElement.getMapDataFromClient({
let exportMapData = mapElement.getMapDataFromClient({
forceData: true,
getAll: true
});
@@ -349,7 +350,7 @@ define([
// import map data ------------------------------------------------------------------------
// check if "FileReader" API is supported
var importFormElement = downloadTabElement.find('#' + config.dialogMapImportFormId);
let importFormElement = downloadTabElement.find('#' + config.dialogMapImportFormId);
if(window.File && window.FileReader && window.FileList && window.Blob){
// show file info in UI
@@ -357,14 +358,14 @@ define([
e.stopPropagation();
e.preventDefault();
var infoContainerElement = importFormElement.find('#' + config.dialogMapImportInfoId);
let infoContainerElement = importFormElement.find('#' + config.dialogMapImportInfoId);
infoContainerElement.hide().empty();
importFormElement.hideFormMessage('all');
var output = [];
var files = e.target.files;
let output = [];
let files = e.target.files;
for (var i = 0, f; !!(f = files[i]); i++) {
for (let i = 0, f; !!(f = files[i]); i++) {
output.push(( i + 1 ) + '. file: ' + f.name + ' - ' +
f.size + ' bytes; last modified: ' +
f.lastModifiedDate.toLocaleDateString() );
@@ -378,14 +379,14 @@ define([
});
// drag&drop
var importData = {};
let importData = {};
importData.mapData = [];
var files = [];
var filesCount = 0;
var filesCountFail = 0;
let files = [];
let filesCount = 0;
let filesCountFail = 0;
// onLoad for FileReader API
var readerOnLoad = function(readEvent) {
let readerOnLoad = function(readEvent) {
// get file content
try{
@@ -404,13 +405,13 @@ define([
}
};
var handleDragOver = function(dragEvent) {
let handleDragOver = function(dragEvent) {
dragEvent.stopPropagation();
dragEvent.preventDefault();
dragEvent.dataTransfer.dropEffect = 'copy'; // Explicitly show this is a copy.
};
var handleFileSelect = function(evt){
let handleFileSelect = function(evt){
evt.stopPropagation();
evt.preventDefault();
@@ -421,14 +422,14 @@ define([
files = evt.dataTransfer.files; // FileList object.
for (var file; !!(file = files[filesCount]); filesCount++){
var reader = new FileReader();
for (let file; !!(file = files[filesCount]); filesCount++){
let reader = new FileReader();
reader.onload = readerOnLoad;
reader.readAsText(file);
}
};
var dropZone = downloadTabElement.find('.' + config.dragDropElementClass);
let dropZone = downloadTabElement.find('.' + config.dragDropElementClass);
dropZone[0].addEventListener('dragover', handleDragOver, false);
dropZone[0].addEventListener('drop', handleFileSelect, false);
@@ -436,19 +437,19 @@ define([
downloadTabElement.find('#' + config.buttonImportId).on('click', function(e) {
importFormElement.validator('validate');
var validImportForm = importFormElement.isValidForm();
let validImportForm = importFormElement.isValidForm();
if(validImportForm){
importData = importFormElement.getFormValues();
importData.mapData = [];
var fileElement = downloadTabElement.find('#' + config.fieldImportId);
let fileElement = downloadTabElement.find('#' + config.fieldImportId);
files = fileElement[0].files;
filesCount = 0;
filesCountFail = 0;
for (var file; !!(file = files[filesCount]); filesCount++){
var reader = new FileReader();
for (let file; !!(file = files[filesCount]); filesCount++){
let reader = new FileReader();
reader.onload = readerOnLoad;
reader.readAsText(file);
}
@@ -462,9 +463,9 @@ define([
// events for tab change
mapInfoDialog.find('.navbar a').on('shown.bs.tab', function(e){
var selectElementCharacter = mapInfoDialog.find('#' + config.characterSelectId);
var selectElementCorporation = mapInfoDialog.find('#' + config.corporationSelectId);
var selectElementAlliance = mapInfoDialog.find('#' + config.allianceSelectId);
let selectElementCharacter = mapInfoDialog.find('#' + config.characterSelectId);
let selectElementCorporation = mapInfoDialog.find('#' + config.corporationSelectId);
let selectElementAlliance = mapInfoDialog.find('#' + config.allianceSelectId);
if($(e.target).attr('href') === '#' + config.dialogMapSettingsContainerId){
// "settings" tab
@@ -498,13 +499,13 @@ define([
* import new map(s) data
* @param importData
*/
var importMaps = function(importData){
let importMaps = function(importData){
var importForm = $('#' + config.dialogMapImportFormId);
let importForm = $('#' + config.dialogMapImportFormId);
importForm.hideFormMessage('all');
// lock dialog
var dialogContent = importForm.parents('.modal-content');
let dialogContent = importForm.parents('.modal-content');
dialogContent.showLoadingAnimation();
$.ajax({
@@ -518,11 +519,14 @@ define([
importForm.showFormMessage(responseData.error);
}else{
// success
if(responseData.warning.length){
importForm.showFormMessage(responseData.warning);
}
Util.showNotify({title: 'Import finished', text: 'Map(s) imported', type: 'success'});
}
}).fail(function( jqXHR, status, error) {
var reason = status + ' ' + error;
let reason = status + ' ' + error;
Util.showNotify({title: jqXHR.status + ': importMap', text: reason, type: 'error'});
}).always(function() {
importForm.find('input, select').resetFormFields().trigger('change');
@@ -537,9 +541,9 @@ define([
*/
$.fn.setExportMapData = function(mapData){
var fieldExport = $('#' + config.fieldExportId);
var filename = '';
var mapDataEncoded = '';
let fieldExport = $('#' + config.fieldExportId);
let filename = '';
let mapDataEncoded = '';
if(fieldExport.length){
filename = fieldExport.val();
@@ -550,7 +554,7 @@ define([
}
return this.each(function(){
var exportButton = $(this);
let exportButton = $(this);
exportButton.attr('href', 'data:' + mapDataEncoded);
exportButton.attr('download', filename + '.json');
});
@@ -561,28 +565,28 @@ define([
* init select2 fields within the settings dialog
* @param mapInfoDialog
*/
var initSettingsSelectFields = function(mapInfoDialog){
let initSettingsSelectFields = function(mapInfoDialog){
var selectElementCharacter = mapInfoDialog.find('#' + config.characterSelectId);
var selectElementCorporation = mapInfoDialog.find('#' + config.corporationSelectId);
var selectElementAlliance = mapInfoDialog.find('#' + config.allianceSelectId);
let selectElementCharacter = mapInfoDialog.find('#' + config.characterSelectId);
let selectElementCorporation = mapInfoDialog.find('#' + config.corporationSelectId);
let selectElementAlliance = mapInfoDialog.find('#' + config.allianceSelectId);
// init character select live search
selectElementCharacter.initAccessSelect({
type: 'character',
maxSelectionLength: Init.maxSharedCount.character
maxSelectionLength: Init.mapTypes.private.defaultConfig.max_shared
});
// init corporation select live search
selectElementCorporation.initAccessSelect({
type: 'corporation',
maxSelectionLength: Init.maxSharedCount.corporation
maxSelectionLength: Init.mapTypes.corporation.defaultConfig.max_shared
});
// init alliance select live search
selectElementAlliance.initAccessSelect({
type: 'alliance',
maxSelectionLength: Init.maxSharedCount.alliance
maxSelectionLength: Init.mapTypes.alliance.defaultConfig.max_shared
});
};
@@ -592,12 +596,11 @@ define([
*/
$.fn.showDeleteMapDialog = function(mapData){
var mapName = mapData.config.name;
let mapName = mapData.config.name;
var mapDeleteDialog = bootbox.confirm('Delete map "' + mapName + '"?', function(result){
let mapDeleteDialog = bootbox.confirm('Delete map "' + mapName + '"?', function(result){
if(result){
var data = {mapData: mapData.config};
let data = {mapData: mapData.config};
$.ajax({
type: 'POST',
@@ -606,12 +609,12 @@ define([
dataType: 'json'
}).done(function(data){
Util.showNotify({title: 'Map deleted', text: 'Map: ' + mapName, type: 'success'});
$(mapDeleteDialog).modal('hide');
}).fail(function( jqXHR, status, error) {
var reason = status + ' ' + error;
let reason = status + ' ' + error;
Util.showNotify({title: jqXHR.status + ': deleteMap', text: reason, type: 'warning'});
$(document).setProgramStatus('problem');
}).always(function() {
$(mapDeleteDialog).modal('hide');
});
return false;

View File

@@ -12,7 +12,7 @@ define([
], function($, Init, Util, Render, bootbox, MapUtil) {
'use strict';
var config = {
let config = {
// dialog
statsDialogId: 'pf-stats-dialog', // id for "stats" dialog
dialogNavigationClass: 'pf-dialog-navigation-list', // class for dialog navigation bar
@@ -35,12 +35,12 @@ define([
* init blank statistics dataTable
* @param dialogElement
*/
var initStatsTable = function(dialogElement){
var columnNumberWidth = 35;
var lineColor = '#477372';
let initStatsTable = function(dialogElement){
let columnNumberWidth = 35;
let lineColor = '#477372';
// render function for inline-chart columns
var renderInlineChartColumn = function(data, type, row, meta){
let renderInlineChartColumn = function(data, type, row, meta){
/*
switch(data.type){
case 'C': lineColor = '#5cb85c'; break;
@@ -58,15 +58,15 @@ define([
};
// render function for numeric columns
var renderNumericColumn = function(data, type, row, meta){
let renderNumericColumn = function(data, type, row, meta){
return data.toLocaleString();
};
// get table element
// Due to "complex" table headers, they are already rendered and part of the stats.html file
var table = dialogElement.find('#' + config.statsTableId);
let table = dialogElement.find('#' + config.statsTableId);
var statsTable = table.DataTable({
let statsTable = table.DataTable({
pageLength: 30,
lengthMenu: [[10, 20, 30, 50], [10, 20, 30, 50]],
paging: true,
@@ -269,10 +269,10 @@ define([
}
],
initComplete: function(settings){
var tableApi = this.api();
let tableApi = this.api();
// initial statistics data request
var requestData = getRequestDataFromTabPanels(dialogElement);
let requestData = getRequestDataFromTabPanels(dialogElement);
getStatsData(requestData, {tableApi: tableApi, callback: drawStatsTable});
},
drawCallback: function(settings){
@@ -286,11 +286,11 @@ define([
});
},
footerCallback: function ( row, data, start, end, display ) {
var api = this.api();
var sumColumnIndexes = [7, 11, 15, 16];
let api = this.api();
let sumColumnIndexes = [7, 11, 15, 16];
// column data for "sum" columns over this page
var pageTotalColumns = api
let pageTotalColumns = api
.columns( sumColumnIndexes, { page: 'current'} )
.data();
@@ -314,7 +314,7 @@ define([
});
}).draw();
var tooltipElements = dialogElement.find('[data-toggle="tooltip"]');
let tooltipElements = dialogElement.find('[data-toggle="tooltip"]');
tooltipElements.tooltip();
};
@@ -323,7 +323,7 @@ define([
* @param requestData
* @param context
*/
var getStatsData = function(requestData, context){
let getStatsData = function(requestData, context){
context.dynamicArea = $('#' + config.statsContainerId + ' .pf-dynamic-area');
context.dynamicArea.showLoadingAnimation();
@@ -339,7 +339,7 @@ define([
this.callback(data);
}).fail(function( jqXHR, status, error) {
var reason = status + ' ' + error;
let reason = status + ' ' + error;
Util.showNotify({title: jqXHR.status + ': loadStatistics', text: reason, type: 'warning'});
});
};
@@ -349,21 +349,21 @@ define([
* update "header"/"filter" elements in dialog
* @param responseData
*/
var drawStatsTable = function(responseData){
var dialogElement = $('#' + config.statsDialogId);
let drawStatsTable = function(responseData){
let dialogElement = $('#' + config.statsDialogId);
// update filter/header -----------------------------------------------------------------------------
var navigationListElements = $('.' + config.dialogNavigationClass);
let navigationListElements = $('.' + config.dialogNavigationClass);
navigationListElements.find('a[data-type="typeId"][data-value="' + responseData.typeId + '"]').tab('show');
navigationListElements.find('a[data-type="period"][data-value="' + responseData.period + '"]').tab('show');
// update period pagination -------------------------------------------------------------------------
var prevButton = dialogElement.find('.' + config.dialogNavigationPrevClass);
let prevButton = dialogElement.find('.' + config.dialogNavigationPrevClass);
prevButton.data('newOffset', responseData.prev);
prevButton.find('span').text('Week ' + responseData.prev.week + ', ' + responseData.prev.year);
prevButton.css('visibility', 'visible');
var nextButton = dialogElement.find('.' + config.dialogNavigationNextClass);
let nextButton = dialogElement.find('.' + config.dialogNavigationNextClass);
if(responseData.next){
nextButton.data('newOffset', responseData.next);
nextButton.find('span').text('Week ' + responseData.next.week + ', ' + responseData.next.year);
@@ -374,7 +374,7 @@ define([
// update current period information label ----------------------------------------------------------
// if period == "weekly" there is no "offset" -> just a single week
var offsetText = 'Week ' + responseData.start.week + ', ' + responseData.start.year;
let offsetText = 'Week ' + responseData.start.week + ', ' + responseData.start.year;
if(responseData.period !== 'weekly'){
offsetText += ' <small><i class="fa fa-fw fa-minus"></i></small> ' +
'Week ' + responseData.offset.week + ', ' + responseData.offset.year;
@@ -385,7 +385,7 @@ define([
.html(offsetText);
// clear and (re)-fill table ------------------------------------------------------------------------
var formattedData = formatStatisticsData(responseData);
let formattedData = formatStatisticsData(responseData);
this.tableApi.clear();
this.tableApi.rows.add(formattedData).draw();
};
@@ -396,23 +396,23 @@ define([
* @param statsData
* @returns {Array}
*/
var formatStatisticsData = function(statsData){
var formattedData = [];
var yearStart = statsData.start.year;
var weekStart = statsData.start.week;
var weekCount = statsData.weekCount;
var yearWeeks = statsData.yearWeeks;
let formatStatisticsData = function(statsData){
let formattedData = [];
let yearStart = statsData.start.year;
let weekStart = statsData.start.week;
let weekCount = statsData.weekCount;
let yearWeeks = statsData.yearWeeks;
var tempRand = function(min, max){
let tempRand = function(min, max){
return Math.random() * (max - min) + min;
};
// format/sum week statistics data for inline charts
var formatWeekData = function(weeksData){
var currentYear = yearStart;
var currentWeek = weekStart;
let formatWeekData = function(weeksData){
let currentYear = yearStart;
let currentWeek = weekStart;
var formattedWeeksData = {
let formattedWeeksData = {
systemCreate: [],
systemUpdate: [],
systemDelete: [],
@@ -507,9 +507,9 @@ define([
$.each(statsData.statistics, function(characterId, data){
var formattedWeeksData = formatWeekData(data.weeks);
let formattedWeeksData = formatWeekData(data.weeks);
var rowData = {
let rowData = {
character: {
id: characterId,
name: data.name,
@@ -568,20 +568,20 @@ define([
* @param dialogElement
* @returns {{}}
*/
var getRequestDataFromTabPanels = function(dialogElement){
var requestData = {};
let getRequestDataFromTabPanels = function(dialogElement){
let requestData = {};
// get data from "tab" panel links ------------------------------------------------------------------
var navigationListElements = dialogElement.find('.' + config.dialogNavigationClass);
let navigationListElements = dialogElement.find('.' + config.dialogNavigationClass);
navigationListElements.find('.' + config.dialogNavigationListItemClass + '.active a').each(function(){
var linkElement = $(this);
let linkElement = $(this);
requestData[linkElement.data('type')]= linkElement.data('value');
});
// get current period (no offset) data (if available) -----------------------------------------------
var navigationOffsetElement = dialogElement.find('.' + config.dialogNavigationOffsetClass);
var startData = navigationOffsetElement.data('start');
var periodOld = navigationOffsetElement.data('period');
let navigationOffsetElement = dialogElement.find('.' + config.dialogNavigationOffsetClass);
let startData = navigationOffsetElement.data('start');
let periodOld = navigationOffsetElement.data('period');
// if period switch was detected
// -> "year" and "week" should not be send
@@ -602,18 +602,18 @@ define([
* @param type
* @returns {boolean}
*/
var isTabTypeEnabled = function(type){
var enabled = false;
let isTabTypeEnabled = function(type){
let enabled = false;
switch(type){
case 'private':
if(Init.activityLogging.character){
if(Init.mapTypes.private.defaultConfig.activity_logging){
enabled = true;
}
break;
case 'corporation':
if(
Init.activityLogging.corporation &&
Init.mapTypes.corporation.defaultConfig.activity_logging &&
Util.getCurrentUserInfo('corporationId')
){
enabled = true;
@@ -621,7 +621,7 @@ define([
break;
case 'alliance':
if(
Init.activityLogging.alliance &&
Init.mapTypes.alliance.defaultConfig.activity_logging &&
Util.getCurrentUserInfo('allianceId')
){
enabled = true;
@@ -638,7 +638,7 @@ define([
$.fn.showStatsDialog = function(){
requirejs(['text!templates/dialog/stats.html', 'mustache', 'peityInlineChart'], function(template, Mustache) {
var data = {
let data = {
id: config.statsDialogId,
dialogNavigationClass: config.dialogNavigationClass,
dialogNavLiClass: config.dialogNavigationListItemClass,
@@ -652,9 +652,9 @@ define([
dialogNavigationNextClass: config.dialogNavigationNextClass
};
var content = Mustache.render(template, data);
let content = Mustache.render(template, data);
var statsDialog = bootbox.dialog({
let statsDialog = bootbox.dialog({
title: 'Statistics',
message: content,
size: 'large',
@@ -669,7 +669,7 @@ define([
// model events
statsDialog.on('show.bs.modal', function(e) {
var dialogElement = $(e.target);
let dialogElement = $(e.target);
initStatsTable(dialogElement);
});
@@ -683,22 +683,22 @@ define([
});
statsDialog.find('a[data-toggle="tab"]').on('shown.bs.tab', function (e) {
var requestData = getRequestDataFromTabPanels(statsDialog);
var tableApi = statsDialog.find('#' + config.statsTableId).DataTable();
let requestData = getRequestDataFromTabPanels(statsDialog);
let tableApi = statsDialog.find('#' + config.statsTableId).DataTable();
getStatsData(requestData, {tableApi: tableApi, callback: drawStatsTable});
});
// offset change links
statsDialog.find('.' + config.dialogNavigationPrevClass + ', .' + config.dialogNavigationNextClass).on('click', function(){
var offsetData = $(this).data('newOffset');
let offsetData = $(this).data('newOffset');
if(offsetData){
// this should NEVER fail!
// get "base" request data (e.g. typeId, period)
// --> overwrite period data with new period data
var tmpRequestData = getRequestDataFromTabPanels(statsDialog);
var requestData = $.extend({}, tmpRequestData, offsetData);
var tableApi = statsDialog.find('#' + config.statsTableId).DataTable();
let tmpRequestData = getRequestDataFromTabPanels(statsDialog);
let requestData = $.extend({}, tmpRequestData, offsetData);
let tableApi = statsDialog.find('#' + config.statsTableId).DataTable();
getStatsData(requestData, {tableApi: tableApi, callback: drawStatsTable});
}

View File

@@ -11,7 +11,7 @@ define([
], function($, Init, Util, bootbox, MapUtil) {
'use strict';
var config = {
let config = {
// module info
moduleClass: 'pf-module', // class for each module
@@ -39,7 +39,7 @@ define([
};
// cache for system routes
var cache = {
let cache = {
systemRoutes: {} // jump information between solar systems
};
@@ -48,17 +48,17 @@ define([
* @param context
* @param routesData
*/
var callbackAddRouteRow = function(context, routesData){
let callbackAddRouteRow = function(context, routesData){
if(routesData.length > 0){
for(var i = 0; i < routesData.length; i++){
var routeData = routesData[i];
for(let i = 0; i < routesData.length; i++){
let routeData = routesData[i];
// format routeData
var rowData = formatRouteData(routeData);
let rowData = formatRouteData(routeData);
if(rowData.route){
var cacheKey = routeData.systemFromData.name.toLowerCase() +
let cacheKey = routeData.systemFromData.name.toLowerCase() +
'_' + routeData.systemToData.name.toLowerCase();
// update route cache
@@ -67,7 +67,7 @@ define([
updated: Util.getServerTime().getTime() / 1000
};
var rowElement = addRow(context, rowData);
let rowElement = addRow(context, rowData);
rowElement.initTooltips({
container: 'body'
@@ -86,15 +86,15 @@ define([
* @param rowData
* @returns {*}
*/
var addRow = function(context, rowData){
var dataTable = context.dataTable;
var rowElement = null;
var row = null;
var animationStatus = 'changed';
let addRow = function(context, rowData){
let dataTable = context.dataTable;
let rowElement = null;
let row = null;
let animationStatus = 'changed';
// search for an existing row (e.g. on mass "table refresh" [all routes])
// get rowIndex where column 1 (equals to "systemToData.name") matches rowData.systemToData.name
var indexes = dataTable.rows().eq(0).filter( function (rowIdx) {
let indexes = dataTable.rows().eq(0).filter( function (rowIdx) {
return (dataTable.cell(rowIdx, 1 ).data().name === rowData.systemToData.name);
});
@@ -122,17 +122,43 @@ define([
return rowElement;
};
/**
* requests route data from eveCentral API and execute callback
* @param requestData
* @param context
* @param callback
*/
let getRouteData = function(requestData, context, callback){
context.moduleElement.showLoadingAnimation();
$.ajax({
url: Init.path.searchRoute,
type: 'POST',
dataType: 'json',
data: requestData,
context: context
}).done(function(routesData){
this.moduleElement.hideLoadingAnimation();
// execute callback
callback(this, routesData.routesData);
});
};
/**
* update complete routes table (refresh all)
* @param moduleElement
* @param dataTable
*/
var updateRoutesTable = function(moduleElement, dataTable){
var context = {
let updateRoutesTable = function(moduleElement, dataTable){
let context = {
moduleElement: moduleElement,
dataTable: dataTable
};
var routeData = [];
let routeData = [];
dataTable.rows().every( function() {
routeData.push( getRouteRequestDataFromRowData( this.data() ));
@@ -146,7 +172,7 @@ define([
* @param {Object} rowData
* @returns {Object}
*/
var getRouteRequestDataFromRowData = function(rowData){
let getRouteRequestDataFromRowData = function(rowData){
return {
mapIds: (rowData.hasOwnProperty('mapIds')) ? rowData.mapIds : [],
systemFromData: (rowData.hasOwnProperty('systemFromData')) ? rowData.systemFromData : {},
@@ -157,7 +183,8 @@ define([
wormholes: (rowData.hasOwnProperty('wormholes')) ? rowData.wormholes | 0 : 1,
wormholesReduced: (rowData.hasOwnProperty('wormholesReduced')) ? rowData.wormholesReduced | 0 : 1,
wormholesCritical: (rowData.hasOwnProperty('wormholesCritical')) ? rowData.wormholesCritical | 0 : 1,
wormholesEOL: (rowData.hasOwnProperty('wormholesEOL')) ? rowData.wormholesEOL | 0 : 1
wormholesEOL: (rowData.hasOwnProperty('wormholesEOL')) ? rowData.wormholesEOL | 0 : 1,
safer: (rowData.hasOwnProperty('safer')) ? rowData.safer.value | 0 : 0
};
};
@@ -165,12 +192,12 @@ define([
* show route dialog. User can search for systems and jump-info for each system is added to a data table
* @param dialogData
*/
var showFindRouteDialog = function(dialogData){
let showFindRouteDialog = function(dialogData){
var mapSelectOptions = [];
var currentMapData = Util.getCurrentMapData();
let mapSelectOptions = [];
let currentMapData = Util.getCurrentMapData();
if(currentMapData !== false){
for(var i = 0; i < currentMapData.length; i++){
for(let i = 0; i < currentMapData.length; i++){
mapSelectOptions.push({
id: currentMapData[i].config.id,
name: currentMapData[i].config.name,
@@ -178,7 +205,7 @@ define([
});
}
}
var data = {
let data = {
id: config.routeDialogId,
selectClass: config.systemDialogSelectClass,
mapSelectId: config.mapSelectId,
@@ -188,9 +215,9 @@ define([
requirejs(['text!templates/dialog/route.html', 'mustache'], function(template, Mustache) {
var content = Mustache.render(template, data);
let content = Mustache.render(template, data);
var findRouteDialog = bootbox.dialog({
let findRouteDialog = bootbox.dialog({
title: 'Route finder',
message: content,
show: false,
@@ -206,15 +233,15 @@ define([
// add new route to route table
// get form Values
var form = $('#' + config.routeDialogId).find('form');
let form = $('#' + config.routeDialogId).find('form');
var routeDialogData = $(form).getFormValues();
let routeDialogData = $(form).getFormValues();
// validate form
form.validator('validate');
// check whether the form is valid
var formValid = form.isValidForm();
let formValid = form.isValidForm();
if(formValid === false){
// don't close dialog
@@ -222,18 +249,18 @@ define([
}
// get all system data from select2
var systemSelectData = form.find('.' + config.systemDialogSelectClass).select2('data');
let systemSelectData = form.find('.' + config.systemDialogSelectClass).select2('data');
if(
systemSelectData &&
systemSelectData.length === 1
){
var context = {
let context = {
moduleElement: dialogData.moduleElement,
dataTable: dialogData.dataTable
};
var requestData = {
let requestData = {
routeData: [{
mapIds: routeDialogData.mapIds,
systemFromData: dialogData.systemFromData,
@@ -264,7 +291,7 @@ define([
setDialogObserver( $(this) );
// init map select ----------------------------------------------------------------
var mapSelect = $(this).find('#' + config.mapSelectId);
let mapSelect = $(this).find('#' + config.mapSelectId);
mapSelect.initMapSelect();
});
@@ -273,7 +300,7 @@ define([
// init system select live search ------------------------------------------------
// -> add some delay until modal transition has finished
var systemTargetSelect = $(this).find('.' + config.systemDialogSelectClass);
let systemTargetSelect = $(this).find('.' + config.systemDialogSelectClass);
systemTargetSelect.delay(240).initSystemSelect({key: 'name'});
});
@@ -282,6 +309,69 @@ define([
});
};
/**
* draw route table
* @param mapId
* @param moduleElement
* @param systemFromData
* @param routesTable
* @param systemsTo
*/
let drawRouteTable = function(mapId, moduleElement, systemFromData, routesTable, systemsTo){
let requestRouteData = [];
let currentTimestamp = Util.getServerTime().getTime();
// Skip some routes from search
// -> this should help to throttle requests (heavy CPU load for route calculation)
let defaultRoutesCount = Init.routeSearch.defaultCount;
for(let i = 0; i < systemsTo.length; i++){
let systemToData = systemsTo[i];
if(systemFromData.name !== systemToData.name){
let cacheKey = 'route_' + mapId + '_' + systemFromData.name.toUpperCase() + '_' + systemToData.name.toUpperCase();
if(
cache.systemRoutes.hasOwnProperty(cacheKey) &&
Math.round(
( currentTimestamp - (new Date( cache.systemRoutes[cacheKey].updated * 1000).getTime())) / 1000
) <= config.routeCacheTTL
){
// route data is cached (client side)
let context = {
dataTable: routesTable
};
addRow(context, cache.systemRoutes[cacheKey].data);
}else{
// get route data
let searchData = {
mapIds: [mapId],
systemFromData: systemFromData,
systemToData: systemToData,
skipSearch: requestRouteData.length >= defaultRoutesCount
};
requestRouteData.push( getRouteRequestDataFromRowData( searchData ));
}
}
}
// check if routes data is not cached and is requested
if(requestRouteData.length > 0){
let contextData = {
moduleElement: moduleElement,
dataTable: routesTable
};
let requestData = {
routeData: requestRouteData
};
getRouteData(requestData, contextData, callbackAddRouteRow);
}
};
/**
* show route settings dialog
* @param dialogData
@@ -289,12 +379,12 @@ define([
* @param systemFromData
* @param routesTable
*/
var showSettingsDialog = function(dialogData, moduleElement, systemFromData, routesTable){
let showSettingsDialog = function(dialogData, moduleElement, systemFromData, routesTable){
var promiseStore = MapUtil.getLocaleData('map', dialogData.mapId);
let promiseStore = MapUtil.getLocaleData('map', dialogData.mapId);
promiseStore.then(function(dataStore) {
// selected systems (if already stored)
var systemSelectOptions = [];
let systemSelectOptions = [];
if(
dataStore &&
dataStore.routes
@@ -303,9 +393,9 @@ define([
}
// max count of "default" target systems
var maxSelectionLength = Init.routeSearch.maxDefaultCount;
let maxSelectionLength = Init.routeSearch.maxDefaultCount;
var data = {
let data = {
id: config.routeSettingsDialogId,
selectClass: config.systemDialogSelectClass,
systemSelectOptions: systemSelectOptions,
@@ -313,9 +403,9 @@ define([
};
requirejs(['text!templates/dialog/route_settings.html', 'mustache'], function(template, Mustache) {
var content = Mustache.render(template, data);
let content = Mustache.render(template, data);
var settingsDialog = bootbox.dialog({
let settingsDialog = bootbox.dialog({
title: 'Route settings',
message: content,
show: false,
@@ -328,10 +418,10 @@ define([
label: '<i class="fa fa-fw fa-check"></i>&nbsp;save',
className: 'btn-success',
callback: function () {
var form = this.find('form');
let form = this.find('form');
// get all system data from select2
var systemSelectData = form.find('.' + config.systemDialogSelectClass).select2('data');
var systemsTo = [];
let systemSelectData = form.find('.' + config.systemDialogSelectClass).select2('data');
let systemsTo = [];
if( systemSelectData.length > 0 ){
systemsTo = formSystemSelectData(systemSelectData);
@@ -353,7 +443,7 @@ define([
// init default system select -----------------------------------------------------
// -> add some delay until modal transition has finished
var systemTargetSelect = $(this).find('.' + config.systemDialogSelectClass);
let systemTargetSelect = $(this).find('.' + config.systemDialogSelectClass);
systemTargetSelect.delay(240).initSystemSelect({key: 'name', maxSelectionLength: maxSelectionLength});
});
@@ -368,10 +458,10 @@ define([
* @param {Array} data
* @returns {Array}
*/
var formSystemSelectData = function(data){
var formattedData = [];
let formSystemSelectData = function(data){
let formattedData = [];
for(let i = 0; i < data.length; i++){
var tmpData = data[i];
let tmpData = data[i];
formattedData.push({
name: tmpData.id,
@@ -386,21 +476,21 @@ define([
* set event observer for route finder dialog
* @param routeDialog
*/
var setDialogObserver = function(routeDialog){
var wormholeCheckbox = routeDialog.find('input[type="checkbox"][name="wormholes"]');
var wormholeReducedCheckbox = routeDialog.find('input[type="checkbox"][name="wormholesReduced"]');
var wormholeCriticalCheckbox = routeDialog.find('input[type="checkbox"][name="wormholesCritical"]');
var wormholeEolCheckbox = routeDialog.find('input[type="checkbox"][name="wormholesEOL"]');
let setDialogObserver = function(routeDialog){
let wormholeCheckbox = routeDialog.find('input[type="checkbox"][name="wormholes"]');
let wormholeReducedCheckbox = routeDialog.find('input[type="checkbox"][name="wormholesReduced"]');
let wormholeCriticalCheckbox = routeDialog.find('input[type="checkbox"][name="wormholesCritical"]');
let wormholeEolCheckbox = routeDialog.find('input[type="checkbox"][name="wormholesEOL"]');
// store current "checked" state for each box ---------------------------------------------
var storeCheckboxStatus = function(){
let storeCheckboxStatus = function(){
wormholeReducedCheckbox.data('selectState', wormholeReducedCheckbox.prop('checked'));
wormholeCriticalCheckbox.data('selectState', wormholeCriticalCheckbox.prop('checked'));
wormholeEolCheckbox.data('selectState', wormholeEolCheckbox.prop('checked'));
};
// on wormhole checkbox change ------------------------------------------------------------
var onWormholeCheckboxChange = function(){
let onWormholeCheckboxChange = function(){
if( $(this).is(':checked') ){
wormholeReducedCheckbox.prop('disabled', false);
@@ -429,37 +519,12 @@ define([
onWormholeCheckboxChange();
};
/**
* requests route data from eveCentral API and execute callback
* @param requestData
* @param context
* @param callback
*/
var getRouteData = function(requestData, context, callback){
context.moduleElement.showLoadingAnimation();
$.ajax({
url: Init.path.searchRoute,
type: 'POST',
dataType: 'json',
data: requestData,
context: context
}).done(function(routesData){
this.moduleElement.hideLoadingAnimation();
// execute callback
callback(this, routesData.routesData);
});
};
/**
* format route data from API request into dataTable row format
* @param routeData
* @returns {{}}
*/
var formatRouteData = function(routeData){
let formatRouteData = function(routeData){
/**
* get status icon for route
@@ -487,14 +552,18 @@ define([
// 0: not found
// 1: round (OK)
// 2: not searched
var routeStatus = routeData.skipSearch ? 2 : 0;
let routeStatus = routeData.skipSearch ? 2 : 0;
var reloadButton = '<i class="fa ' + ['fa-refresh'].join(' ') + '"></i>';
var searchButton = '<i class="fa ' + ['fa-search-plus '].join(' ') + '"></i>';
var deleteButton = '<i class="fa ' + ['fa-close', 'txt-color', 'txt-color-redDarker'].join(' ') + '"></i>';
// button class for "safer" routes
let saferButtonClass = routeData.safer ? 'txt-color-success' : '';
let saferButton = '<i class="fa ' + ['fa-shield', 'txt-color', saferButtonClass].join(' ') + '"></i>';
let reloadButton = '<i class="fa ' + ['fa-refresh'].join(' ') + '"></i>';
let searchButton = '<i class="fa ' + ['fa-search-plus '].join(' ') + '"></i>';
let deleteButton = '<i class="fa ' + ['fa-close', 'txt-color', 'txt-color-redDarker'].join(' ') + '"></i>';
// default row data (e.g. no route found)
var tableRowData = {
let tableRowData = {
systemFromData: routeData.systemFromData,
systemToData: routeData.systemToData,
jumps: {
@@ -512,6 +581,10 @@ define([
wormholesReduced: routeData.wormholesReduced,
wormholesCritical: routeData.wormholesCritical,
wormholesEOL: routeData.wormholesEOL,
safer: {
value: routeData.safer,
button: saferButton
},
reload: {
button: routeData.skipSearch ? searchButton : reloadButton
},
@@ -530,14 +603,14 @@ define([
routeStatus = 1;
// add route Data
var jumpData = [];
var avgSecTemp = 0;
let jumpData = [];
let avgSecTemp = 0;
// loop all systems on this route
for(var i = 0; i < routeData.route.length; i++){
for(let i = 0; i < routeData.route.length; i++){
let routeNodeData = routeData.route[i];
// format system name (camelCase)
let systemName = routeNodeData.system.charAt(0).toUpperCase() + routeNodeData.system.slice(1).toLowerCase();
// format system name
let systemName = routeNodeData.system;
let systemSec = Number(routeNodeData.security).toFixed(1).toString();
let tempSystemSec = systemSec;
@@ -565,14 +638,14 @@ define([
}
}
var avgSec = ( avgSecTemp / (routeData.route.length - 1)).toFixed(2);
var avgSecForClass = Number(avgSec).toFixed(1);
let avgSec = ( avgSecTemp / (routeData.route.length - 1)).toFixed(2);
let avgSecForClass = Number(avgSec).toFixed(1);
if(avgSecForClass <= 0){
avgSecForClass = '0.0';
}
var avgSecClass = config.systemSecurityClassPrefix + avgSecForClass.toString().replace('.', '-');
let avgSecClass = config.systemSecurityClassPrefix + avgSecForClass.toString().replace('.', '-');
tableRowData.jumps = {
value: routeData.routeJumps,
@@ -599,15 +672,15 @@ define([
* get the route finder moduleElement
* @returns {*}
*/
var getModule = function(){
let getModule = function(){
// create new module container
var moduleElement = $('<div>', {
let moduleElement = $('<div>', {
class: [config.moduleClass, config.systemRouteModuleClass].join(' ')
});
// headline toolbar icons
var headlineToolbar = $('<h5>', {
let headlineToolbar = $('<h5>', {
class: 'pull-right'
}).append(
$('<i>', {
@@ -627,7 +700,7 @@ define([
moduleElement.append(headlineToolbar);
// headline
var headline = $('<h5>', {
let headline = $('<h5>', {
class: 'pull-left',
text: 'Routes'
});
@@ -635,14 +708,14 @@ define([
moduleElement.append(headline);
// crate new route table
var table = $('<table>', {
let table = $('<table>', {
class: ['compact', 'stripe', 'order-column', 'row-border', config.systemInfoRoutesTableClass].join(' ')
});
moduleElement.append( $(table) );
// init empty table
var routesTable = table.DataTable( {
let routesTable = table.DataTable( {
paging: false,
ordering: true,
order: [[ 2, 'asc' ], [ 0, 'asc' ]],
@@ -711,41 +784,35 @@ define([
data: 'route'
},{
targets: 5,
title: '',
title: '<i title="search safer route (HS)" data-toggle="tooltip" class="fa fa-shield text-right"></i>',
orderable: false,
searchable: false,
width: '10px',
class: ['text-center', config.dataTableActionCellClass].join(' '),
data: 'reload',
data: 'safer',
render: {
_: 'button'
},
createdCell: function(cell, cellData, rowData, rowIndex, colIndex){
var tempTableApi = this.api();
let tempTableApi = this.api();
$(cell).on('click', function(e) {
// get current row data (important!)
// -> "rowData" param is not current state, values are "on createCell()" state
rowData = tempTableApi.row( $(cell).parents('tr')).data();
let routeData = getRouteRequestDataFromRowData( rowData );
var context = {
// overwrite some params
routeData.skipSearch = 0;
routeData.safer = 1 - routeData.safer; // toggle
let context = {
moduleElement: moduleElement,
dataTable: tempTableApi
};
var requestData = {
routeData: [{
mapIds: rowData.mapIds,
systemFromData: rowData.systemFromData,
systemToData: rowData.systemToData,
skipSearch: 0,
stargates: rowData.stargates ? 1 : 0,
jumpbridges: rowData.jumpbridges ? 1 : 0,
wormholes: rowData.wormholes ? 1 : 0,
wormholesReduced: rowData.wormholesReduced ? 1 : 0,
wormholesCritical: rowData.wormholesCritical ? 1 : 0,
wormholesEOL: rowData.wormholesEOL ? 1 : 0
}]
let requestData = {
routeData: [routeData]
};
getRouteData(requestData, context, callbackAddRouteRow);
@@ -758,14 +825,49 @@ define([
searchable: false,
width: '10px',
class: ['text-center', config.dataTableActionCellClass].join(' '),
data: 'reload',
render: {
_: 'button'
},
createdCell: function(cell, cellData, rowData, rowIndex, colIndex){
let tempTableApi = this.api();
$(cell).on('click', function(e) {
// get current row data (important!)
// -> "rowData" param is not current state, values are "on createCell()" state
rowData = tempTableApi.row( $(cell).parents('tr')).data();
let routeData = getRouteRequestDataFromRowData( rowData );
// overwrite some params
routeData.skipSearch = 0;
let context = {
moduleElement: moduleElement,
dataTable: tempTableApi
};
let requestData = {
routeData: [routeData]
};
getRouteData(requestData, context, callbackAddRouteRow);
});
}
},{
targets: 7,
title: '',
orderable: false,
searchable: false,
width: '10px',
class: ['text-center', config.dataTableActionCellClass].join(' '),
data: 'clear',
render: {
_: 'button'
},
createdCell: function(cell, cellData, rowData, rowIndex, colIndex){
var tempTableElement = this;
let tempTableElement = this;
var confirmationSettings = {
let confirmationSettings = {
container: 'body',
placement: 'left',
btnCancelClass: 'btn btn-sm btn-default',
@@ -776,7 +878,7 @@ define([
btnOkLabel: 'delete',
btnOkIcon: 'fa fa-fw fa-close',
onConfirm : function(e, target){
var deleteRowElement = $(cell).parents('tr');
let deleteRowElement = $(cell).parents('tr');
tempTableElement.api().rows(deleteRowElement).remove().draw();
}
};
@@ -788,14 +890,14 @@ define([
],
drawCallback: function(settings){
var animationRows = this.api().rows().nodes().to$().filter(function() {
let animationRows = this.api().rows().nodes().to$().filter(function() {
return (
$(this).data('animationStatus') ||
$(this).data('animationTimer')
);
});
for(var i = 0; i < animationRows.length; i++){
for(let i = 0; i < animationRows.length; i++){
$(animationRows[i]).pulseTableRow($(animationRows[i]).data('animationStatus'));
$(animationRows[i]).removeData('animationStatus');
}
@@ -805,7 +907,7 @@ define([
});
// init tooltips for this module
var tooltipElements = moduleElement.find('[data-toggle="tooltip"]');
let tooltipElements = moduleElement.find('[data-toggle="tooltip"]');
tooltipElements.tooltip({
container: 'body'
});
@@ -819,19 +921,19 @@ define([
* @param options
*/
$.fn.initSystemPopover = function(options){
var elements = $(this);
var eventNamespace = 'hideSystemPopup';
var systemToData = options.systemToData;
let elements = $(this);
let eventNamespace = 'hideSystemPopup';
let systemToData = options.systemToData;
requirejs(['text!templates/tooltip/system_popover.html', 'mustache'], function (template, Mustache) {
var data = {
let data = {
systemToData: systemToData
};
var content = Mustache.render(template, data);
let content = Mustache.render(template, data);
elements.each(function() {
var element = $(this);
let element = $(this);
// destroy "popover" and remove "click" event for animation
element.popover('destroy').off();
@@ -859,11 +961,11 @@ define([
// set link observer "on shown" event
elements.on('shown.bs.popover', function () {
var popoverRoot = $(this);
let popoverRoot = $(this);
popoverRoot.data('bs.popover').tip().find('a').on('click', function(){
// hint: "data" attributes should be in lower case!
var systemData = {
let systemData = {
name: $(this).data('name'),
systemId: $(this).data('systemid')
};
@@ -883,16 +985,16 @@ define([
* @param mapId
* @param systemData
*/
var initModule = function(moduleElement, mapId, systemData){
let initModule = function(moduleElement, mapId, systemData){
var systemFromData = {
let systemFromData = {
name: systemData.name,
systemId: systemData.systemId
};
var routesTableElement = moduleElement.find('.' + config.systemInfoRoutesTableClass);
let routesTableElement = moduleElement.find('.' + config.systemInfoRoutesTableClass);
var routesTable = routesTableElement.DataTable();
let routesTable = routesTableElement.DataTable();
// init refresh routes --------------------------------------------------------------------
moduleElement.find('.' + config.systemModuleHeadlineIconRefresh).on('click', function(e){
@@ -907,7 +1009,7 @@ define([
// max routes limit reached -> show warning
Util.showNotify({title: 'Route limit reached', text: 'Serch is limited by ' + maxRouteSearchLimit, type: 'warning'});
}else{
var dialogData = {
let dialogData = {
moduleElement: moduleElement,
mapId: mapId,
systemFromData: systemFromData,
@@ -922,7 +1024,7 @@ define([
// init settings dialog -------------------------------------------------------------------
moduleElement.find('.' + config.systemModuleHeadlineIconSettings).on('click', function(e){
var dialogData = {
let dialogData = {
mapId: mapId
};
@@ -930,10 +1032,10 @@ define([
});
// fill routesTable with data -------------------------------------------------------------
var promiseStore = MapUtil.getLocaleData('map', mapId);
let promiseStore = MapUtil.getLocaleData('map', mapId);
promiseStore.then(function(dataStore) {
// selected systems (if already stored)
var systemsTo = [{
let systemsTo = [{
name: 'Jita',
systemId: 30000142
}];
@@ -950,69 +1052,6 @@ define([
};
/**
* draw route table
* @param mapId
* @param moduleElement
* @param systemFromData
* @param routesTable
* @param systemsTo
*/
var drawRouteTable = function(mapId, moduleElement, systemFromData, routesTable, systemsTo){
var requestRouteData = [];
var currentTimestamp = Util.getServerTime().getTime();
// Skip some routes from search
// -> this should help to throttle requests (heavy CPU load for route calculation)
var defaultRoutesCount = Init.routeSearch.defaultCount;
for(var i = 0; i < systemsTo.length; i++){
var systemToData = systemsTo[i];
if(systemFromData.name !== systemToData.name){
var cacheKey = 'route_' + mapId + '_' + systemFromData.name.toUpperCase() + '_' + systemToData.name.toUpperCase();
if(
cache.systemRoutes.hasOwnProperty(cacheKey) &&
Math.round(
( currentTimestamp - (new Date( cache.systemRoutes[cacheKey].updated * 1000).getTime())) / 1000
) <= config.routeCacheTTL
){
// route data is cached (client side)
var context = {
dataTable: routesTable
};
addRow(context, cache.systemRoutes[cacheKey].data);
}else{
// get route data
var searchData = {
mapIds: [mapId],
systemFromData: systemFromData,
systemToData: systemToData,
skipSearch: requestRouteData.length >= defaultRoutesCount
};
requestRouteData.push( getRouteRequestDataFromRowData( searchData ));
}
}
}
// check if routes data is not cached and is requested
if(requestRouteData.length > 0){
var contextData = {
moduleElement: moduleElement,
dataTable: routesTable
};
var requestData = {
routeData: requestRouteData
};
getRouteData(requestData, contextData, callbackAddRouteRow);
}
};
/**
* updates an dom element with the system route module
* @param mapId
@@ -1020,10 +1059,10 @@ define([
*/
$.fn.drawSystemRouteModule = function(mapId, systemData){
var parentElement = $(this);
let parentElement = $(this);
// show route module
var showModule = function(moduleElement){
let showModule = function(moduleElement){
if(moduleElement){
moduleElement.css({ opacity: 0 });
parentElement.append(moduleElement);
@@ -1039,7 +1078,7 @@ define([
};
// check if module already exists
var moduleElement = parentElement.find('.' + config.systemRouteModuleClass);
let moduleElement = parentElement.find('.' + config.systemRouteModuleClass);
if(moduleElement.length > 0){
moduleElement.velocity('transition.slideDownOut', {

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

162
js/app/worker/map.js Normal file
View File

@@ -0,0 +1,162 @@
'use strict';
// "fake" window object will contain "MsgWorker" after import
let window = {}; // jshint ignore:line
// import "MsgWorker" class
self.importScripts( self.name ); // jshint ignore:line
let MsgWorker = window.MsgWorker;
let socket = null;
let ports = [];
let characterPorts = [];
// init "WebSocket" connection ========================================================================================
let initSocket = (uri) => {
let MsgWorkerOpen = new MsgWorker('ws:open');
if(socket === null){
socket = new WebSocket(uri);
// "WebSocket" open -----------------------------------------------------------------------
socket.onopen = (e) => {
MsgWorkerOpen.meta({
readyState: socket.readyState
});
sendToCurrentPort(MsgWorkerOpen);
};
// "WebSocket message ---------------------------------------------------------------------
socket.onmessage = (e) => {
let response = JSON.parse(e.data);
let MsgWorkerSend = new MsgWorker('ws:send');
MsgWorkerSend.task( response.task );
MsgWorkerSend.meta({
readyState: this.readyState,
characterIds: response.characterIds
});
MsgWorkerSend.data( response.load );
broadcastPorts(MsgWorkerSend);
};
// "WebSocket" close ----------------------------------------------------------------------
socket.onclose = (closeEvent) => {
let MsgWorkerClosed = new MsgWorker('ws:closed');
MsgWorkerClosed.meta({
readyState: socket.readyState,
code: closeEvent.code,
reason: closeEvent.reason,
wasClean: closeEvent.wasClean
});
broadcastPorts(MsgWorkerClosed);
socket = null; // reset WebSocket
};
// "WebSocket" error ----------------------------------------------------------------------
socket.onerror = (e) => {
let MsgWorkerError = new MsgWorker('ws:error');
MsgWorkerError.meta({
readyState: socket.readyState
});
sendToCurrentPort(MsgWorkerError);
};
}else{
// socket still open
MsgWorkerOpen.meta({
readyState: socket.readyState
});
sendToCurrentPort(MsgWorkerOpen);
}
};
// send message to port(s) ============================================================================================
let sendToCurrentPort = (load) => {
ports[ports.length - 1].postMessage(load);
};
let broadcastPorts = (load) => {
// default: sent to all ports
let sentToPorts = ports;
// check if send() is limited to some ports
let meta = load.meta();
if(
meta &&
meta.characterIds &&
meta.characterIds !== 'undefined' &&
meta.characterIds instanceof Array
){
// ... get ports for characterIds
sentToPorts = getPortsByCharacterIds(meta.characterIds);
}
for (let i = 0; i < sentToPorts.length; i++) {
sentToPorts[i].postMessage(load);
}
};
// port functions =====================================================================================================
let addPort = (port, characterId) => {
characterId = parseInt(characterId);
if(characterId > 0){
characterPorts.push({
characterId: characterId,
port: port
});
}else{
ports.push(port);
}
};
let getPortsByCharacterIds = (characterIds) => {
let ports = [];
for(let i = 0; i < characterPorts.length; i++){
for(let j = 0; j < characterIds.length; j++){
if(characterPorts[i].characterId === characterIds[j]){
ports.push(characterPorts[i].port);
}
}
}
return ports;
};
// "SharedWorker" connection ==========================================================================================
self.addEventListener('connect', (event) => { // jshint ignore:line
let port = event.ports[0];
addPort(port);
port.addEventListener('message', (e) => {
let MsgWorkerMessage = e.data;
Object.setPrototypeOf(MsgWorkerMessage, MsgWorker.prototype);
switch(MsgWorkerMessage.command){
case 'ws:init':
let data = MsgWorkerMessage.data();
// add character specific port (for broadcast) to individual ports (tabs)
addPort(port, data.characterId);
initSocket(data.uri);
break;
case 'ws:send':
let MsgSocket = {
task: MsgWorkerMessage.task(),
load: MsgWorkerMessage.data()
};
socket.send(JSON.stringify(MsgSocket));
break;
case 'ws:close':
// closeSocket();
break;
}
}, false);
port.start();
}, false);

55
js/app/worker/message.js Normal file
View File

@@ -0,0 +1,55 @@
window.MsgWorker = class MessageWorker {
constructor(cmd){
/**
* "command" type (identifies this message)
*/
this.cmd = cmd;
/**
* "task" what should be done with this message
* @type {string}
*/
this.msgTask = '';
/**
* "message" meta data (e.g. error/close data from WebSocket
* @type {null}
*/
this.msgMeta = null;
/**
* "message" body (load)
* @type {null}
*/
this.msgBody = null;
}
get command(){
return this.cmd;
}
task(task) {
if(task){
this.msgTask = task;
}
return this.msgTask;
}
meta(metaData) {
if(metaData){
this.msgMeta = metaData;
}
return this.msgMeta;
}
data(data) {
if(data){
this.msgBody = data;
}
return this.msgBody;
}
};

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -26,8 +26,8 @@ requirejs.config({
text: 'lib/requirejs/text', // v2.0.12 A RequireJS/AMD loader plugin for loading text resources.
mustache: 'lib/mustache.min', // v1.0.0 Javascript template engine - http://mustache.github.io
localForage: 'lib/localforage.min', // v1.4.2 localStorage library - https://mozilla.github.io/localForage
velocity: 'lib/velocity.min', // v1.2.2 animation engine - http://julian.com/research/velocity
velocityUI: 'lib/velocity.ui.min', // v5.0.4 plugin for velocity - http://julian.com/research/velocity/#uiPack
velocity: 'lib/velocity.min', // v1.4.1 animation engine - http://julian.com/research/velocity
velocityUI: 'lib/velocity.ui.min', // v5.2.0 plugin for velocity - http://julian.com/research/velocity/#uiPack
slidebars: 'lib/slidebars', // v0.10 Slidebars - side menu plugin http://plugins.adchsm.me/slidebars
jsPlumb: 'lib/dom.jsPlumb-1.7.6', // v1.7.6 jsPlumb (Vanilla)- main map draw plugin https://jsplumbtoolkit.com
farahey: 'lib/farahey-0.5', // v0.5 jsPlumb "magnetizing" extension - https://github.com/jsplumb/farahey

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,162 @@
// "fake" window object will contain "MsgWorker" after import
let window = {}; // jshint ignore:line
// import "MsgWorker" class
self.importScripts( self.name ); // jshint ignore:line
let MsgWorker = window.MsgWorker;
let socket = null;
let ports = [];
let characterPorts = [];
// init "WebSocket" connection ========================================================================================
let initSocket = (uri) => {
let MsgWorkerOpen = new MsgWorker('ws:open');
if(socket === null){
socket = new WebSocket(uri);
// "WebSocket" open -----------------------------------------------------------------------
socket.onopen = (e) => {
MsgWorkerOpen.meta({
readyState: socket.readyState
});
sendToCurrentPort(MsgWorkerOpen);
};
// "WebSocket message ---------------------------------------------------------------------
socket.onmessage = (e) => {
let response = JSON.parse(e.data);
let MsgWorkerSend = new MsgWorker('ws:send');
MsgWorkerSend.task( response.task );
MsgWorkerSend.meta({
readyState: this.readyState,
characterIds: response.characterIds
});
MsgWorkerSend.data( response.load );
broadcastPorts(MsgWorkerSend);
};
// "WebSocket" close ----------------------------------------------------------------------
socket.onclose = (closeEvent) => {
let MsgWorkerClosed = new MsgWorker('ws:closed');
MsgWorkerClosed.meta({
readyState: socket.readyState,
code: closeEvent.code,
reason: closeEvent.reason,
wasClean: closeEvent.wasClean
});
broadcastPorts(MsgWorkerClosed);
socket = null; // reset WebSocket
};
// "WebSocket" error ----------------------------------------------------------------------
socket.onerror = (e) => {
let MsgWorkerError = new MsgWorker('ws:error');
MsgWorkerError.meta({
readyState: socket.readyState
});
sendToCurrentPort(MsgWorkerError);
};
}else{
// socket still open
MsgWorkerOpen.meta({
readyState: socket.readyState
});
sendToCurrentPort(MsgWorkerOpen);
}
};
// send message to port(s) ============================================================================================
let sendToCurrentPort = (load) => {
ports[ports.length - 1].postMessage(load);
};
let broadcastPorts = (load) => {
// default: sent to all ports
let sentToPorts = ports;
// check if send() is limited to some ports
let meta = load.meta();
if(
meta &&
meta.characterIds &&
meta.characterIds !== 'undefined' &&
meta.characterIds instanceof Array
){
// ... get ports for characterIds
sentToPorts = getPortsByCharacterIds(meta.characterIds);
}
for (let i = 0; i < sentToPorts.length; i++) {
sentToPorts[i].postMessage(load);
}
};
// port functions =====================================================================================================
let addPort = (port, characterId) => {
characterId = parseInt(characterId);
if(characterId > 0){
characterPorts.push({
characterId: characterId,
port: port
});
}else{
ports.push(port);
}
};
let getPortsByCharacterIds = (characterIds) => {
let ports = [];
for(let i = 0; i < characterPorts.length; i++){
for(let j = 0; j < characterIds.length; j++){
if(characterPorts[i].characterId === characterIds[j]){
ports.push(characterPorts[i].port);
}
}
}
return ports;
};
// "SharedWorker" connection ==========================================================================================
self.addEventListener('connect', (event) => { // jshint ignore:line
let port = event.ports[0];
addPort(port);
port.addEventListener('message', (e) => {
let MsgWorkerMessage = e.data;
Object.setPrototypeOf(MsgWorkerMessage, MsgWorker.prototype);
switch(MsgWorkerMessage.command){
case 'ws:init':
let data = MsgWorkerMessage.data();
// add character specific port (for broadcast) to individual ports (tabs)
addPort(port, data.characterId);
initSocket(data.uri);
break;
case 'ws:send':
let MsgSocket = {
task: MsgWorkerMessage.task(),
load: MsgWorkerMessage.data()
};
socket.send(JSON.stringify(MsgSocket));
break;
case 'ws:close':
// closeSocket();
break;
}
}, false);
port.start();
}, false);

View File

@@ -0,0 +1,55 @@
window.MsgWorker = class MessageWorker {
constructor(cmd){
/**
* "command" type (identifies this message)
*/
this.cmd = cmd;
/**
* "task" what should be done with this message
* @type {string}
*/
this.msgTask = '';
/**
* "message" meta data (e.g. error/close data from WebSocket
* @type {null}
*/
this.msgMeta = null;
/**
* "message" body (load)
* @type {null}
*/
this.msgBody = null;
}
get command(){
return this.cmd;
}
task(task) {
if(task){
this.msgTask = task;
}
return this.msgTask;
}
meta(metaData) {
if(metaData){
this.msgMeta = metaData;
}
return this.msgMeta;
}
data(data) {
if(data){
this.msgBody = data;
}
return this.msgBody;
}
};

View File

@@ -104,6 +104,7 @@ lib/datatables/Responsive-2.1.0/js/dataTables.responsive.min.js
lib/datatables/Select-1.2.0/js/dataTables.select.min.js
app/module_map.js
app/page.js
app/map/worker.js
app/ui/form_element.js
app/mappage.js
@@ -127,6 +128,7 @@ lib/jquery.hoverIntent.minified.js
lib/bootstrap-confirmation.js
lib/bootstrap2-toggle.min.js
app/util.js
app/map/worker.js
app/setup.js
app/notification.js

Some files were not shown because too many files have changed in this diff Show More