- new "logging" system for map/system/signature/connection changes, closed #271

- new map change log to Slack channel
- new "rally point" logging to Slack channel
- new "rally point" poke options (e.g. custom message), closed #295
- new log options for WebSocket installations
- added ship "mass" logging (backend only), #313
- added map logging to Slack, #326
- added "ESI error rate" limit detection
- added "Monolog" as new logging library (Composer dependency)
- added "Swiftmailer" as new eMail library (Composer dependency)
- added Support for Redis session hander (performance boost)
- improved character select panels (visible "online" status)
- improved "activity logging" (more DB columns added to check)
- improved eMail logging (HTML template support)
- improved "delete map" now become "inactive" for some days before delete
- improved character logout handling
- improved /setup page for DB bootstrap (new button for DB create if not exists)
- fixed broken ship tracking (ship name re-added)
- fixed broken ship tracking for multiple chars on different browser tabs
- fixed broken cursor coordinates, closed #518
- fixed null pointer "charactermodel.php->isActive():925" closed #529
- fixed broken "scroll offset", closed #533 closed #534
- Updated "validation" library JS v0.10.1 -> v0.11.9
- Updated ORM Mapper _Cortex_ v1.5.0-dev -> v1.5.0
- and many more....
This commit is contained in:
Exodus4D
2017-10-22 17:58:33 +02:00
parent d00e550972
commit a8edf39697
235 changed files with 10571 additions and 3515 deletions

1
.gitignore vendored
View File

@@ -55,3 +55,4 @@ Temporary Items
/node_modules/
/public/js/vX.X.X/
/vendor/
/history/

View File

@@ -16,7 +16,7 @@ Mapping tool for [*EVE ONLINE*](https://www.eveonline.com)
- Database will be cleared from time to time
- Installation guide:
- [wiki](https://github.com/exodus4d/pathfinder/wiki)
- Developer chat [Slack](https://slack.com) :
- Developer [Slack](https://slack.com) chat:
- https://pathfinder-eve-online.slack.com
- Please send me a mail for invite: pathfinder@exodus4d.de
@@ -28,44 +28,46 @@ Issues should be reported in the [Issue](https://github.com/exodus4d/pathfinder/
### Project structure
```
|-- (0755) app --> backend [*.php]
|-- app --> "Fat Free Framework" extensions
|-- lib --> "Fat Free Framework"
|-- main --> "PATHFINDER" root
|-- [0755] app/ --> backend [*.php]
|-- app/ --> "Fat Free Framework" extensions
|-- lib/ --> "Fat Free Framework"
|-- main/ --> "PATHFINDER" root
|-- config.ini --> config "f3" framework
|-- cron.ini --> config - cronjobs
|-- environment.ini --> config - system environment
|-- pathfinder.ini --> config - pathfinder
|-- requirements.ini --> config - system requirements
|-- routes.ini --> config - routes
|-- (0755) export --> DB export data
|-- sql --> static DB data for import (pathfinder.sql)
|-- (0755) favicon --> Favicons
|-- (0755) js --> JS source files (raw)
|-- app --> "PASTHFINDER" core files (not used for production)
|-- lib --> 3rd partie extension/library (not used for production)
|-- [0755] export/ --> static data
|-- csv/ --> *.csv used by /setup page
|-- json/ --> *.json used by /setup page
|-- sql/ --> DB dump for import (pathfinder.sql)
|-- [0755] favicon/ --> Favicons
|-- [0777] history/ --> log files (map history logs) [optional]
|-- [0755] js/ --> JS source files (raw)
|-- app/ --> "PASTHFINDER" core files (not used for production)
|-- lib/ --> 3rd partie extension/library (not used for production)
|-- app.js --> require.js config (!required for production!)
|-- (0777) logs --> log files
|-- [0777] logs/ --> log files
|-- ...
| -- node_modules --> node.js modules (not used for production)
| -- node_modules/ --> node.js modules (not used for production)
|-- ...
|-- (0755) public --> frontend source
|-- css --> CSS dist/build folder (minified)
|-- fonts --> (icon)-Fonts
|-- img --> images
|-- js --> JS dist/build folder and source maps (minified, uglified)
|-- templates --> templates
|-- sass --> SCSS source (not used for production)
|-- ...
|-- (0777) tmp --> cache folder
|-- ...
|-- (0755) .htaccess --> reroute/caching rules ("Apache" only!)
|-- (0755) index.php
|-- [0755] public/ --> frontend source
|-- css/ --> CSS dist/build folder (minified)
|-- fonts/ --> (icon)-Fonts
|-- img/ --> images
|-- js/ --> JS dist/build folder and source maps (minified, uglified)
|-- templates/ --> templates
|-- sass/ --> SCSS source (not used for production)
|-- [0777] tmp/ --> cache folder
|-- [0755] .htaccess --> reroute/caching rules ("Apache" only!)
|-- [0755] index.php
--------------------------
CI/CD config files:
--------------------------
|-- .jshintrc --> "JSHint" config (not used for production)
|-- composer.json --> Composer package definition
|-- config.rb --> "Compass" config (not used for production)
|-- gulpfile.js --> "Gulp" task config (not used for production )
|-- package.json --> "Node.js" dependency config (not used for production)

View File

@@ -34,23 +34,23 @@ class Schema extends Controller
$this->f3->set('CACHE', false);
$dbs = array(
/*'mysql' => new \DB\SQL(
'mysql' => new \DB\SQL(
'mysql:host=localhost;port=3306;dbname=fatfree', 'fatfree', ''
),*/
),
'sqlite' => new \DB\SQL(
'sqlite::memory:'
// 'sqlite:db/sqlite.db'
// 'sqlite:db/sqlite.db'
),
/*'pgsql' => new \DB\SQL(
'pgsql' => new \DB\SQL(
'pgsql:host=localhost;dbname=fatfree', 'fatfree', 'fatfree'
),*/
/*'sqlsrv2012' => new \DB\SQL(
'sqlsrv:SERVER=LOCALHOST\SQLEXPRESS2012;Database=fatfree','fatfree', 'fatfree'
),*/
/*'sqlsrv2008' => new \DB\SQL(
'sqlsrv:SERVER=LOCALHOST\SQLEXPRESS2008;Database=fatfree','fatfree', 'fatfree'
)*/
);
),
// 'sqlsrv2012' => new \DB\SQL(
// 'sqlsrv:SERVER=LOCALHOST\SQLEXPRESS2012;Database=fatfree','fatfree', 'fatfree'
// ),
// 'sqlsrv2008' => new \DB\SQL(
// 'sqlsrv:SERVER=LOCALHOST\SQLEXPRESS2008;Database=fatfree','fatfree', 'fatfree'
// )
);
$this->roundTime = microtime(TRUE) - \Base::instance()->get('timer');
$this->tname = 'test_table';
@@ -117,10 +117,27 @@ class Schema extends Controller
$this->getTestDesc('adding column ['.$field.'], nullable')
);
}
$r1 = $table->getCols(true);
foreach (array_keys($schema->dataTypes) as $index => $field) {
if (isset($r1['column_'.$index])) {
$datType=$schema->findQuery($schema->dataTypes[$field]);
$compatible = $schema->isCompatible($field,$r1['column_'.$index]['type']);
$this->test->expect(
$compatible,
$this->getTestDesc('reverse lookup compatible: '.
($compatible?'YES':'NO').
', '.$field.': ['.$datType.' > '.$r1['column_'.$index]['type'].']')
);
}
}
unset($r1);
// adding some testing data
$mapper = new \DB\SQL\Mapper($db, $this->tname);
$mapper->column_5 = 123.456;
$mapper->column_6 = 123456.789012;
$mapper->column_7 = 'hello world';
$mapper->save();
$mapper->reset();
@@ -130,11 +147,33 @@ class Schema extends Controller
$result['column_7'] == 'hello world',
$this->getTestDesc('mapping dummy data')
);
$this->test->expect(
$result['column_5'] == 123.456,
$this->getTestDesc('testing float value: '.$result['column_5'])
);
$this->test->expect(
$result['column_6'] == 123456.789012,
$this->getTestDesc('testing decimal value: '.$result['column_6'])
);
$mapper = new \DB\SQL\Mapper($db, $this->tname);
$mapper->load();
$num = $this->current_engine == 'sqlite' ? '123456789.012345' : '123456789012.345678';
$mapper->column_6 = $num;
$mapper->save();
$mapper->reset();
$result = $mapper->findone(array('column_7 = ?', 'hello world'))->cast();
$this->test->expect(
$result['column_6'] == $num,
$this->getTestDesc('testing max decimal precision: '.$result['column_6'])
);
unset($mapper);
// default value text, not nullable
$table->addColumn('text_default_not_null')
->type($schema::DT_VARCHAR128)
->nullable(false)->defaults('foo bar');
->type($schema::DT_VARCHAR128)
->nullable(false)->defaults('foo bar');
$table->build();
$r1 = $table->getCols(true);
$this->test->expect(
@@ -160,7 +199,7 @@ class Schema extends Controller
// default value numeric, not nullable
$table->addColumn('int_default_not_null')
->type($schema::DT_INT4)->nullable(false)->defaults(123);
->type($schema::DT_INT4)->nullable(false)->defaults(123);
$table->build();
$r1 = $table->getCols(true);
$this->test->expect(
@@ -187,8 +226,8 @@ class Schema extends Controller
// default value text, nullable
$table->addColumn('text_default_nullable')
->type($schema::DT_VARCHAR128)
->defaults('foo bar');
->type($schema::DT_VARCHAR128)
->defaults('foo bar');
$table->build();
$r1 = $table->getCols(true);
$this->test->expect(
@@ -253,9 +292,9 @@ class Schema extends Controller
// current timestamp
$table->addColumn('stamp')
->type($schema::DT_TIMESTAMP)
->nullable(false)
->defaults($schema::DF_CURRENT_TIMESTAMP);
->type($schema::DT_TIMESTAMP)
->nullable(false)
->defaults($schema::DF_CURRENT_TIMESTAMP);
$table->build();
$r1 = $table->getCols(true);
$this->test->expect(
@@ -321,7 +360,7 @@ class Schema extends Controller
$table->renameColumn('title123', 'text_default_not_null');
$table->build();
unset($result,$mapper);
// remove column
$table->dropColumn('column_1');
$table->build();
@@ -380,7 +419,7 @@ class Schema extends Controller
// adding composite primary keys
$table = $schema->createTable($this->tname);
$table->addColumn('version')->type($schema::DT_INT4)
->defaults(1)->nullable(false);
->defaults(1)->nullable(false);
$table->primary(array('id', 'version'));
$table = $table->build();
$r1 = $table->getCols(true);
@@ -399,7 +438,7 @@ class Schema extends Controller
$table->addColumn('title')->type($schema::DT_VARCHAR256);
$table->addColumn('title2')->type($schema::DT_TEXT);
$table->addColumn('title_notnull')
->type($schema::DT_VARCHAR128)->nullable(false)->defaults("foo");
->type($schema::DT_VARCHAR128)->nullable(false)->defaults("foo");
$table->build();
$r1 = $table->getCols(true);
$this->test->expect(
@@ -461,6 +500,18 @@ class Schema extends Controller
$this->getTestDesc('adding items with composite primary-keys')
);
$mapper = new \DB\SQL\Mapper($db, $this->tname);
$mapper->load();
$rec_count_cur = $mapper->loaded();
$schema->truncateTable($this->tname);
$mapper->reset();
$mapper->load();
$rec_count_new = $mapper->loaded();
$this->test->expect(
$rec_count_cur==3 && $rec_count_new == 0,
$this->getTestDesc('truncate table')
);
$schema->dropTable($this->tname);
// indexes
@@ -520,11 +571,54 @@ class Schema extends Controller
$table->updateColumn('bar',$schema::DT_TEXT);
$table->build();
$r1 = $table->getCols(true);
$text = preg_match('/sybase|dblib|odbc|sqlsrv/',$this->current_engine)
? 'nvarchar' : 'text';
$this->test->expect(
array_key_exists('bar', $r1) && $r1['bar']['type'] == 'text',
array_key_exists('bar', $r1) && $r1['bar']['type'] == $text,
$this->getTestDesc('update column')
);
// update column
$cols = $table->getCols(true);
$bar = $cols['bar'];
$col = new \DB\SQL\Column('bar',$table);
$col->copyfrom($bar);
$col->type_varchar(60);
$col->defaults('great');
$table->updateColumn('bar',$col);
$table->build();
$r1 = $table->getCols(true);
$this->test->expect(
array_key_exists('bar', $r1)
&& $r1['bar']['default'] == 'great',
$this->getTestDesc('update column and default')
);
// update column default only
$cols = $table->getCols(true);
$bar = $cols['bar'];
$col = new \DB\SQL\Column('bar',$table);
$col->copyfrom($bar);
$col->passThrough();
$col->defaults('');
$table->updateColumn('bar',$col);
$table->build();
$r1 = $table->getCols(true);
$this->test->expect(
array_key_exists('bar', $r1) && $r1['bar']['default'] == '',
$this->getTestDesc('update default value')
);
$col->nullable(false);
$table->updateColumn('bar',$col);
$table->build();
$r1 = $table->getCols(true);
$this->test->expect(
array_key_exists('bar', $r1) && $r1['bar']['nullable'] == false,
$this->getTestDesc('update nullable flag')
);
// create table with text not nullable column
$table2 = $schema->createTable($this->tname.'_notnulltext');
$table2->addColumn('desc')->type($schema::DT_TEXT)->nullable(false);
@@ -538,7 +632,37 @@ class Schema extends Controller
);
$table2->drop();
// boolean fields are actually bit/tinyint
$schema->dropTable($this->tname.'_notnullbool');
$table2 = $schema->createTable($this->tname.'_notnullbool');
$table2->addColumn('active')->type($schema::DT_BOOL)->nullable(false);
$table2 = $table2->build();
$r1 = $schema->getTables();
$r2 = $table2->getCols(true);
$this->test->expect(
in_array($this->tname.'_notnullbool', $r1) && array_key_exists('active', $r2)
&& $r2['active']['nullable']==false,
$this->getTestDesc('create new table with not nullable boolean column')
);
$table2->addColumn('active2')->type($schema::DT_BOOL)->nullable(false)->defaults(0);
$table2->addColumn('active3')->type($schema::DT_BOOL)->nullable(false)->defaults(1);
$table2->build();
$r1 = $schema->getTables();
$r2 = $table2->getCols(true);
$this->test->expect(
in_array($this->tname.'_notnullbool', $r1)
&& array_key_exists('active2', $r2) && $r2['active2']['nullable']==false &&
((int)$r2['active2']['default']==0||$r2['active2']['default']=='false')
&& array_key_exists('active3', $r2) && $r2['active3']['nullable']==false &&
((int)$r2['active3']['default']==1||$r2['active3']['default']=='true'),
$this->getTestDesc('add not nullable boolean columns with default to existing table')
);
$table2->drop();
}
}

View File

@@ -21,6 +21,13 @@ TZ = UTC
CACHE = folder=tmp/cache/
;CACHE = redis=localhost:6379
; Cache backend used by Session handler.
; default
; -If CACHE is enabled (see above), the same location is used for Session data (e.g. fileCache, RedisDB)
; mysql
; - Session data get stored in your 'PathfinderDB' table 'sessions' (faster)
SESSION_CACHE = mysql
; Callback functions ==============================================================================
ONERROR = Controller\Controller->showError
UNLOAD = Controller\Controller->unload

View File

@@ -14,13 +14,13 @@ BASE =
URL = {{@SCHEME}}://local.pathfinder
; level of debug/error stack trace
DEBUG = 3
; main db
DB_DNS = mysql:host=localhost;port=3306;dbname=
DB_NAME = pathfinder
DB_USER = root
DB_PASS =
; Pathfinder database
DB_PF_DNS = mysql:host=localhost;port=3306;dbname=
DB_PF_NAME = pathfinder
DB_PF_USER = root
DB_PF_PASS =
; EVE-Online CCP Database export
; EVE-Online CCP database export
DB_CCP_DNS = mysql:host=localhost;port=3306;dbname=
DB_CCP_NAME = eve_citadel_min
DB_CCP_USER = root
@@ -60,11 +60,11 @@ BASE =
URL = {{@SCHEME}}://www.pathfinder-w.space
; level of debug/error stack trace
DEBUG = 0
; main db
DB_DNS = mysql:host=localhost;port=3306;dbname=
DB_NAME =
DB_USER =
DB_PASS =
; Pathfinder database
DB_PF_DNS = mysql:host=localhost;port=3306;dbname=
DB_PF_NAME =
DB_PF_USER =
DB_PF_PASS =
; EVE-Online CCP Database export
DB_CCP_DNS = mysql:host=localhost;port=3306;dbname=

View File

@@ -18,8 +18,8 @@
* https://github.com/ikkez/F3-Sugar/
*
* @package DB
* @version 1.5.0-dev
* @date 27.02.2017
* @version 1.5.0
* @date 30.06.2017
* @since 24.04.2012
*/
@@ -312,6 +312,7 @@ class Cortex extends Cursor {
static public function setup($db=null, $table=null, $fields=null) {
/** @var Cortex $self */
$self = get_called_class();
$self::$schema_cache=[];
if (is_null($db) || is_null($table) || is_null($fields))
$df = $self::resolveConfiguration();
if (!is_object($db=(is_string($db=($db?:$df['db']))?\Base::instance()->get($db):$db)))
@@ -836,9 +837,14 @@ class Cortex extends Cursor {
array_unshift($filter,$crit);
}
}
if ($options) {
$options = $this->queryParser->prepareOptions($options,$this->dbsType);
if ($count)
unset($options['order']);
}
return ($count)
? $this->mapper->count($filter,$options,$ttl)
: $this->mapper->find($filter,$this->queryParser->prepareOptions($options,$this->dbsType),$ttl);
: $this->mapper->find($filter,$options,$ttl);
}
/**
@@ -1250,12 +1256,12 @@ class Cortex extends Cursor {
$mmTable = $this->mmTable($relConf,$key);
$filter = array($mmTable.'.'.$relConf['relField']
.' = '.$this->table.'.'.$this->primary);
$from=$mmTable;
$from = $this->db->quotekey($mmTable);
if (array_key_exists($key, $this->relFilter) &&
!empty($this->relFilter[$key][0])) {
$options=array();
$from = $mmTable.' '.$this->_sql_left_join($key,$mmTable,
$relConf['relPK'],$relConf['relTable']);
$from = $this->db->quotekey($mmTable).' '.
$this->_sql_left_join($key,$mmTable,$relConf['relPK'],$relConf['relTable']);
$relFilter = $this->relFilter[$key];
$this->_sql_mergeRelCondition($relFilter,$relConf['relTable'],
$filter,$options);
@@ -1266,8 +1272,8 @@ class Cortex extends Cursor {
if (count($filter)>0)
$this->preBinds=array_merge($this->preBinds,$filter);
$this->mapper->set($alias,
'(select count('.$this->db->quotekey($mmTable.'.'.$relConf['relField']).') from '.
$this->db->quotekey($from).' where '.$crit.
'(select count('.$this->db->quotekey($mmTable.'.'.$relConf['relField']).')'.
' from '.$from.' where '.$crit.
' group by '.$this->db->quotekey($mmTable.'.'.$relConf['relField']).')');
if ($this->whitelist && !in_array($alias,$this->whitelist))
$this->whitelist[] = $alias;
@@ -2444,6 +2450,8 @@ class CortexQueryParser extends \Prefab {
$child = array();
for ($i = 0, $max = count($parts); $i < $max; $i++) {
$part = $parts[$i];
if (is_string($part))
$part = trim($part);
if ($part == '(') {
// add sub-bracket to parse array
if ($b_offset > 0)
@@ -2460,14 +2468,15 @@ class CortexQueryParser extends \Prefab {
else
// add sub-bracket to parse array
$child[] = $part;
} // add to parse array
elseif ($b_offset > 0)
$child[] = $part;
// condition type
elseif (!is_array($part)) {
if (strtoupper(trim($part)) == 'AND')
}
elseif ($b_offset > 0) {
// add to parse array
$child[]=$part;
// condition type
} elseif (!is_array($part)) {
if (strtoupper($part) == 'AND')
$add = true;
elseif (strtoupper(trim($part)) == 'OR')
elseif (strtoupper($part) == 'OR')
$or = true;
} else // skip
$ncond[] = $part;
@@ -2589,7 +2598,7 @@ class CortexQueryParser extends \Prefab {
if (array_key_exists('group', $options) && is_string($options['group'])) {
$keys = explode(',',$options['group']);
$options['group']=array('keys'=>array(),'initial'=>array(),
'reduce'=>'function (obj, prev) {}','finalize'=>'');
'reduce'=>'function (obj, prev) {}','finalize'=>'');
$keys = array_combine($keys,array_fill(0,count($keys),1));
$options['group']['keys']=$keys;
$options['group']['initial']=$keys;

View File

@@ -32,77 +32,77 @@ class Schema extends DB_Utils {
public
$dataTypes = array(
'BOOLEAN' => array('mysql' => 'tinyint(1)',
'sqlite2?|pgsql' => 'BOOLEAN',
'mssql|sybase|dblib|odbc|sqlsrv' => 'bit',
'ibm' => 'numeric(1,0)',
'sqlite2?|pgsql' => 'BOOLEAN',
'mssql|sybase|dblib|odbc|sqlsrv' => 'bit',
'ibm' => 'numeric(1,0)',
),
'INT1' => array('mysql' => 'tinyint(4)',
'sqlite2?' => 'integer(4)',
'mssql|sybase|dblib|odbc|sqlsrv' => 'tinyint',
'pgsql|ibm' => 'smallint',
'sqlite2?' => 'integer(4)',
'mssql|sybase|dblib|odbc|sqlsrv' => 'tinyint',
'pgsql|ibm' => 'smallint',
),
'INT2' => array('mysql' => 'smallint(6)',
'sqlite2?' => 'integer(6)',
'pgsql|ibm|mssql|sybase|dblib|odbc|sqlsrv' => 'smallint',
'sqlite2?' => 'integer(6)',
'pgsql|ibm|mssql|sybase|dblib|odbc|sqlsrv' => 'smallint',
),
'INT4' => array('sqlite2?' => 'integer(11)',
'pgsql|imb' => 'integer',
'mysql' => 'int(11)',
'mssql|dblib|sybase|odbc|sqlsrv' => 'int',
'pgsql|imb' => 'integer',
'mysql' => 'int(11)',
'mssql|dblib|sybase|odbc|sqlsrv' => 'int',
),
'INT8' => array('sqlite2?' => 'integer(20)',
'pgsql|mssql|sybase|dblib|odbc|sqlsrv|imb' => 'bigint',
'mysql' => 'bigint(20)',
'pgsql|mssql|sybase|dblib|odbc|sqlsrv|imb' => 'bigint',
'mysql' => 'bigint(20)',
),
'FLOAT' => array('mysql|sqlite2?' => 'FLOAT',
'pgsql' => 'double precision',
'mssql|sybase|dblib|odbc|sqlsrv' => 'float',
'imb' => 'decfloat'
'pgsql' => 'double precision',
'mssql|sybase|dblib|odbc|sqlsrv' => 'float',
'imb' => 'decfloat'
),
'DOUBLE' => array('mysql|ibm' => 'decimal(18,6)',
'sqlite2?' => 'decimal(15,6)', // max 15-digit on sqlite
'pgsql' => 'numeric(18,6)',
'mssql|dblib|sybase|odbc|sqlsrv' => 'decimal(18,6)',
'sqlite2?' => 'decimal(15,6)', // max 15-digit on sqlite
'pgsql' => 'numeric(18,6)',
'mssql|dblib|sybase|odbc|sqlsrv' => 'decimal(18,6)',
),
'VARCHAR128' => array('mysql|sqlite2?|ibm|mssql|sybase|dblib|odbc|sqlsrv' => 'varchar(128)',
'pgsql' => 'character varying(128)',
'pgsql' => 'character varying(128)',
),
'VARCHAR256' => array('mysql|sqlite2?|ibm|mssql|sybase|dblib|odbc|sqlsrv' => 'varchar(255)',
'pgsql' => 'character varying(255)',
'pgsql' => 'character varying(255)',
),
'VARCHAR512' => array('mysql|sqlite2?|ibm|mssql|sybase|dblib|odbc|sqlsrv' => 'varchar(512)',
'pgsql' => 'character varying(512)',
'pgsql' => 'character varying(512)',
),
'TEXT' => array('mysql|sqlite2?|pgsql|mssql' => 'text',
'sybase|dblib|odbc|sqlsrv' => 'nvarchar(max)',
'ibm' => 'BLOB SUB_TYPE TEXT',
'sybase|dblib|odbc|sqlsrv' => 'nvarchar(max)',
'ibm' => 'BLOB SUB_TYPE TEXT',
),
'LONGTEXT' => array('mysql' => 'LONGTEXT',
'sqlite2?|pgsql|mssql' => 'text',
'sybase|dblib|odbc|sqlsrv' => 'nvarchar(max)',
'ibm' => 'CLOB(2000000000)',
'sqlite2?|pgsql|mssql' => 'text',
'sybase|dblib|odbc|sqlsrv' => 'nvarchar(max)',
'ibm' => 'CLOB(2000000000)',
),
'DATE' => array('mysql|sqlite2?|pgsql|mssql|sybase|dblib|odbc|sqlsrv|ibm' => 'date',
),
'DATETIME' => array('pgsql' => 'timestamp without time zone',
'mysql|sqlite2?|mssql|sybase|dblib|odbc|sqlsrv' => 'datetime',
'ibm' => 'timestamp',
'mysql|sqlite2?|mssql|sybase|dblib|odbc|sqlsrv' => 'datetime',
'ibm' => 'timestamp',
),
'TIMESTAMP' => array('mysql|ibm' => 'timestamp',
'pgsql|odbc' => 'timestamp without time zone',
'sqlite2?|mssql|sybase|dblib|sqlsrv'=>'DATETIME',
'pgsql|odbc' => 'timestamp without time zone',
'sqlite2?|mssql|sybase|dblib|sqlsrv'=>'DATETIME',
),
'BLOB' => array('mysql|odbc|sqlite2?|ibm' => 'blob',
'pgsql' => 'bytea',
'mssql|sybase|dblib' => 'image',
'sqlsrv' => 'varbinary(max)',
'pgsql' => 'bytea',
'mssql|sybase|dblib' => 'image',
'sqlsrv' => 'varbinary(max)',
),
),
$defaultTypes = array(
'CUR_STAMP' => array('mysql' => 'CURRENT_TIMESTAMP',
'mssql|sybase|dblib|odbc|sqlsrv' => 'getdate()',
'pgsql' => 'LOCALTIMESTAMP(0)',
'sqlite2?' => "(datetime('now','localtime'))",
'mssql|sybase|dblib|odbc|sqlsrv' => 'getdate()',
'pgsql' => 'LOCALTIMESTAMP(0)',
'sqlite2?' => "(datetime('now','localtime'))",
),
);

View File

@@ -17,30 +17,30 @@ class AccessController extends Controller {
/**
* event handler
* @param \Base $f3
* @param array $params
* @param $params
* @return bool
*/
function beforeroute(\Base $f3, $params) {
parent::beforeroute($f3, $params);
function beforeroute(\Base $f3, $params): bool {
if($return = parent::beforeroute($f3, $params)){
// Any route/endpoint of a child class of this one,
// requires a valid logged in user!
if( !$this->isLoggedIn($f3) ){
// no character found or login timer expired
$this->logoutCharacter();
// Any route/endpoint of a child class of this one,
// requires a valid logged in user!
$loginCheck = $this->isLoggedIn($f3);
if( !$loginCheck ){
// no user found or login timer expired
$this->logout($f3);
if( $f3->get('AJAX') ){
// unauthorized request
$f3->status(403);
}else{
// redirect to landing page
$f3->reroute(['login']);
if($f3->get('AJAX')){
// unauthorized request
$f3->status(403);
}else{
// redirect to landing page
$f3->reroute(['login']);
}
// skip route handler and afterroute()
$return = false;
}
// die() triggers unload() function
die();
}
return $return;
}
/**
@@ -48,7 +48,7 @@ class AccessController extends Controller {
* @param \Base $f3
* @return bool
*/
protected function isLoggedIn(\Base $f3){
protected function isLoggedIn(\Base $f3): bool {
$loginCheck = false;
if( $character = $this->getCharacter() ){
if($this->checkLogTimer($f3, $character)){
@@ -84,7 +84,7 @@ class AccessController extends Controller {
$minutes += $timeDiff->h * 60;
$minutes += $timeDiff->i;
if($minutes <= $f3->get('PATHFINDER.TIMER.LOGGED')){
if($minutes <= Config::getPathfinderData('timer.logged')){
$loginCheck = true;
}
}

View File

@@ -32,9 +32,11 @@ class Admin extends Controller{
* event handler for all "views"
* some global template variables are set in here
* @param \Base $f3
* @param $params
* @return bool
*/
function beforeroute(\Base $f3, $params) {
parent::beforeroute($f3, $params);
function beforeroute(\Base $f3, $params): bool {
$return = parent::beforeroute($f3, $params);
$f3->set('tplPage', 'login');
@@ -54,6 +56,8 @@ class Admin extends Controller{
// body element class
$f3->set('tplBodyClass', 'pf-landing');
return $return;
}
/**
@@ -291,7 +295,9 @@ class Admin extends Controller{
}
foreach($corporations as $corporation){
$data->corpMembers[$corporation->name] = $corporation->getCharacters();
if($characters = $corporation->getCharacters()){
$data->corpMembers[$corporation->name] = $corporation->getCharacters();
}
}
// sort corporation from current user first

View File

@@ -12,16 +12,6 @@ use Model;
class Access extends Controller\AccessController {
/**
* event handler
* @param \Base $f3
* @param array $params
*/
function beforeroute(\Base $f3, $params) {
// set header for all routes
header('Content-type: application/json');
parent::beforeroute($f3, $params);
}
/**
* search character/corporation or alliance by name

View File

@@ -12,15 +12,6 @@ use Model;
class Connection extends Controller\AccessController {
/**
* @param \Base $f3
* @param array $params
*/
function beforeroute(\Base $f3, $params) {
// set header for all routes
header('Content-type: application/json');
parent::beforeroute($f3, $params);
}
/**
* save a new connection or updates an existing (drag/drop) between two systems
@@ -29,7 +20,10 @@ class Connection extends Controller\AccessController {
*/
public function save(\Base $f3){
$postData = (array)$f3->get('POST');
$newConnectionData = [];
$return = (object) [];
$return->error = [];
$return->connectionData = (object) [];
if(
isset($postData['connectionData']) &&
@@ -75,27 +69,23 @@ class Connection extends Controller\AccessController {
$connectionData['scope'] = 'wh';
$connectionData['type'] = ['wh_fresh'];
}
$connectionData['mapId'] = $map;
// "updated" should not be set by client e.g. after manual drag&drop
unset($connectionData['updated']);
$connection->setData($connectionData);
if( $connection->isValid() ){
$connection->save();
$newConnectionData = $connection->getData();
if($connection->save($activeCharacter)){
$return->connectionData = $connection->getData();
// broadcast map changes
$this->broadcastMapData($connection->mapId);
}else{
$return->error = $connection->getErrors();
}
}
}
}
echo json_encode($newConnectionData);
echo json_encode($return);
}
/**

View File

@@ -7,7 +7,7 @@
*/
namespace Controller\Api;
use Model;
use lib\Config;
use Controller;
@@ -43,7 +43,7 @@ class GitHub extends Controller\Controller {
$releaseCount = 4;
if( !$f3->exists($cacheKey) ){
$apiPath = $this->getF3()->get('PATHFINDER.API.GIT_HUB') . '/repos/exodus4d/pathfinder/releases';
$apiPath = Config::getPathfinderData('api.git_hub') . '/repos/exodus4d/pathfinder/releases';
// build request URL
$options = $this->getRequestOptions();

View File

@@ -8,9 +8,11 @@
namespace Controller\Api;
use Controller;
use data\file\FileHandler;
use lib\Config;
use lib\Socket;
use Model;
use Exception;
/**
* Map controller
@@ -23,24 +25,15 @@ class Map extends Controller\AccessController {
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';
const CACHE_KEY_HISTORY = 'CACHED_MAP_HISTORY_%s';
/**
* event handler
* @param \Base $f3
* @param array $params
*/
function beforeroute(\Base $f3, $params) {
// set header for all routes
header('Content-type: application/json');
parent::beforeroute($f3, $params);
}
/**
* get map data cache key
* @param Model\CharacterModel $character
* @return string
*/
protected function getMapDataCacheKey(Model\CharacterModel $character){
protected function getMapDataCacheKey(Model\CharacterModel $character): string {
return sprintf(self::CACHE_KEY_MAP_DATA, 'CHAR_' . $character->_id);
}
@@ -62,10 +55,19 @@ class Map extends Controller\AccessController {
* @param int $systemId
* @return string
*/
protected function getUserDataCacheKey($mapId, $systemId = 0){
protected function getUserDataCacheKey($mapId, $systemId = 0): string {
return sprintf(self::CACHE_KEY_USER_DATA, 'MAP_' . $mapId, 'SYS_' . $systemId);
}
/**
* get log history data cache key
* @param int $mapId
* @return string
*/
protected function getHistoryDataCacheKey(int $mapId): string {
return sprintf(self::CACHE_KEY_HISTORY, 'MAP_' . $mapId);
}
/**
* Get all required static config data for program initialization
* @param \Base $f3
@@ -79,10 +81,10 @@ class Map extends Controller\AccessController {
$return = (object) [];
$return->error = [];
// static program data ----------------------------------------------------------------------------------------
$return->timer = $f3->get('PATHFINDER.TIMER');
// static program data ------------------------------------------------------------------------------------
$return->timer = Config::getPathfinderData('timer');
// get all available map types --------------------------------------------------------------------------------
// get all available map types ----------------------------------------------------------------------------
$mapType = Model\BasicModel::getNew('MapTypeModel');
$rows = $mapType->find('active = 1', null, $expireTimeSQL);
@@ -103,7 +105,7 @@ class Map extends Controller\AccessController {
}
$return->mapTypes = $mapTypeData;
// get all available map scopes -------------------------------------------------------------------------------
// get all available map scopes ---------------------------------------------------------------------------
$mapScope = Model\BasicModel::getNew('MapScopeModel');
$rows = $mapScope->find('active = 1', null, $expireTimeSQL);
$mapScopeData = [];
@@ -116,7 +118,7 @@ class Map extends Controller\AccessController {
}
$return->mapScopes = $mapScopeData;
// get all available system status ----------------------------------------------------------------------------
// get all available system status ------------------------------------------------------------------------
$systemStatus = Model\BasicModel::getNew('SystemStatusModel');
$rows = $systemStatus->find('active = 1', null, $expireTimeSQL);
$systemScopeData = [];
@@ -130,7 +132,7 @@ class Map extends Controller\AccessController {
}
$return->systemStatus = $systemScopeData;
// get all available system types -----------------------------------------------------------------------------
// get all available system types -------------------------------------------------------------------------
$systemType = Model\BasicModel::getNew('SystemTypeModel');
$rows = $systemType->find('active = 1', null, $expireTimeSQL);
$systemTypeData = [];
@@ -143,7 +145,7 @@ class Map extends Controller\AccessController {
}
$return->systemType = $systemTypeData;
// get available connection scopes ----------------------------------------------------------------------------
// get available connection scopes ------------------------------------------------------------------------
$connectionScope = Model\BasicModel::getNew('ConnectionScopeModel');
$rows = $connectionScope->find('active = 1', null, $expireTimeSQL);
$connectionScopeData = [];
@@ -157,7 +159,7 @@ class Map extends Controller\AccessController {
}
$return->connectionScopes = $connectionScopeData;
// get available character status -----------------------------------------------------------------------------
// get available character status -------------------------------------------------------------------------
$characterStatus = Model\BasicModel::getNew('CharacterStatusModel');
$rows = $characterStatus->find('active = 1', null, $expireTimeSQL);
$characterStatusData = [];
@@ -171,21 +173,27 @@ class Map extends Controller\AccessController {
}
$return->characterStatus = $characterStatusData;
// route search config ----------------------------------------------------------------------------------------
// 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'),
'defaultCount' => Config::getPathfinderData('route.search_default_count'),
'maxDefaultCount' => Config::getPathfinderData('route.max_default_count'),
'limit' => Config::getPathfinderData('route.limit')
];
// get program routes -----------------------------------------------------------------------------------------
// get program routes -------------------------------------------------------------------------------------
$return->routes = [
'ssoLogin' => $this->getF3()->alias( 'sso', ['action' => 'requestAuthorization'] )
];
// get notification status ------------------------------------------------------------------------------------
$return->notificationStatus = [
'rallySet' => (bool)Config::getNotificationMail('RALLY_SET')
// get third party APIs -----------------------------------------------------------------------------------
$return->url = [
'ccpImageServer' => Config::getPathfinderData('api.ccp_image_server'),
'zKillboard' => Config::getPathfinderData('api.z_killboard')
];
// Slack integration status -------------------------------------------------------------------------------
$return->slack = [
'status' => (bool)Config::getPathfinderData('slack.status')
];
$f3->set(self::CACHE_KEY_INIT, $return, $expireTimeCache );
@@ -195,7 +203,7 @@ class Map extends Controller\AccessController {
// program mode (e.g. "maintenance") --------------------------------------------------------------------------
$return->programMode = [
'maintenance' => $this->getF3()->get('PATHFINDER.LOGIN.MODE_MAINTENANCE')
'maintenance' => Config::getPathfinderData('login.mode_maintenance')
];
// get SSO error messages that should be shown immediately ----------------------------------------------------
@@ -263,17 +271,12 @@ class Map extends Controller\AccessController {
isset($mapData['data']['systems']) &&
isset($mapData['data']['connections'])
){
if(isset($mapData['config']['id'])){
unset($mapData['config']['id']);
}
$systemCount = count($mapData['data']['systems']);
if( $systemCount <= $defaultConfig['max_systems']){
$map->setData($mapData['config']);
$map->typeId = (int)$importData['typeId'];
$map->save();
$map->save($activeCharacter);
// new system IDs will be generated
// therefore we need to temp store a mapping between IDs
@@ -282,13 +285,10 @@ class Map extends Controller\AccessController {
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();
$system->save($activeCharacter);
$tempSystemIdMapping[$oldId] = $system->id;
$system->reset();
@@ -301,15 +301,11 @@ class Map extends Controller\AccessController {
isset( $tempSystemIdMapping[$connectionData['source']] ) &&
isset( $tempSystemIdMapping[$connectionData['target']] )
){
if(isset($connectionData['id'])){
unset($connectionData['id']);
}
$connection->setData($connectionData);
$connection->mapId = $map;
$connection->source = $tempSystemIdMapping[$connectionData['source']];
$connection->target = $tempSystemIdMapping[$connectionData['target']];
$connection->save();
$connection->save($activeCharacter);
$connection->reset();
}
@@ -391,150 +387,161 @@ class Map extends Controller\AccessController {
$map->dry() ||
$map->hasAccess($activeCharacter)
){
// new map
$map->setData($formData);
$map = $map->save();
try{
// new map
$map->setData($formData);
$map = $map->save($activeCharacter);
// save global map access. Depends on map "type"
if($map->isPrivate()){
$mapDefaultConf = Config::getMapsDefaultConfig();
// share map between characters -> set access
if(isset($formData['mapCharacters'])){
// remove character corporation (re-add later)
$accessCharacters = array_diff($formData['mapCharacters'], [$activeCharacter->_id]);
// save global map access. Depends on map "type"
if($map->isPrivate()){
// avoid abuse -> respect share limits
$maxShared = max($f3->get('PATHFINDER.MAP.PRIVATE.MAX_SHARED') - 1, 0);
$accessCharacters = array_slice($accessCharacters, 0, $maxShared);
// clear map access. In case something has removed from access list
$map->clearAccess();
if($accessCharacters){
/**
* @var $tempCharacter Model\CharacterModel
*/
$tempCharacter = Model\BasicModel::getNew('CharacterModel');
foreach($accessCharacters as $characterId){
$tempCharacter->getById( (int)$characterId );
if(
!$tempCharacter->dry() &&
$tempCharacter->shared == 1 // check if map shared is enabled
){
$map->setAccess($tempCharacter);
}
$tempCharacter->reset();
}
}
}
// the current character itself should always have access
// just in case he removed himself :)
$map->setAccess($activeCharacter);
}elseif($map->isCorporation()){
$corporation = $activeCharacter->getCorporation();
if($corporation){
// the current user has to have a corporation when
// working on corporation maps!
// share map between corporations -> set access
if(isset($formData['mapCorporations'])){
// share map between characters -> set access
if(isset($formData['mapCharacters'])){
// remove character corporation (re-add later)
$accessCorporations = array_diff($formData['mapCorporations'], [$corporation->_id]);
$accessCharacters = array_diff($formData['mapCharacters'], [$activeCharacter->_id]);
// avoid abuse -> respect share limits
$maxShared = max($f3->get('PATHFINDER.MAP.CORPORATION.MAX_SHARED') - 1, 0);
$accessCorporations = array_slice($accessCorporations, 0, $maxShared);
$maxShared = max($mapDefaultConf['private']['max_shared'] - 1, 0);
$accessCharacters = array_slice($accessCharacters, 0, $maxShared);
// clear map access. In case something has removed from access list
$map->clearAccess();
if($accessCorporations){
if($accessCharacters){
/**
* @var $tempCorporation Model\CorporationModel
* @var $tempCharacter Model\CharacterModel
*/
$tempCorporation = Model\BasicModel::getNew('CorporationModel');
$tempCharacter = Model\BasicModel::getNew('CharacterModel');
foreach($accessCorporations as $corporationId){
$tempCorporation->getById( (int)$corporationId );
foreach($accessCharacters as $characterId){
$tempCharacter->getById( (int)$characterId );
if(
!$tempCorporation->dry() &&
$tempCorporation->shared == 1 // check if map shared is enabled
!$tempCharacter->dry() &&
$tempCharacter->shared == 1 // check if map shared is enabled
){
$map->setAccess($tempCorporation);
$map->setAccess($tempCharacter);
}
$tempCorporation->reset();
$tempCharacter->reset();
}
}
}
// the corporation of the current user should always have access
$map->setAccess($corporation);
}
}elseif($map->isAlliance()){
$alliance = $activeCharacter->getAlliance();
// the current character itself should always have access
// just in case he removed himself :)
$map->setAccess($activeCharacter);
}elseif($map->isCorporation()){
$corporation = $activeCharacter->getCorporation();
if($alliance){
// the current user has to have a alliance when
// working on alliance maps!
if($corporation){
// the current user has to have a corporation when
// working on corporation maps!
// share map between alliances -> set access
if(isset($formData['mapAlliances'])){
// remove character alliance (re-add later)
$accessAlliances = array_diff($formData['mapAlliances'], [$alliance->_id]);
// share map between corporations -> set access
if(isset($formData['mapCorporations'])){
// remove character corporation (re-add later)
$accessCorporations = array_diff($formData['mapCorporations'], [$corporation->_id]);
// avoid abuse -> respect share limits
$maxShared = max($f3->get('PATHFINDER.MAP.ALLIANCE.MAX_SHARED') - 1, 0);
$accessAlliances = array_slice($accessAlliances, 0, $maxShared);
// avoid abuse -> respect share limits
$maxShared = max($mapDefaultConf['corporation']['max_shared'] - 1, 0);
$accessCorporations = array_slice($accessCorporations, 0, $maxShared);
// 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
*/
$tempAlliance = Model\BasicModel::getNew('AllianceModel');
if($accessCorporations){
/**
* @var $tempCorporation Model\CorporationModel
*/
$tempCorporation = Model\BasicModel::getNew('CorporationModel');
foreach($accessAlliances as $allianceId){
$tempAlliance->getById( (int)$allianceId );
foreach($accessCorporations as $corporationId){
$tempCorporation->getById( (int)$corporationId );
if(
!$tempAlliance->dry() &&
$tempAlliance->shared == 1 // check if map shared is enabled
){
$map->setAccess($tempAlliance);
if(
!$tempCorporation->dry() &&
$tempCorporation->shared == 1 // check if map shared is enabled
){
$map->setAccess($tempCorporation);
}
$tempCorporation->reset();
}
$tempAlliance->reset();
}
}
}
// the alliance of the current user should always have access
$map->setAccess($alliance);
// the corporation of the current user should always have access
$map->setAccess($corporation);
}
}elseif($map->isAlliance()){
$alliance = $activeCharacter->getAlliance();
if($alliance){
// the current user has to have a alliance when
// working on alliance maps!
// share map between alliances -> set access
if(isset($formData['mapAlliances'])){
// remove character alliance (re-add later)
$accessAlliances = array_diff($formData['mapAlliances'], [$alliance->_id]);
// avoid abuse -> respect share limits
$maxShared = max($mapDefaultConf['alliance']['max_shared'] - 1, 0);
$accessAlliances = array_slice($accessAlliances, 0, $maxShared);
// clear map access. In case something has removed from access list
$map->clearAccess();
if($accessAlliances){
/**
* @var $tempAlliance Model\AllianceModel
*/
$tempAlliance = Model\BasicModel::getNew('AllianceModel');
foreach($accessAlliances as $allianceId){
$tempAlliance->getById( (int)$allianceId );
if(
!$tempAlliance->dry() &&
$tempAlliance->shared == 1 // check if map shared is enabled
){
$map->setAccess($tempAlliance);
}
$tempAlliance->reset();
}
}
}
// the alliance of the current user should always have access
$map->setAccess($alliance);
}
}
// reload the same map model (refresh)
// this makes sure all data is up2date
$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();
}catch(Exception\ValidationException $e){
$validationError = (object) [];
$validationError->type = 'error';
$validationError->field = $e->getField();
$validationError->message = $e->getMessage();
$return->error[] = $validationError;
}
// reload the same map model (refresh)
// this makes sure all data is up2date
$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{
// map access denied
$captchaError = (object) [];
@@ -559,18 +566,30 @@ class Map extends Controller\AccessController {
*/
public function delete(\Base $f3){
$mapData = (array)$f3->get('POST.mapData');
$activeCharacter = $this->getCharacter();
$mapId = (int)$mapData['id'];
$return = (object) [];
$return->deletedMapIds = [];
/**
* @var $map Model\MapModel
*/
$map = Model\BasicModel::getNew('MapModel');
$map->getById($mapData['id']);
$map->delete( $activeCharacter, function($mapId){
$this->broadcastMapDeleted($mapId);
});
if($mapId){
$activeCharacter = $this->getCharacter();
echo json_encode([]);
/**
* @var $map Model\MapModel
*/
$map = Model\BasicModel::getNew('MapModel');
$map->getById($mapId);
if($map->hasAccess($activeCharacter)){
$map->setActive(false);
$map->save($activeCharacter);
$return->deletedMapIds[] = $mapId;
// broadcast map delete
$this->broadcastMapDeleted($mapId);
}
}
echo json_encode($return);
}
/**
@@ -654,7 +673,7 @@ class Map extends Controller\AccessController {
$return = (object) [];
$return->error = [];
// get current map data ===============================================================================
// get current map data ===================================================================================
$maps = $activeCharacter->getMaps();
// loop all submitted map data that should be saved
@@ -680,13 +699,13 @@ class Map extends Controller\AccessController {
count($connections) > 0
){
// map changes expected =======================================================================
// map changes expected ===========================================================================
// loop current user maps and check for changes
foreach($maps as $map){
$mapChanged = false;
// update system data ---------------------------------------------------------------------
// update system data -------------------------------------------------------------------------
foreach($systems as $i => $systemData){
// check if current system belongs to the current map
@@ -703,25 +722,25 @@ class Map extends Controller\AccessController {
// system belongs to the current map
if(is_object($filteredMap->systems)){
// update
unset($systemData['updated']);
/**
* @var $system Model\SystemModel
*/
$system = $filteredMap->systems->current();
$system->setData($systemData);
$system->updatedCharacterId = $activeCharacter;
$system->save();
$mapChanged = true;
// a system belongs to ONE map -> speed up for multiple maps
unset($systemData[$i]);
if($system->save($activeCharacter)){
$mapChanged = true;
// one system belongs to ONE map -> speed up for multiple maps
unset($systemData[$i]);
}else{
$return->error = array_merge($return->error, $system->getErrors());
}
}
}
}
// update connection data -----------------------------------------------------------------
// update connection data ---------------------------------------------------------------------
foreach($connections as $i => $connectionData){
// check if the current connection belongs to the current map
@@ -738,19 +757,20 @@ class Map extends Controller\AccessController {
// connection belongs to the current map
if(is_object($filteredMap->connections)){
// update
unset($connectionData['updated']);
/**
* @var $connection Model\ConnectionModel
*/
$connection = $filteredMap->connections->current();
$connection->setData($connectionData);
$connection->save();
$mapChanged = true;
// a connection belongs to ONE map -> speed up for multiple maps
unset($connectionData[$i]);
if($connection->save($activeCharacter)){
$mapChanged = true;
// one connection belongs to ONE map -> speed up for multiple maps
unset($connectionData[$i]);
}else{
$return->error = array_merge($return->error, $connection->getErrors());
}
}
}
}
@@ -767,7 +787,7 @@ class Map extends Controller\AccessController {
// 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;
$responseTTL = (int)Config::getPathfinderData('timer.update_server_map.delay') / 1000;
$f3->set($cacheKey, $return, $responseTTL);
}
@@ -852,7 +872,7 @@ class Map extends Controller\AccessController {
// cache time (seconds) should be equal or less than request trigger time
// prevent request flooding
$responseTTL = (int)$f3->get('PATHFINDER.TIMER.UPDATE_SERVER_USER_DATA.DELAY') / 1000;
$responseTTL = (int)Config::getPathfinderData('timer.update_server_user_data.delay') / 1000;
// cache response
$f3->set($cacheKey, $return, $responseTTL);
@@ -1003,13 +1023,13 @@ class Map extends Controller\AccessController {
break;
}
// save source system -------------------------------------------------------------------------------------
// save source system ---------------------------------------------------------------------------------
if(
$addSourceSystem &&
$sourceSystem &&
!$sourceExists
){
$sourceSystem = $map->saveSystem($sourceSystem, $systemPosX, $systemPosY, $character);
$sourceSystem = $map->saveSystem($sourceSystem, $character, $systemPosX, $systemPosY);
// get updated maps object
if($sourceSystem){
$map = $sourceSystem->mapId;
@@ -1021,13 +1041,13 @@ class Map extends Controller\AccessController {
}
}
// save target system -------------------------------------------------------------------------------------
// save target system ---------------------------------------------------------------------------------
if(
$addTargetSystem &&
$targetSystem &&
!$targetExists
){
$targetSystem = $map->saveSystem($targetSystem, $systemPosX, $systemPosY, $character);
$targetSystem = $map->saveSystem($targetSystem, $character, $systemPosX, $systemPosY);
// get updated maps object
if($targetSystem){
$map = $targetSystem->mapId;
@@ -1036,7 +1056,7 @@ class Map extends Controller\AccessController {
}
}
// save connection ----------------------------------------------------------------------------------------
// save connection ------------------------------------------------------------------------------------
if(
$addConnection &&
$sourceExists &&
@@ -1046,7 +1066,7 @@ class Map extends Controller\AccessController {
!$map->searchConnection( $sourceSystem, $targetSystem )
){
$connection = $map->getNewConnection($sourceSystem, $targetSystem);
$connection = $map->saveConnection($connection);
$connection = $map->saveConnection($connection, $character);
// get updated maps object
if($connection){
$map = $connection->mapId;
@@ -1097,6 +1117,53 @@ class Map extends Controller\AccessController {
echo json_encode($connectionData);
}
/**
* get map log data
* @param \Base $f3
*/
public function getLogData(\Base $f3){
$postData = (array)$f3->get('POST');
$return = (object) [];
$return->data = [];
// validate query parameters
$return->query = [
'mapId' => (int) $postData['mapId'],
'offset' => FileHandler::validateOffset( (int)$postData['offset'] ),
'limit' => FileHandler::validateLimit( (int)$postData['limit'] )
];
if($mapId = (int)$postData['mapId']){
$activeCharacter = $this->getCharacter();
/**
* @var Model\MapModel $map
*/
$map = Model\BasicModel::getNew('MapModel');
$map->getById($mapId);
if($map->hasAccess($activeCharacter)){
$cacheKey = $this->getHistoryDataCacheKey($mapId);
if($return->query['offset'] === 0){
// check cache
$return->data = $f3->get($cacheKey);
}
if(empty($return->data)){
$return->data = $map->getLogData($return->query['offset'], $return->query['limit']);
if(
$return->query['offset'] === 0 &&
!empty($return->data))
{
$f3->set($cacheKey, $return->data, (int)Config::getPathfinderData('history.cache'));
}
}
}
}
echo json_encode($return);
}
}

View File

@@ -8,6 +8,7 @@
namespace Controller\Api;
use Controller;
use lib\Config;
use Model;
@@ -530,7 +531,7 @@ class Route extends Controller\AccessController {
$map = Model\BasicModel::getNew('MapModel');
// limit max search routes to max limit
array_splice($routesData, $f3->get('PATHFINDER.ROUTE.LIMIT'));
array_splice($routesData, Config::getPathfinderData('route.limit'));
foreach($routesData as $key => $routeData){
// mapIds are optional. If mapIds is empty or not set
@@ -609,7 +610,7 @@ class Route extends Controller\AccessController {
$returnRoutData = $cachedData;
}else{
// max search depth for search
$searchDepth = $f3->get('PATHFINDER.ROUTE.SEARCH_DEPTH');
$searchDepth = Config::getPathfinderData('route.search_depth');
// set jump data for following route search
// --> don´t filter some systems (e.g. systemFrom, systemTo) even if they are are WH,LS,0.0

View File

@@ -13,16 +13,6 @@ use Model;
class Signature extends Controller\AccessController {
/**
* event handler
* @param \Base $f3
* @param array $params
*/
function beforeroute(\Base $f3, $params) {
// set header for all routes
header('Content-type: application/json');
parent::beforeroute($f3, $params);
}
/**
* get signature data for systems
@@ -120,8 +110,6 @@ class Signature extends Controller\AccessController {
if($signature->dry()){
// new signature
$signature->systemId = $system;
$signature->updatedCharacterId = $activeCharacter;
$signature->createdCharacterId = $activeCharacter;
$signature->setData($data);
}else{
// update signature
@@ -181,13 +169,11 @@ class Signature extends Controller\AccessController {
}
if( $signature->hasChanged($newData) ){
// Character should only be changed if something else has changed
$signature->updatedCharacterId = $activeCharacter;
$signature->setData($newData);
}
}
$signature->save();
$signature->save($activeCharacter);
$updatedSignatureIds[] = $signature->id;
// get a fresh signature object with the new data. This is a bad work around!

View File

@@ -9,6 +9,7 @@
namespace controller\api;
use Controller;
use lib\Config;
use Model\CharacterModel;
class Statistic extends Controller\AccessController {
@@ -131,19 +132,19 @@ class Statistic extends Controller\AccessController {
$objectId = 0;
// add map-"typeId" (private/corp/ally) condition -------------------------------------------------------------
// check if "ACTIVITY_LOGGING" is active for a given "typeId"
// check if "LOG_ACTIVITY_ENABLED" is active for a given "typeId"
$sqlMapType = "";
switch($typeId){
case 2:
if( $this->getF3()->get('PATHFINDER.MAP.PRIVATE.ACTIVITY_LOGGING') ){
if( Config::getMapsDefaultConfig('private')['log_activity_enabled'] ){
$sqlMapType .= " AND `character`.`id` = :objectId ";
$objectId = $character->_id;
}
break;
case 3:
if(
$this->getF3()->get('PATHFINDER.MAP.CORPORATION.ACTIVITY_LOGGING') &&
Config::getMapsDefaultConfig('corporation')['log_activity_enabled'] &&
$character->hasCorporation()
){
$sqlMapType .= " AND `character`.`corporationId` = :objectId ";
@@ -152,7 +153,7 @@ class Statistic extends Controller\AccessController {
break;
case 4:
if(
$this->getF3()->get('PATHFINDER.MAP.ALLIANCE.ACTIVITY_LOGGING') &&
Config::getMapsDefaultConfig('alliance')['log_activity_enabled'] &&
$character->hasAlliance()
){
$sqlMapType .= " AND `character`.`allianceId` = :objectId ";
@@ -181,6 +182,9 @@ class Statistic extends Controller\AccessController {
`log`.`characterId`,
`character`.`name`,
`character`.`lastLogin`,
SUM(`log`.`mapCreate`) `mapCreate`,
SUM(`log`.`mapUpdate`) `mapUpdate`,
SUM(`log`.`mapDelete`) `mapDelete`,
SUM(`log`.`systemCreate`) `systemCreate`,
SUM(`log`.`systemUpdate`) `systemUpdate`,
SUM(`log`.`systemDelete`) `systemDelete`,

View File

@@ -8,8 +8,8 @@
namespace Controller\Api;
use Controller;
use Controller\Ccp\Sso;
use Data\Mapper as Mapper;
use lib\Config;
use Model;
class System extends Controller\AccessController {
@@ -65,16 +65,6 @@ class System extends Controller\AccessController {
private $limitQuery = "";
/**
* @param \Base $f3
* @param array $params
*/
function beforeroute(\Base $f3, $params) {
parent::beforeroute($f3, $params);
// set header for all routes
header('Content-type: application/json');
}
/**
* build query
@@ -187,9 +177,12 @@ class System extends Controller\AccessController {
* @param \Base $f3
*/
public function save(\Base $f3){
$newSystemData = [];
$postData = (array)$f3->get('POST');
$return = (object) [];
$return->error = [];
$return->systemData = (object) [];
if(
isset($postData['systemData']) &&
isset($postData['mapData'])
@@ -229,23 +222,21 @@ class System extends Controller\AccessController {
*/
$map = Model\BasicModel::getNew('MapModel');
$map->getById($mapData['id']);
if(
!$map->dry() &&
$map->hasAccess($activeCharacter)
){
if( $map->hasAccess($activeCharacter) ){
// make sure system is not already on map
// --> (e.g. multiple simultaneously save() calls for the same system)
$systemModel = $map->getSystemByCCPId($systemData['systemId']);
if( is_null($systemModel) ){
// system not found on map -> get static system data (CCP DB)
$systemModel = $map->getNewSystem($systemData['systemId']);
$systemModel->createdCharacterId = $activeCharacter;
$systemModel->statusId = isset($systemData['statusId']) ? $systemData['statusId'] : 1;
$defaultStatusId = 1;
}else{
// system already exists (e.g. was inactive)
$systemModel->statusId = isset($systemData['statusId']) ? $systemData['statusId'] : $systemModel->statusId;
$defaultStatusId = $systemModel->statusId;
}
$systemModel->statusId = isset($systemData['statusId']) ? $systemData['statusId'] : $defaultStatusId;
// map is not changeable for a system! (security)
$systemData['mapId'] = $map;
}
@@ -255,28 +246,32 @@ class System extends Controller\AccessController {
// "statusId" was set above
unset($systemData['statusId']);
unset($systemData['mapId']);
unset($systemData['createdCharacterId']);
unset($systemData['updatedCharacterId']);
// set/update system
$systemModel->setData($systemData);
// activate system (e.g. was inactive))
$systemModel->setActive(true);
$systemModel->updatedCharacterId = $activeCharacter;
$systemModel->save();
// get data from "fresh" model (e.g. some relational data has changed: "statusId")
$newSystemModel = Model\BasicModel::getNew('SystemModel');
$newSystemModel->getById( $systemModel->id, 0);
$newSystemModel->clearCacheData();
$newSystemData = $newSystemModel->getData();
// broadcast map changes
$this->broadcastMapData($newSystemModel->mapId);
if($systemModel->save($activeCharacter)){
// get data from "fresh" model (e.g. some relational data has changed: "statusId")
/**
* @var $newSystemModel Model\SystemModel
*/
$newSystemModel = Model\BasicModel::getNew('SystemModel');
$newSystemModel->getById( $systemModel->id, 0);
$newSystemModel->clearCacheData();
$return->systemData = $newSystemModel->getData();
// broadcast map changes
$this->broadcastMapData($newSystemModel->mapId);
}else{
$return->error = $systemModel->getErrors();
}
}
}
echo json_encode($newSystemData);
echo json_encode($return);
}
/**
@@ -358,7 +353,7 @@ class System extends Controller\AccessController {
$return->systemData[] = $systemModel->getData();
}
$f3->set($cacheKey, $return->systemData, $f3->get('PATHFINDER.CACHE.CONSTELLATION_SYSTEMS') );
$f3->set($cacheKey, $return->systemData, Config::getPathfinderData('cache.constellation_systems'));
}
}
@@ -407,6 +402,37 @@ class System extends Controller\AccessController {
echo json_encode($return);
}
/**
* send Rally Point poke
* @param \Base $f3
*/
public function pokeRally(\Base $f3){
$rallyData = (array)$f3->get('POST');
$systemId = (int)$rallyData['systemId'];
$return = (object) [];
if($systemId){
$activeCharacter = $this->getCharacter();
/**
* @var Model\SystemModel $system
*/
$system = Model\BasicModel::getNew('SystemModel');
$system->getById($systemId);
if($system->hasAccess($activeCharacter)){
$rallyData['pokeDesktop'] = $rallyData['pokeDesktop'] === '1';
$rallyData['pokeMail'] = $rallyData['pokeMail'] === '1';
$rallyData['pokeSlack'] = $rallyData['pokeSlack'] === '1';
$rallyData['message'] = trim($rallyData['message']);
$system->sendRallyPoke($rallyData, $activeCharacter);
}
}
echo json_encode($return);
}
/**
* delete systems and all its connections from map
* -> set "active" flag
@@ -428,7 +454,7 @@ class System extends Controller\AccessController {
$map = Model\BasicModel::getNew('MapModel');
$map->getById($mapId);
if( $map->hasAccess($activeCharacter) ){
if($map->hasAccess($activeCharacter)){
foreach($systemIds as $systemId){
if( $system = $map->getSystemById($systemId) ){
// check whether system should be deleted OR set "inactive"
@@ -437,7 +463,7 @@ class System extends Controller\AccessController {
}else{
// keep data -> set "inactive"
$system->setActive(false);
$system->save();
$system->save($activeCharacter);
}
$system->reset();

View File

@@ -8,7 +8,6 @@
namespace Controller\Api;
use Controller;
use controller\MailController;
use Model;
use Exception;
@@ -26,8 +25,8 @@ class User extends Controller\Controller{
// character specific session keys
const SESSION_KEY_CHARACTERS = 'SESSION.CHARACTERS';
// temp login character ID (during HTTP redirects on login)
const SESSION_KEY_TEMP_CHARACTER_ID = 'SESSION.TEMP_CHARACTER_ID';
// temp login character data (during HTTP redirects on login)
const SESSION_KEY_TEMP_CHARACTER_DATA = 'SESSION.TEMP_CHARACTER_DATA';
// log text
const LOG_LOGGED_IN = 'userId: [%10s], userName: [%30s], charId: [%20s], charName: %s';
@@ -43,9 +42,10 @@ class User extends Controller\Controller{
/**
* login a valid character
* @param Model\CharacterModel $characterModel
* @param string $browserTabId
* @return bool
*/
protected function loginByCharacter(Model\CharacterModel &$characterModel){
protected function loginByCharacter(Model\CharacterModel &$characterModel, string $browserTabId){
$login = false;
if($user = $characterModel->getUser()){
@@ -68,7 +68,7 @@ class User extends Controller\Controller{
){
// user has changed OR new user ---------------------------------------------------
//-> set user/character data to session
$this->f3->set(self::SESSION_KEY_USER, [
$this->getF3()->set(self::SESSION_KEY_USER, [
'ID' => $user->_id,
'NAME' => $user->name
]);
@@ -77,7 +77,7 @@ class User extends Controller\Controller{
$sessionCharacters = $characterModel::mergeSessionCharacterData($sessionCharacters);
}
$this->f3->set(self::SESSION_KEY_CHARACTERS, $sessionCharacters);
$this->getF3()->set(self::SESSION_KEY_CHARACTERS, $sessionCharacters);
// save user login information --------------------------------------------------------
$characterModel->roleId = $characterModel->requestRoleId();
@@ -94,6 +94,10 @@ class User extends Controller\Controller{
)
);
// set temp character data ------------------------------------------------------------
// -> pass character data over for next http request (reroute())
$this->setTempCharacterData($characterModel->_id, $browserTabId);
$login = true;
}
@@ -118,6 +122,14 @@ class User extends Controller\Controller{
if( !empty($characters = $this->getCookieCharacters(array_slice($cookieData, 0, 1, true), false)) ){
// character is valid and allowed to login
$return->character = reset($characters)->getData();
// get Session status for character
if($activeCharacter = $this->getCharacter()){
if($activeUser = $activeCharacter->getUser()){
if($sessionCharacterData = $activeUser->findSessionCharacterData($return->character->id)){
$return->character->hasActiveSession = true;
}
}
}
}else{
$characterError = (object) [];
$characterError->type = 'warning';
@@ -179,9 +191,7 @@ class User extends Controller\Controller{
*/
public function deleteLog(\Base $f3){
if($activeCharacter = $this->getCharacter()){
if($characterLog = $activeCharacter->getLog()){
$characterLog->erase();
}
$activeCharacter->logout(false, true, false);
}
}
@@ -190,8 +200,7 @@ class User extends Controller\Controller{
* @param \Base $f3
*/
public function logout(\Base $f3){
$this->deleteLog($f3);
parent::logout($f3);
$this->logoutCharacter(false, true, true, true);
$return = (object) [];
$return->reroute = rtrim(self::getEnvironmentData('URL'), '/') . $f3->alias('login');
@@ -371,23 +380,15 @@ class User extends Controller\Controller{
$user = $activeCharacter->getUser();
if($user){
// try to send delete account mail
$msg = 'Hello ' . $user->name . ',<br><br>';
$msg .= 'your account data has been successfully deleted.';
$mailController = new MailController();
$mailController->sendDeleteAccount($user->email, $msg);
// save log
self::getLogger('DELETE_ACCOUNT')->write(
sprintf(self::LOG_DELETE_ACCOUNT, $user->id, $user->name)
);
// remove user
$this->logoutCharacter(true, true, true, true);
$user->erase();
$this->logout($f3);
die();
$return->reroute = rtrim(self::getEnvironmentData('URL'), '/') . $f3->alias('login');
}
}else{
// captcha not valid -> return error

View File

@@ -13,6 +13,33 @@ use lib\Config;
class AppController extends Controller {
public function beforeroute(\Base $f3, $params) : bool{
// page title
$f3->set('tplPageTitle', Config::getPathfinderData('name'));
// main page content
$f3->set('tplPageContent', Config::getPathfinderData('view.login'));
// body element class
$f3->set('tplBodyClass', 'pf-landing');
// JS main file
$f3->set('tplJsView', 'login');
if($return = parent::beforeroute($f3, $params)){
// href for SSO Auth
$f3->set('tplAuthType', $f3->alias( 'sso', ['action' => 'requestAuthorization'] ));
// characters from cookies
$f3->set('cookieCharacters', $this->getCookieByName(self::COOKIE_PREFIX_CHARACTER, true));
$f3->set('getCharacterGrid', function($characters){
return ( ((12 / count($characters)) <= 3) ? 3 : (12 / count($characters)) );
});
}
return $return;
}
/**
* event handler after routing
* @param \Base $f3
@@ -31,26 +58,7 @@ class AppController extends Controller {
* @param \Base $f3
*/
public function init(\Base $f3) {
// page title
$f3->set('tplPageTitle', Config::getPathfinderData('name'));
// main page content
$f3->set('tplPageContent', Config::getPathfinderData('view.login'));
// body element class
$f3->set('tplBodyClass', 'pf-landing');
// JS main file
$f3->set('tplJsView', 'login');
// href for SSO Auth
$f3->set('tplAuthType', $f3->alias( 'sso', ['action' => 'requestAuthorization'] ));
// characters from cookies
$f3->set('cookieCharacters', $this->getCookieByName(self::COOKIE_PREFIX_CHARACTER, true));
$f3->set('getCharacterGrid', function($characters){
return ( ((12 / count($characters)) <= 3) ? 3 : (12 / count($characters)) );
});
}
}

View File

@@ -34,6 +34,7 @@ class Sso extends Api\User{
const SESSION_KEY_SSO_ERROR = 'SESSION.SSO.ERROR';
const SESSION_KEY_SSO_STATE = 'SESSION.SSO.STATE';
const SESSION_KEY_SSO_FROM = 'SESSION.SSO.FROM';
const SESSION_KEY_SSO_TAB_ID = 'SESSION.SSO.TABID';
// error messages
const ERROR_CCP_SSO_URL = 'Invalid "ENVIRONMENT.[ENVIRONMENT].CCP_SSO_URL" url. %s';
@@ -53,6 +54,8 @@ class Sso extends Api\User{
* @param \Base $f3
*/
public function requestAdminAuthorization($f3){
// store browser tabId to be "targeted" after login
$f3->set(self::SESSION_KEY_SSO_TAB_ID, '');
$f3->set(self::SESSION_KEY_SSO_FROM, 'admin');
$scopes = self::getScopesByAuthType('admin');
@@ -66,6 +69,10 @@ class Sso extends Api\User{
*/
public function requestAuthorization($f3){
$params = $f3->get('GET');
$browserTabId = trim((string)$params['tabId']);
// store browser tabId to be "targeted" after login
$f3->set(self::SESSION_KEY_SSO_TAB_ID, $browserTabId);
if(
isset($params['characterId']) &&
@@ -73,7 +80,7 @@ class Sso extends Api\User{
){
// authentication restricted to a characterId -----------------------------------------------
// restrict login to this characterId e.g. for character switch on map page
$characterId = (int)trim($params['characterId']);
$characterId = (int)trim((string)$params['characterId']);
/**
* @var Model\CharacterModel $character
@@ -101,15 +108,12 @@ class Sso extends Api\User{
$character->hasUserCharacter() &&
($character->isAuthorized() === 'OK')
){
$loginCheck = $this->loginByCharacter($character);
$loginCheck = $this->loginByCharacter($character, $browserTabId);
if($loginCheck){
// set "login" cookie
$this->setLoginCookie($character);
// -> pass current character data to target page
$f3->set(Api\User::SESSION_KEY_TEMP_CHARACTER_ID, $character->_id);
// route to "map"
$f3->reroute(['map']);
}
@@ -174,6 +178,8 @@ class Sso extends Api\User{
$rootAlias = $f3->get(self::SESSION_KEY_SSO_FROM);
}
$browserTabId = (string)$f3->get(self::SESSION_KEY_SSO_TAB_ID) ;
if($f3->exists(self::SESSION_KEY_SSO_STATE)){
// check response and validate 'state'
if(
@@ -186,6 +192,7 @@ class Sso extends Api\User{
// clear 'state' for new next login request
$f3->clear(self::SESSION_KEY_SSO_STATE);
$f3->clear(self::SESSION_KEY_SSO_FROM);
$f3->clear(self::SESSION_KEY_SSO_TAB_ID);
$accessData = $this->getSsoAccessData($getParams['code']);
@@ -252,14 +259,14 @@ class Sso extends Api\User{
$characterModel = $userCharactersModel->getCharacter();
// login by character
$loginCheck = $this->loginByCharacter($characterModel);
$loginCheck = $this->loginByCharacter($characterModel, $browserTabId);
if($loginCheck){
// set "login" cookie
$this->setLoginCookie($characterModel);
// -> pass current character data to target page
$f3->set(Api\User::SESSION_KEY_TEMP_CHARACTER_ID, $characterModel->_id);
$f3->set(Api\User::SESSION_KEY_TEMP_CHARACTER_DATA, $characterModel->_id);
// route to "map"
if($rootAlias == 'admin'){
@@ -302,6 +309,7 @@ class Sso extends Api\User{
public function login(\Base $f3){
$data = (array)$f3->get('GET');
$cookieName = empty($data['cookie']) ? '' : $data['cookie'];
$browserTabId = empty($data['tabId']) ? '' : $data['tabId'];
$character = null;
if( !empty($cookieName) ){
@@ -316,12 +324,8 @@ class Sso extends Api\User{
if( is_object($character)){
// login by character
$loginCheck = $this->loginByCharacter($character);
$loginCheck = $this->loginByCharacter($character, $browserTabId);
if($loginCheck){
// set character id
// -> pass current character data to target page
$f3->set(Api\User::SESSION_KEY_TEMP_CHARACTER_ID, $character->_id);
// route to "map"
$f3->reroute(['map']);
}

View File

@@ -9,6 +9,7 @@
namespace Controller;
use Controller\Api as Api;
use lib\Config;
use Lib\Monolog;
use lib\Socket;
use Lib\Util;
use Model;
@@ -21,8 +22,8 @@ class Controller {
const COOKIE_PREFIX_CHARACTER = 'char';
// log text
const LOG_UNAUTHORIZED = 'User-Agent: [%s]';
const ERROR_SESSION_SUSPECT = 'Suspect id: [%45s], ip: [%45s], new ip: [%45s], User-Agent: [%s]';
const ERROR_SESSION_SUSPECT = 'id: [%45s], ip: [%45s], User-Agent: [%s]';
const ERROR_TEMP_CHARACTER_ID = 'Invalid temp characterId: %s';
/**
* @var \Base
@@ -48,46 +49,38 @@ class Controller {
return $this->template;
}
/**
* set $f3 base object
* @param \Base $f3
*/
protected function setF3(\Base $f3){
$this->f3 = $f3;
}
/**
* get $f3 base object
* @return \Base
*/
protected function getF3(){
if( !($this->f3 instanceof \Base) ){
$this->setF3( \Base::instance() );
}
return $this->f3;
return \Base::instance();
}
/**
* event handler for all "views"
* some global template variables are set in here
* @param \Base $f3
* @param array $params
* @param $params
* @return bool
*/
function beforeroute(\Base $f3, $params) {
$this->setF3($f3);
function beforeroute(\Base $f3, $params): bool {
// initiate DB connection
DB\Database::instance('PF');
DB\Database::instance()->getDB('PF');
// init user session
$this->initSession();
$this->initSession($f3);
if( !$f3->get('AJAX') ){
if($f3->get('AJAX')){
header('Content-type: application/json');
}else{
// js path (build/minified or raw uncompressed files)
$f3->set('tplPathJs', 'public/js/' . Config::getPathfinderData('version') );
$this->setTemplate( Config::getPathfinderData('view.index') );
}
return true;
}
/**
@@ -96,9 +89,6 @@ class Controller {
* @param \Base $f3
*/
public function afterroute(\Base $f3){
// store all user activities that are buffered for logging in this request
self::storeActivities();
if($this->getTemplate()){
// Ajax calls don´t need a page render..
// this happens on client side
@@ -118,32 +108,35 @@ class Controller {
/**
* init new Session handler
*/
protected function initSession(){
protected function initSession(\Base $f3){
$sessionCacheKey = $f3->get('SESSION_CACHE');
$session = null;
// init DB based Session (not file based)
if( $this->getDB('PF') instanceof DB\SQL){
// init session with custom "onsuspect()" handler
new DB\SQL\Session($this->getDB('PF'), 'sessions', true, function($session, $sid){
$f3 = $this->getF3();
if( ($ip = $session->ip() )!= $f3->get('IP') ){
// IP address changed -> not critical
self::getLogger('SESSION_SUSPECT')->write( sprintf(
self::ERROR_SESSION_SUSPECT,
$sid,
$session->ip(),
$f3->get('IP'),
$f3->get('AGENT')
));
// no more error handling here
return true;
}elseif($session->agent() != $f3->get('AGENT') ){
// The default behaviour destroys the suspicious session.
return false;
}
/**
* callback() for suspect sessions
* @param $session
* @param $sid
* @return bool
*/
$onSuspect = function($session, $sid){
self::getLogger('SESSION_SUSPECT')->write( sprintf(
self::ERROR_SESSION_SUSPECT,
$sid,
$session->ip(),
$session->agent()
));
// .. continue with default onSuspect() handler
// -> destroy session
return false;
};
return true;
});
if(
$sessionCacheKey === 'mysql' &&
$this->getDB('PF') instanceof DB\SQL
){
$session = new DB\SQL\Session($this->getDB('PF'), 'sessions', true, $onSuspect);
}
}
/**
@@ -190,12 +183,11 @@ class Controller {
* @param Model\CharacterModel $character
*/
protected function setLoginCookie(Model\CharacterModel $character){
if( $this->getCookieState() ){
$expireSeconds = (int) $this->getF3()->get('PATHFINDER.LOGIN.COOKIE_EXPIRE');
$expireSeconds = (int)Config::getPathfinderData('login.cookie_expire');
$expireSeconds *= 24 * 60 * 60;
$timezone = new \DateTimeZone( $this->getF3()->get('TZ') );
$timezone = $this->getF3()->get('getTimeZone')();
$expireTime = new \DateTime('now', $timezone);
// add cookie expire time
@@ -258,7 +250,7 @@ class Controller {
*/
$characterAuth = Model\BasicModel::getNew('CharacterAuthenticationModel');
$timezone = new \DateTimeZone( $this->getF3()->get('TZ') );
$timezone = $this->getF3()->get('getTimeZone')();
$currentTime = new \DateTime('now', $timezone);
foreach($cookieData as $name => $value){
@@ -268,7 +260,7 @@ class Controller {
$data = explode(':', $value);
if(count($data) === 2){
// cookie data is well formatted
$characterAuth->getByForeignKey('selector', $data[0], ['limit' => 1], 0);
$characterAuth->getByForeignKey('selector', $data[0], ['limit' => 1]);
// validate "scope hash"
// -> either "normal" scopes OR "admin" scopes
@@ -354,28 +346,37 @@ class Controller {
$data = [];
if($user = $this->getUser()){
$requestedCharacterId = 0;
$header = self::getRequestHeaders();
$requestedCharacterId = (int)$header['Pf-Character'];
$browserTabId = (string)$header['Pf-Tab-Id'];
$tempCharacterData = (array)$this->getF3()->get(Api\User::SESSION_KEY_TEMP_CHARACTER_DATA);
// get all characterData from currently active characters
if($this->getF3()->get('AJAX')){
// Ajax request -> get characterId from Header (if already available!)
$header = $this->getRequestHeaders();
$requestedCharacterId = (int)$header['Pf-Character'];
// _blank browser tab don´t have a $browserTabId jet..
// first Ajax call from that new tab with empty $requestedCharacterId -> bind to that new tab
if(
!empty($browserTabId) &&
$requestedCharacterId <= 0 &&
(int)$tempCharacterData['ID'] > 0 &&
empty($tempCharacterData['TAB_ID'])
){
$tempCharacterData['TAB_ID'] = $browserTabId;
// update tempCharacterData (SESSION)
$this->setTempCharacterData($tempCharacterData['ID'], $tempCharacterData['TAB_ID']);
}
if(
$requestedCharacterId > 0 &&
(int)$this->getF3()->get(Api\User::SESSION_KEY_TEMP_CHARACTER_ID) === $requestedCharacterId
!empty($browserTabId) &&
!empty($tempCharacterData['TAB_ID']) &&
(int)$tempCharacterData['ID'] > 0 &&
$browserTabId === $tempCharacterData['TAB_ID']
){
// requested characterId is "now" available on the client (Javascript)
// -> clear temp characterId for next character login/switch
$this->getF3()->clear(Api\User::SESSION_KEY_TEMP_CHARACTER_ID);
$requestedCharacterId = (int)$tempCharacterData['ID'];
}
}
if($requestedCharacterId <= 0){
// Ajax BUT characterID not yet set as HTTP header
// OR non Ajax -> get characterId from temp session (e.g. from HTTP redirect)
$requestedCharacterId = (int)$this->getF3()->get(Api\User::SESSION_KEY_TEMP_CHARACTER_ID);
}elseif((int)$tempCharacterData['ID'] > 0){
$requestedCharacterId = (int)$tempCharacterData['ID'];
}
$data = $user->getSessionCharacterData($requestedCharacterId);
@@ -420,21 +421,18 @@ class Controller {
public function getUser($ttl = 0){
$user = null;
if( $this->getF3()->exists(Api\User::SESSION_KEY_USER_ID) ){
$userId = (int)$this->getF3()->get(Api\User::SESSION_KEY_USER_ID);
if($userId){
/**
* @var $userModel Model\UserModel
*/
$userModel = Model\BasicModel::getNew('UserModel');
$userModel->getById($userId, $ttl);
if($this->getF3()->exists(Api\User::SESSION_KEY_USER_ID, $userId)){
/**
* @var $userModel Model\UserModel
*/
$userModel = Model\BasicModel::getNew('UserModel');
$userModel->getById($userId, $ttl);
if(
!$userModel->dry() &&
$userModel->hasUserCharacters()
){
$user = &$userModel;
}
if(
!$userModel->dry() &&
$userModel->hasUserCharacters()
){
$user = &$userModel;
}
}
@@ -442,26 +440,57 @@ class Controller {
}
/**
* log out current character
* @param \Base $f3
* set temp login character data (required during HTTP redirects on login)
* @param int $characterId
* @param string $browserTabId
* @throws \Exception
*/
public function logout(\Base $f3){
$params = (array)$f3->get('POST');
protected function setTempCharacterData(int $characterId, string $browserTabId){
if($characterId > 0){
$tempCharacterData = [
'ID' => $characterId,
'TAB_ID' => trim($browserTabId)
];
$this->getF3()->set(Api\User::SESSION_KEY_TEMP_CHARACTER_DATA, $tempCharacterData);
}else{
throw new \Exception( sprintf(self::ERROR_TEMP_CHARACTER_ID, $characterId) );
}
}
if( $activeCharacter = $this->getCharacter() ){
/**
* log out current character or all active characters (multiple browser tabs)
* @param bool $all
* @param bool $deleteSession
* @param bool $deleteLog
* @param bool $deleteCookie
*/
protected function logoutCharacter(bool $all = false, bool $deleteSession = true, bool $deleteLog = true, bool $deleteCookie = false){
$sessionCharacterData = (array)$this->getF3()->get(Api\User::SESSION_KEY_CHARACTERS);
if($params['clearCookies'] === '1'){
// delete server side cookie validation data
// for the active character
$activeCharacter->logout();
if($sessionCharacterData){
$activeCharacterId = ($activeCharacter = $this->getCharacter()) ? $activeCharacter->_id : 0;
/**
* @var Model\CharacterModel $character
*/
$character = Model\BasicModel::getNew('CharacterModel');
$characterIds = [];
foreach($sessionCharacterData as $characterData){
if($characterData['ID'] === $activeCharacterId){
$characterIds[] = $activeCharacter->_id;
$activeCharacter->logout($deleteSession, $deleteLog, $deleteCookie);
}elseif($all){
$character->getById($characterData['ID']);
$characterIds[] = $character->_id;
$character->logout($deleteSession, $deleteLog, $deleteCookie);
}
$character->reset();
}
// broadcast logout information to webSocket server
(new Socket( Config::getSocketUri() ))->sendData('characterLogout', $activeCharacter->_id);
if($characterIds){
// broadcast logout information to webSocket server
(new Socket( Config::getSocketUri() ))->sendData('characterLogout', $characterIds);
}
}
// destroy session login data -------------------------------
$f3->clear('SESSION');
}
/**
@@ -482,7 +511,7 @@ class Controller {
if( !empty($response) ){
// calculate time diff since last server restart
$timezone = new \DateTimeZone( $f3->get('TZ') );
$timezone = $f3->get('getTimeZone')();
$dateNow = new \DateTime('now', $timezone);
$dateServerStart = new \DateTime($response['startTime']);
$interval = $dateNow->diff($dateServerStart);
@@ -504,14 +533,24 @@ class Controller {
}
/**
* get error object is a user is not found/logged of
* @param int $code
* @param string $message
* @param string $status
* @param null $trace
* @return \stdClass
*/
protected function getLogoutError(){
$userError = (object) [];
$userError->type = 'error';
$userError->message = 'User not found';
return $userError;
protected function getErrorObject(int $code, string $message = '', string $status = '', $trace = null): \stdClass{
$object = (object) [];
$object->type = 'error';
$object->code = $code;
$object->status = empty($status) ? @constant('Base::HTTP_' . $code) : $status;
if(!empty($message)){
$object->message = $message;
}
if(!empty($trace)){
$object->trace = $trace;
}
return $object;
}
/**
@@ -558,59 +597,53 @@ class Controller {
* -> on AJAX request -> return JSON with error information
* -> on HTTP request -> render error page
* @param \Base $f3
* @return bool
*/
public function showError(\Base $f3){
// set HTTP status
$errorCode = $f3->get('ERROR.code');
if(!empty($errorCode)){
$f3->status($errorCode);
}
if(!headers_sent()){
// collect error info -------------------------------------------------------------------------------------
$error = $this->getErrorObject(
$f3->get('ERROR.code'),
$f3->get('ERROR.status'),
$f3->get('ERROR.text'),
$f3->get('DEBUG') === 3 ? $f3->get('ERROR.trace') : null
);
// collect error info ---------------------------------------
$return = (object) [];
$error = (object) [];
$error->type = 'error';
$error->code = $errorCode;
$error->status = $f3->get('ERROR.status');
$error->message = $f3->get('ERROR.text');
// check if error is a PDO Exception ----------------------------------------------------------------------
if(strpos(strtolower( $f3->get('ERROR.text') ), 'duplicate') !== false){
preg_match_all('/\'([^\']+)\'/', $f3->get('ERROR.text'), $matches, PREG_SET_ORDER);
// append stack trace for greater debug level
if( $f3->get('DEBUG') === 3){
$error->trace = $f3->get('ERROR.trace');
}
// check if error is a PDO Exception
if(strpos(strtolower( $f3->get('ERROR.text') ), 'duplicate') !== false){
preg_match_all('/\'([^\']+)\'/', $f3->get('ERROR.text'), $matches, PREG_SET_ORDER);
if(count($matches) === 2){
$error->field = $matches[1][1];
$error->message = 'Value "' . $matches[0][1] . '" already exists';
}
}
$return->error[] = $error;
// return error information ---------------------------------
if($f3->get('AJAX')){
header('Content-type: application/json');
echo json_encode($return);
die();
}else{
$f3->set('tplPageTitle', 'ERROR - ' . $error->code . ' | Pathfinder');
// set error data for template rendering
$error->redirectUrl = $this->getRouteUrl();
$f3->set('errorData', $error);
if( preg_match('/^4[0-9]{2}$/', $error->code) ){
// 4xx error -> render error page
$f3->set('tplPageContent', Config::getPathfinderData('STATUS.4XX') );
}elseif( preg_match('/^5[0-9]{2}$/', $error->code) ){
$f3->set('tplPageContent', Config::getPathfinderData('STATUS.5XX'));
if(count($matches) === 2){
$error->field = $matches[1][1];
$error->message = 'Value "' . $matches[0][1] . '" already exists';
}
}
echo \Template::instance()->render( Config::getPathfinderData('view.index') );
die();
// set response status ------------------------------------------------------------------------------------
if(!empty($error->code)){
$f3->status($error->code);
}
if($f3->get('AJAX')){
$return = (object) [];
$return->error[] = $error;
echo json_encode($return);
}else{
$f3->set('tplPageTitle', 'ERROR - ' . $error->code);
// set error data for template rendering
$error->redirectUrl = $this->getRouteUrl();
$f3->set('errorData', $error);
if( preg_match('/^4[0-9]{2}$/', $error->code) ){
// 4xx error -> render error page
$f3->set('tplPageContent', Config::getPathfinderData('STATUS.4XX') );
}elseif( preg_match('/^5[0-9]{2}$/', $error->code) ){
$f3->set('tplPageContent', Config::getPathfinderData('STATUS.5XX'));
}
}
}
return true;
}
/**
@@ -624,44 +657,36 @@ class Controller {
// track some 4xx Client side errors
// 5xx errors are handled in "ONERROR" callback
$status = http_response_code();
$halt = false;
if(!headers_sent() && $status >= 300){
if($f3->get('AJAX')){
$params = (array)$f3->get('POST');
$return = (object) [];
if((bool)$params['reroute']){
$return->reroute = rtrim(self::getEnvironmentData('URL'), '/') . $f3->alias('login');
}else{
// no reroute -> errors can be shown
$return->error[] = $this->getErrorObject($status, Config::getMessageFromHTTPStatus($status));
}
switch( $status ){
case 403: // Unauthorized
self::getLogger('UNAUTHORIZED')->write(sprintf(
self::LOG_UNAUTHORIZED,
$f3->get('AGENT')
));
$halt = true;
break;
}
// Ajax
if(
$halt &&
$f3->get('AJAX')
){
$params = (array)$f3->get('POST');
$response = (object) [];
$response->type = 'error';
$response->code = $status;
$response->message = 'Access denied: User not found';
$return = (object) [];
if( (bool)$params['reroute']){
$return->reroute = rtrim(self::getEnvironmentData('URL'), '/') . $f3->alias('login');
}else{
// no reroute -> errors can be shown
$return->error[] = $response;
echo json_encode($return);
}
echo json_encode($return);
die();
}
// store all user activities that are buffered for logging in this request
// this should work even on non HTTP200 responses
$this->logActivities();
return true;
}
/**
* store activity log data to DB
*/
protected function logActivities(){
LogController::instance()->logActivities();
Monolog::instance()->log();
}
/**
* get controller by class name
* -> controller class is searched within all controller directories
@@ -793,7 +818,7 @@ class Controller {
* @return int
*/
static function getRegistrationStatus(){
return (int)\Base::instance()->get('PATHFINDER.REGISTRATION.STATUS');
return (int)Config::getPathfinderData('registration.status');
}
/**
@@ -806,13 +831,6 @@ class Controller {
return LogController::getLogger($type);
}
/**
* store activity log data to DB
*/
static function storeActivities(){
LogController::instance()->storeActivities();
}
/**
* removes illegal characters from a Hive-key that are not allowed
* @param $key
@@ -842,20 +860,4 @@ class Controller {
(new Socket( Config::getSocketUri(), $ttl ))->sendData('healthCheck', $load);
}
/**
* get required MySQL variable value
* @param $key
* @return string|null
*/
static function getRequiredMySqlVariables($key){
$f3 = \Base::instance();
$requiredMySqlVarKey = 'REQUIREMENTS[MYSQL][VARS][' . $key . ']';
$data = null;
if( $f3->exists($requiredMySqlVarKey) ){
$data = $f3->get($requiredMySqlVarKey);
}
return $data;
}
}

View File

@@ -8,42 +8,73 @@
namespace controller;
use DB;
use lib\Config;
use Lib\Logging\MapLog;
use Model\ActivityLogModel;
use Model\BasicModel;
class LogController extends \Prefab {
const CACHE_KEY_ACTIVITY_COLUMNS = 'CACHED_ACTIVITY_COLUMNS';
const CACHE_TTL_ACTIVITY_COLUMNS = 300;
/**
* @var string[]
*/
protected $activityLogColumns = [];
/**
* buffered activity log data for this singleton LogController() class
* -> this buffered data can be stored somewhere (e.g. DB) before HTTP response
* -> should be cleared afterwards!
* @var array
*/
protected $activityLogBuffer = [];
protected $activityLogBuffer = [];
/**
* reserve a "new" character activity for logging
* @param $characterId
* @param $mapId
* @param $action
* get columns from ActivityLogModel that can be uses as counter
* @return array
*/
public function bufferActivity($characterId, $mapId, $action){
$characterId = (int)$characterId;
$mapId = (int)$mapId;
protected function getActivityLogColumns(): array{
if(empty($this->activityLogColumns)){
$f3 = \Base::instance();
if(!$f3->exists(self::CACHE_KEY_ACTIVITY_COLUMNS, $this->activityLogColumns)){
/**
* @var $activityLogModel ActivityLogModel
*/
$activityLogModel = BasicModel::getNew('ActivityLogModel');
$this->activityLogColumns = $activityLogModel->getCountableColumnNames();
$f3->set(self::CACHE_KEY_ACTIVITY_COLUMNS, self::CACHE_TTL_ACTIVITY_COLUMNS);
}
}
if(
$characterId > 0 &&
$mapId > 0
){
$key = $this->getBufferedActivityKey($characterId, $mapId);
return $this->activityLogColumns;
}
if( is_null($key) ){
$activity = [
'characterId' => $characterId,
'mapId' => $mapId,
$action => 1
];
$this->activityLogBuffer[] = $activity;
}else{
$this->activityLogBuffer[$key][$action]++;
/**
* buffered activity log data for this singleton LogController() class
* -> this buffered data can be stored somewhere (e.g. DB) before HTTP response
* -> should be cleared afterwards!
* @param MapLog $log
*/
public function push(MapLog $log){
$action = $log->getAction();
// check $action to be valid (table column exists)
if($action && in_array($action, $this->getActivityLogColumns())){
if($mapId = $log->getChannelId()){
$logData = $log->getData();
if($characterId = (int)$logData['character']['id']){
if($index = $this->getBufferedActivityIndex($characterId, $mapId)){
$this->activityLogBuffer[$index][$action]++;
}else{
$this->activityLogBuffer[] = [
'characterId' => $characterId,
'mapId' => $mapId,
$action => 1
];
}
}
}
}
}
@@ -51,7 +82,7 @@ class LogController extends \Prefab {
/**
* store all buffered activity log data to DB
*/
public function storeActivities(){
public function logActivities(){
if( !empty($this->activityLogBuffer) ){
$db = DB\Database::instance()->getDB('PF');
@@ -104,24 +135,20 @@ class LogController extends \Prefab {
}
/**
* get array key from "buffered activity log" array
* get array key/index from "buffered activity log" array
* @param int $characterId
* @param int $mapId
* @return int|null
* @return int
*/
private function getBufferedActivityKey($characterId, $mapId){
$activityKey = null;
if(
$characterId > 0 &&
$mapId > 0
){
private function getBufferedActivityIndex(int $characterId, int $mapId): int {
$activityKey = 0;
if($characterId > 0 && $mapId > 0 ){
foreach($this->activityLogBuffer as $key => $activityData){
if(
$activityData['characterId'] === $characterId &&
$activityData['mapId'] === $mapId
){
$activityKey = $key;
$activityKey = (int)$key;
break;
}
}
@@ -136,8 +163,7 @@ class LogController extends \Prefab {
* @return \Log|null
*/
public static function getLogger($type){
$f3 = \Base::instance();
$logFiles = $f3->get('PATHFINDER.LOGFILES');
$logFiles = Config::getPathfinderData('logfiles');
$logFileName = empty($logFiles[$type]) ? 'error' : $logFiles[$type];
$logFile = $logFileName . '.log';

View File

@@ -1,80 +0,0 @@
<?php
/**
* Created by PhpStorm.
* User: Exodus
* Date: 30.08.2015
* Time: 14:48
*/
namespace controller;
class MailController extends \SMTP{
public function __construct(){
$host = Controller::getEnvironmentData('SMTP_HOST');
$port = Controller::getEnvironmentData('SMTP_PORT');
$scheme = Controller::getEnvironmentData('SMTP_SCHEME');
$user = Controller::getEnvironmentData('SMTP_USER');
$pw = Controller::getEnvironmentData('SMTP_PASS');
parent::__construct($host,$port,$scheme,$user,$pw);
// error handling
$this->set('Errors-to', '<' . Controller::getEnvironmentData('SMTP_ERROR') . '>');
$this->set('MIME-Version', '1.0');
$this->set('Content-Type', 'text/html; charset=ISO-8859-1');
}
/**
* send mail to removed user account
* @param $to
* @param $msg
* @return bool
*/
public function sendDeleteAccount($to, $msg){
$status = false;
if( !empty($to)){
$this->set('To', '<' . $to . '>');
$this->set('From', '"Pathfinder" <' . Controller::getEnvironmentData('SMTP_FROM') . '>');
$this->set('Subject', 'Account deleted');
$status = $this->send($msg);
}
return $status;
}
/**
* send notification mail for new rally point systems
* @param $to
* @param $msg
* @return bool
*/
public function sendRallyPoint($to, $msg){
$status = false;
if( !empty($to)){
$this->set('To', '<' . $to . '>');
$this->set('From', '"Pathfinder" <' . Controller::getEnvironmentData('SMTP_FROM') . '>');
$this->set('Subject', 'PATHFINDER - New rally point');
$status = $this->send($msg);
}
return $status;
}
public function send($message, $log = true, $mock = false){
$status = false;
if(
!empty($this->host) &&
!empty($this->port)
){
$status = parent::send($message, $log, $mock);
}
return $status;
}
}

View File

@@ -13,6 +13,7 @@ use DB;
use DB\SQL;
use DB\SQL\MySQL as MySQL;
use lib\Config;
use Lib\Util;
use Model;
class Setup extends Controller {
@@ -26,10 +27,10 @@ class Setup extends Controller {
'BASE',
'URL',
'DEBUG',
'DB_DNS',
'DB_NAME',
'DB_USER',
'DB_PASS',
'DB_PF_DNS',
'DB_PF_NAME',
'DB_PF_USER',
'DB_PF_PASS',
'DB_CCP_DNS',
'DB_CCP_NAME',
'DB_CCP_USER',
@@ -95,15 +96,15 @@ class Setup extends Controller {
],
'tables' => []
],
/* WIP ...
'UNIVERSE' => [
'info' => [],
'models' => [
'Model\Universe\RegionModel',
'Model\Universe\ConstellationModel'
'Model\Universe\TypeModel',
//'Model\Universe\RegionModel',
//'Model\Universe\ConstellationModel'
],
'tables' => []
], */
],
'CCP' => [
'info' => [],
'models' => [],
@@ -120,19 +121,28 @@ class Setup extends Controller {
]
];
/**
* @var DB\Database
*/
protected $dbLib = null;
/**
* database error
* @var bool
*/
protected $databaseCheck = true;
protected $databaseHasError = false;
/**
* event handler for all "views"
* some global template variables are set in here
* @param \Base $f3
* @param array $params
* @return bool
*/
function beforeroute(\Base $f3, $params) {
function beforeroute(\Base $f3, $params): bool {
// init dbLib class. Manages all DB connections
$this->dbLib = DB\Database::instance();
// page title
$f3->set('tplPageTitle', 'Setup | ' . Config::getPathfinderData('name'));
@@ -144,8 +154,13 @@ class Setup extends Controller {
// js path (build/minified or raw uncompressed files)
$f3->set('tplPathJs', 'public/js/' . Config::getPathfinderData('version') );
return true;
}
/**
* @param \Base $f3
*/
public function afterroute(\Base $f3) {
// js view (file)
$f3->set('tplJsView', 'setup');
@@ -159,6 +174,16 @@ class Setup extends Controller {
return $cacheType;
});
// simple counter (called within template)
$counter = 0;
$f3->set('tplCounter', function(string $action = 'add') use (&$counter){
switch($action){
case 'add': $counter++; break;
case 'get': return $counter; break;
case 'reset': $counter = 0; break;
}
});
// render view
echo \Template::instance()->render( Config::getPathfinderData('view.index') );
}
@@ -175,26 +200,31 @@ class Setup extends Controller {
// enables automatic column fix
$fixColumns = false;
// bootstrap database from model class definition
if( !empty($params['db']) ){
$this->bootstrapDB($params['db']);
// reload page
// -> remove GET param
$f3->reroute('@setup');
return;
}elseif( !empty($params['fixCols']) ){
$fixColumns = true;
}elseif( !empty($params['buildIndex']) ){
$this->setupSystemJumpTable();
}elseif( !empty($params['importTable']) ){
$this->importTable($params['importTable']);
}elseif( !empty($params['exportTable']) ){
$this->exportTable($params['exportTable']);
}elseif( !empty($params['clearCache']) ){
$this->clearCache($f3);
}elseif( !empty($params['invalidateCookies']) ){
$this->invalidateCookies($f3);
switch($params['action']){
case 'createDB':
$this->createDB($params['db']);
break;
case 'bootstrapDB':
$this->bootstrapDB($params['db']);
break;
case 'fixCols':
$fixColumns = true;
break;
case 'buildIndex':
$this->setupSystemJumpTable();
break;
case 'importTable':
$this->importTable($params['model']);
break;
case 'exportTable':
$this->exportTable($params['model']);
break;
case 'clearCache':
$this->clearCache($f3);
break;
case 'invalidateCookies':
$this->invalidateCookies($f3);
break;
}
// set template data ----------------------------------------------------------------
@@ -207,6 +237,12 @@ class Setup extends Controller {
// set requirement check information
$f3->set('checkRequirements', $this->checkRequirements($f3));
// set php config check information
$f3->set('checkPHPConfig', $this->checkPHPConfig($f3));
// set map default config
$f3->set('mapsDefaultConfig', $this->getMapsDefaultConfig($f3));
// set database connection information
$f3->set('checkDatabase', $this->checkDatabase($f3, $fixColumns));
@@ -320,9 +356,9 @@ class Setup extends Controller {
protected function getEnvironmentInformation(\Base $f3){
$environmentData = [];
// exclude some sensitive data (e.g. database, passwords)
$excludeVars = ['DB_DNS', 'DB_NAME', 'DB_USER',
'DB_PASS', 'DB_CCP_DNS', 'DB_CCP_NAME',
'DB_CCP_USER', 'DB_CCP_PASS'
$excludeVars = [
'DB_PF_DNS', 'DB_PF_NAME', 'DB_PF_USER', 'DB_PF_PASS',
'DB_CCP_DNS', 'DB_CCP_NAME', 'DB_CCP_USER', 'DB_CCP_PASS'
];
// obscure some values
@@ -338,10 +374,7 @@ class Setup extends Controller {
$check = false;
$value = '[missing]';
}elseif( in_array($var, $obscureVars)){
$length = strlen($value);
$hideChars = ($length < 10) ? $length : 10;
$value = substr_replace($value, str_repeat('.', 3), -$hideChars);
$value .= ' [' . $length . ']';
$value = Util::obscureString($value);
}
$environmentData[$var] = [
@@ -444,6 +477,9 @@ class Setup extends Controller {
'version' => (PHP_INT_SIZE * 8) . '-bit',
'check' => $f3->get('REQUIREMENTS.PHP.PHP_INT_SIZE') == PHP_INT_SIZE
],
[
'label' => 'PHP extensions'
],
'pcre' => [
'label' => 'PCRE',
'required' => $f3->get('REQUIREMENTS.PHP.PCRE_VERSION'),
@@ -486,20 +522,6 @@ class Setup extends Controller {
'version' => (extension_loaded('curl') && function_exists('curl_version')) ? 'installed' : 'missing',
'check' => (extension_loaded('curl') && function_exists('curl_version'))
],
'maxInputVars' => [
'label' => 'max_input_vars',
'required' => $f3->get('REQUIREMENTS.PHP.MAX_INPUT_VARS'),
'version' => ini_get('max_input_vars'),
'check' => ini_get('max_input_vars') >= $f3->get('REQUIREMENTS.PHP.MAX_INPUT_VARS'),
'tooltip' => 'PHP default = 1000. Increase it in order to import larger maps.'
],
'maxExecutionTime' => [
'label' => 'max_execution_time',
'required' => $f3->get('REQUIREMENTS.PHP.MAX_EXECUTION_TIME'),
'version' => ini_get('max_execution_time'),
'check' => ini_get('max_execution_time') >= $f3->get('REQUIREMENTS.PHP.MAX_EXECUTION_TIME'),
'tooltip' => 'PHP default = 30. Max execution time for PHP scripts.'
],
[
'label' => 'Redis Server [optional]'
],
@@ -594,6 +616,125 @@ class Setup extends Controller {
return $checkRequirements;
}
/**
* check PHP config (php.ini)
* @param \Base $f3
* @return array
*/
protected function checkPHPConfig(\Base $f3): array {
$phpConfig = [
'maxInputVars' => [
'label' => 'max_input_vars',
'required' => $f3->get('REQUIREMENTS.PHP.MAX_INPUT_VARS'),
'version' => ini_get('max_input_vars'),
'check' => ini_get('max_input_vars') >= $f3->get('REQUIREMENTS.PHP.MAX_INPUT_VARS'),
'tooltip' => 'PHP default = 1000. Increase it in order to import larger maps.'
],
'maxExecutionTime' => [
'label' => 'max_execution_time',
'required' => $f3->get('REQUIREMENTS.PHP.MAX_EXECUTION_TIME'),
'version' => ini_get('max_execution_time'),
'check' => ini_get('max_execution_time') >= $f3->get('REQUIREMENTS.PHP.MAX_EXECUTION_TIME'),
'tooltip' => 'PHP default = 30. Max execution time for PHP scripts.'
],
'htmlErrors' => [
'label' => 'html_errors',
'required' => $f3->get('REQUIREMENTS.PHP.HTML_ERRORS'),
'version' => (int)ini_get('html_errors'),
'check' => (bool)ini_get('html_errors') == (bool)$f3->get('REQUIREMENTS.PHP.HTML_ERRORS'),
'tooltip' => 'Formatted HTML StackTrace on error.'
],
[
'label' => 'Session'
],
'sessionSaveHandler' => [
'label' => 'save_handler',
'version' => ini_get('session.save_handler'),
'check' => true,
'tooltip' => 'PHP Session save handler (Redis is preferred).'
],
'sessionSavePath' => [
'label' => 'session.save_path',
'version' => ini_get('session.save_path'),
'check' => true,
'tooltip' => 'PHP Session save path (Redis is preferred).'
],
'sessionName' => [
'label' => 'session.name',
'version' => ini_get('session.name'),
'check' => true,
'tooltip' => 'PHP Session name.'
]
];
return $phpConfig;
}
/**
* get default map config
* @param \Base $f3
* @return array
*/
protected function getMapsDefaultConfig(\Base $f3): array {
$matrix = \Matrix::instance();
$mapsDefaultConfig = (array)Config::getMapsDefaultConfig();
$matrix->transpose($mapsDefaultConfig);
$mapConfig = ['mapTypes' => array_keys(reset($mapsDefaultConfig))];
foreach($mapsDefaultConfig as $option => $defaultConfig){
$tooltip = '';
switch($option){
case 'lifetime':
$label = 'Map lifetime (days)';
$tooltip = 'Unchanged/inactive maps get auto deleted afterwards (cronjob).';
break;
case 'max_count':
$label = 'Max. maps count/user';
break;
case 'max_shared':
$label = 'Map share limit/map';
$tooltip = 'E.g. A Corp map can be shared with X other corps.';
break;
case 'max_systems':
$label = 'Max. systems count/map';
break;
case 'log_activity_enabled':
$label = '<i class="fa fa-fw fa-bar-chart"></i> Activity statistics';
$tooltip = 'If "enabled", map admins can enable user statistics for a map.';
break;
case 'log_history_enabled':
$label = '<i class="fa fa-fw fa-file-text"></i> History log files';
$tooltip = 'If "enabled", map admins can pipe map logs to file. (one file per map)';
break;
case 'send_history_slack_enabled':
$label = '<i class="fa fa-fw fa-slack"></i> History log Slack';
$tooltip = 'If "enabled", map admins can set a Slack channel were map logs get piped to.';
break;
case 'send_rally_slack_enabled':
$label = '<i class="fa fa-fw fa-slack"></i> Rally point poke Slack';
$tooltip = 'If "enabled", map admins can set a Slack channel for rally point pokes.';
break;
case 'send_rally_mail_enabled':
$label = '<i class="fa fa-fw fa-envelope"></i> Rally point poke Email';
$tooltip = 'If "enabled", rally point pokes can be send by Email (SMTP config + recipient address required).';
break;
default:
$label = 'unknown';
}
$mapsDefaultConfig[$option] = [
'label' => $label,
'tooltip' => $tooltip,
'data' => $defaultConfig
];
}
$mapConfig['mapConfig'] = $mapsDefaultConfig;
return $mapConfig;
}
/**
* get database connection information
* @param \Base $f3
@@ -611,7 +752,9 @@ class Setup extends Controller {
$dbConnected = false;
// DB type (e.g. MySql,..)
$dbDriver = 'unknown';
// enable database ::setup() function in UI
// enable database ::create() function on UI
$dbCreate = false;
// enable database ::setup() function on UI
$dbSetupEnable = false;
// check of everything is OK (connection, tables, columns, indexes,..)
$dbStatusCheckCount = 0;
@@ -622,7 +765,9 @@ class Setup extends Controller {
// get DB config
$dbConfigValues = Config::getDatabaseConfig($dbKey);
// check DB for valid connection
$db = DB\Database::instance()->getDB($dbKey);
$db = $this->dbLib->getDB($dbKey);
// collection for errors
$dbErrors = [];
// check config that does NOT require a valid DB connection
switch($dbKey){
@@ -631,8 +776,9 @@ class Setup extends Controller {
case 'CCP': $dbLabel = 'EVE-Online [SDE]'; break;
}
$dbName = $dbConfigValues['NAME'];
$dbUser = $dbConfigValues['USER'];
$dbName = $dbConfigValues['NAME'];
$dbUser = $dbConfigValues['USER'];
$dbAlias = $dbConfigValues['ALIAS'];
if($db){
switch($dbKey){
@@ -858,27 +1004,47 @@ class Setup extends Controller {
}else{
// DB connection failed
$dbStatusCheckCount++;
foreach($this->dbLib->getErrors($dbAlias, 10) as $dbException){
$dbErrors[] = $dbException->getMessage();
}
// try to connect without! DB (-> offer option to create them)
// do not log errors (silent)
$this->dbLib->setSilent(true);
$dbServer = $this->dbLib->connectToServer($dbAlias);
$this->dbLib->setSilent(false);
if(!is_null($dbServer)){
// connection succeeded
$dbCreate = true;
$dbDriver = $dbServer->driver();
}
}
if($dbStatusCheckCount !== 0){
$this->databaseCheck = false;
$this->databaseHasError = true;
}
// sort tables for better readability
ksort($requiredTables);
$this->databases[$dbKey]['info'] = [
'db' => $db,
'label' => $dbLabel,
'driver' => $dbDriver,
'name' => $dbName,
'user' => $dbUser,
'dbConfig' => $dbConfig,
'setupEnable' => $dbSetupEnable,
'connected' => $dbConnected,
'statusCheckCount' => $dbStatusCheckCount,
'columnQueries' => $dbColumnQueries,
'tableData' => $requiredTables
// 'db' => $db,
'label' => $dbLabel,
'host' => Config::getDatabaseDNSValue((string)$dbConfigValues['DNS'], 'host'),
'port' => Config::getDatabaseDNSValue((string)$dbConfigValues['DNS'], 'port'),
'driver' => $dbDriver,
'name' => $dbName,
'user' => $dbUser,
'pass' => Util::obscureString((string)$dbConfigValues['PASS'], 8),
'dbConfig' => $dbConfig,
'dbCreate' => $dbCreate,
'setupEnable' => $dbSetupEnable,
'connected' => $dbConnected,
'statusCheckCount' => $dbStatusCheckCount,
'columnQueries' => $dbColumnQueries,
'tableData' => $requiredTables,
'errors' => $dbErrors
];
}
@@ -941,24 +1107,46 @@ class Setup extends Controller {
return $dbConfig;
}
/**
* try to create a fresh database
* @param string $dbKey
*/
protected function createDB(string $dbKey){
// check for valid key
if(!empty($this->databases[$dbKey])){
// disable logging (we expect the DB connect to fail -> no db created)
$this->dbLib->setSilent(true);
// try to connect
$db = $this->dbLib->getDB($dbKey);
// enable logging
$this->dbLib->setSilent(false, true);
if(is_null($db)){
// try create new db
$db = $this->dbLib->createDB($dbKey);
if(is_null($db)){
foreach($this->dbLib->getErrors($dbKey, 5) as $error){
// ... no further error handling here -> check log files
//$error->getMessage()
}
}
}
}
}
/**
* init the complete database
* - create tables
* - create indexes
* - set default static values
* @param $dbKey
* @param string $dbKey
* @return array
*/
protected function bootstrapDB($dbKey){
$db = DB\Database::instance()->getDB($dbKey);
protected function bootstrapDB(string $dbKey){
$db = $this->dbLib->getDB($dbKey);
$checkTables = [];
if($db){
// set/change default "character set" and "collation"
$db->exec('ALTER DATABASE ' . $db->quotekey($db->name())
. ' CHARACTER SET ' . self::getRequiredMySqlVariables('CHARACTER_SET_DATABASE')
. ' COLLATE ' . self::getRequiredMySqlVariables('COLLATION_DATABASE')
);
// set some default config for this database
DB\Database::prepareDatabase($db);
// setup tables
foreach($this->databases[$dbKey]['models'] as $modelClass){
@@ -976,10 +1164,10 @@ class Setup extends Controller {
// $ttl for health check
$ttl = 600;
$heachCheckToken = microtime(true);
$healthCheckToken = microtime(true);
// ping TCP Socket with checkToken
self::checkTcpSocket($ttl, $heachCheckToken);
self::checkTcpSocket($ttl, $healthCheckToken);
$socketInformation = [
'tcpSocket' => [
@@ -1004,7 +1192,7 @@ class Setup extends Controller {
'check' => !empty( $ttl )
]
],
'token' => $heachCheckToken
'token' => $healthCheckToken
],
'webSocket' => [
'label' => 'WebSocket (clients) [HTTP]',
@@ -1026,78 +1214,77 @@ class Setup extends Controller {
* @return array
*/
protected function getIndexData(){
// active DB and tables are required for obtain index data
if( $this->databaseCheck ){
if(!$this->databaseHasError){
$indexInfo = [
'SystemNeighbourModel' => [
'action' => [
'task' => [
[
'task' => 'buildIndex',
'action' => 'buildIndex',
'label' => 'build',
'icon' => 'fa-refresh',
'btn' => 'btn-primary'
]
],
'table' => Model\BasicModel::getNew('SystemNeighbourModel')->getTable(),
'count' => DB\Database::instance()->getRowCount( Model\BasicModel::getNew('SystemNeighbourModel')->getTable() )
'count' => $this->dbLib->getRowCount( Model\BasicModel::getNew('SystemNeighbourModel')->getTable() )
],
'WormholeModel' => [
'action' => [
'task' => [
[
'task' => 'exportTable',
'action' => 'exportTable',
'label' => 'export',
'icon' => 'fa-download',
'btn' => 'btn-default'
],[
'task' => 'importTable',
'action' => 'importTable',
'label' => 'import',
'icon' => 'fa-upload',
'btn' => 'btn-primary'
]
],
'table' => Model\BasicModel::getNew('WormholeModel')->getTable(),
'count' => DB\Database::instance()->getRowCount( Model\BasicModel::getNew('WormholeModel')->getTable() )
'count' => $this->dbLib->getRowCount( Model\BasicModel::getNew('WormholeModel')->getTable() )
],
'SystemWormholeModel' => [
'action' => [
'task' => [
[
'task' => 'exportTable',
'action' => 'exportTable',
'label' => 'export',
'icon' => 'fa-download',
'btn' => 'btn-default'
],[
'task' => 'importTable',
'action' => 'importTable',
'label' => 'import',
'icon' => 'fa-upload',
'btn' => 'btn-primary'
]
],
'table' => Model\BasicModel::getNew('SystemWormholeModel')->getTable(),
'count' => DB\Database::instance()->getRowCount( Model\BasicModel::getNew('SystemWormholeModel')->getTable() )
'count' => $this->dbLib->getRowCount( Model\BasicModel::getNew('SystemWormholeModel')->getTable() )
],
'ConstellationWormholeModel' => [
'action' => [
'task' => [
[
'task' => 'exportTable',
'action' => 'exportTable',
'label' => 'export',
'icon' => 'fa-download',
'btn' => 'btn-default'
],[
'task' => 'importTable',
'action' => 'importTable',
'label' => 'import',
'icon' => 'fa-upload',
'btn' => 'btn-primary'
]
],
'table' => Model\BasicModel::getNew('ConstellationWormholeModel')->getTable(),
'count' => DB\Database::instance()->getRowCount( Model\BasicModel::getNew('ConstellationWormholeModel')->getTable() )
'count' => $this->dbLib->getRowCount( Model\BasicModel::getNew('ConstellationWormholeModel')->getTable() )
]
];
}else{
$indexInfo = [
'SystemNeighbourModel' => [
'action' => [],
'task' => [],
'table' => 'Fix database errors first!'
]
];
@@ -1178,7 +1365,7 @@ class Setup extends Controller {
/**
* import table data from existing dump file (e.g *.csv)
* @param $modelClass
* @param string $modelClass
* @return bool
* @throws \Exception
*/
@@ -1189,7 +1376,7 @@ class Setup extends Controller {
/**
* export table data
* @param $modelClass
* @param string $modelClass
* @throws \Exception
*/
protected function exportTable($modelClass){

View File

@@ -12,11 +12,25 @@ use data\filesystem\Search;
class Cache {
const LOG_TEXT = '%s [%\'_10s] files, size [%\'_10s] byte, not writable [%\'_10s] files, errors [%\'_10s], exec (%.3Fs)';
const LOG_TEXT = '%s [%\'_10s] files, size [%\'_10s] byte, not writable [%\'_10s] files, errors [%\'_10s], exec (%.3Fs)';
/**
* default max expire for files (seconds)
*/
const CACHE_EXPIRE_MAX = 864000;
/**
* @param \Base $f3
* @return int
*/
protected function getExpireMaxTime(\Base $f3): int {
$expireTime = (int)$f3->get('PATHFINDER.CACHE.EXPIRE_MAX');
return ($expireTime >= 0) ? $expireTime : self::CACHE_EXPIRE_MAX;
}
/**
* clear expired cached files
* >> >php index.php "/cron/deleteExpiredCacheData"
* >> php index.php "/cron/deleteExpiredCacheData"
* @param \Base $f3
*/
function deleteExpiredData(\Base $f3){
@@ -25,8 +39,8 @@ class Cache {
// cache dir (dir is recursively searched...)
$cacheDir = $f3->get('TEMP');
$filterTime = (int)strtotime('-' . $f3->get('PATHFINDER.CACHE.EXPIRE_MAX') . ' seconds');
$expiredFiles = Search::getFilesByMTime($cacheDir, $filterTime);
$filterTime = (int)strtotime('-' . $this->getExpireMaxTime($f3) . ' seconds');
$expiredFiles = Search::getFilesByMTime($cacheDir, $filterTime, Search::DEFAULT_FILE_LIMIT);
$deletedFiles = 0;
$deletedSize = 0;
@@ -36,16 +50,18 @@ class Cache {
/**
* @var $file \SplFileInfo
*/
if( $file->isWritable() ){
$tmpSize = $file->getSize();
if( unlink($file->getRealPath()) ){
$deletedSize += $tmpSize;
$deletedFiles++;
if($file->isFile()){
if( $file->isWritable() ){
$tmpSize = $file->getSize();
if( unlink($file->getRealPath()) ){
$deletedSize += $tmpSize;
$deletedFiles++;
}else{
$deleteErrors++;
}
}else{
$deleteErrors++;
$notWritableFiles++;
}
}else{
$notWritableFiles++;
}
}

View File

@@ -8,6 +8,7 @@
namespace cron;
use DB;
use lib\Config;
use Model;
class MapUpdate {
@@ -23,19 +24,20 @@ class MapUpdate {
* @param \Base $f3
*/
function deactivateMapData(\Base $f3){
$privateMapLifetime = (int)$f3->get('PATHFINDER.MAP.PRIVATE.LIFETIME');
$privateMapLifetime = (int)Config::getMapsDefaultConfig('private.lifetime');
if($privateMapLifetime > 0){
$pfDB = DB\Database::instance()->getDB('PF');
$sqlDeactivateExpiredMaps = "UPDATE map SET
if($pfDB){
$sqlDeactivateExpiredMaps = "UPDATE map SET
active = 0
WHERE
map.active = 1 AND
map.typeId = 2 AND
TIMESTAMPDIFF(DAY, map.updated, NOW() ) > :lifetime";
$pfDB->exec($sqlDeactivateExpiredMaps, ['lifetime' => $privateMapLifetime]);
$pfDB->exec($sqlDeactivateExpiredMaps, ['lifetime' => $privateMapLifetime]);
}
}
}
@@ -45,18 +47,31 @@ class MapUpdate {
* @param \Base $f3
*/
function deleteMapData(\Base $f3){
$pfDB = DB\Database::instance()->getDB('PF');
$deletedMapsCount = 0;
$sqlDeleteDisabledMaps = "DELETE FROM
if($pfDB){
$sqlDeleteDisabledMaps = "SELECT
id
FROM
map
WHERE
map.active = 0 AND
TIMESTAMPDIFF(DAY, map.updated, NOW() ) > :deletion_time";
$pfDB->exec($sqlDeleteDisabledMaps, ['deletion_time' => self::DAYS_UNTIL_MAP_DELETION]);
$disabledMaps = $pfDB->exec($sqlDeleteDisabledMaps, ['deletion_time' => self::DAYS_UNTIL_MAP_DELETION]);
$deletedMapsCount = $pfDB->count();
if($deletedMapsCount = $pfDB->count()){
$mapModel = Model\BasicModel::getNew('MapModel');
foreach($disabledMaps as $data){
$mapModel->getById( (int)$data['id'], 3, false );
if( !$mapModel->dry() ){
$mapModel->erase();
}
$mapModel->reset();
}
}
}
// Log ------------------------
$log = new \Log('cron_' . __FUNCTION__ . '.log');
@@ -73,8 +88,8 @@ class MapUpdate {
if($eolExpire > 0){
$pfDB = DB\Database::instance()->getDB('PF');
$sql = "SELECT
if($pfDB){
$sql = "SELECT
`con`.`id`
FROM
`connection` `con` INNER JOIN
@@ -85,20 +100,21 @@ class MapUpdate {
TIMESTAMPDIFF(SECOND, `con`.`eolUpdated`, NOW() ) > :expire_time
";
$connectionsData = $pfDB->exec($sql, [
'deleteEolConnections' => 1,
'expire_time' => $eolExpire
]);
$connectionsData = $pfDB->exec($sql, [
'deleteEolConnections' => 1,
'expire_time' => $eolExpire
]);
if($connectionsData){
/**
* @var $connection Model\ConnectionModel
*/
$connection = Model\BasicModel::getNew('ConnectionModel');
foreach($connectionsData as $data){
$connection->getById( (int)$data['id'] );
if( !$connection->dry() ){
$connection->erase();
if($connectionsData){
/**
* @var $connection Model\ConnectionModel
*/
$connection = Model\BasicModel::getNew('ConnectionModel');
foreach($connectionsData as $data){
$connection->getById( (int)$data['id'] );
if( !$connection->dry() ){
$connection->erase();
}
}
}
}
@@ -115,8 +131,8 @@ class MapUpdate {
if($whExpire > 0){
$pfDB = DB\Database::instance()->getDB('PF');
$sql = "SELECT
if($pfDB){
$sql = "SELECT
`con`.`id`
FROM
`connection` `con` INNER JOIN
@@ -128,21 +144,22 @@ class MapUpdate {
TIMESTAMPDIFF(SECOND, `con`.`created`, NOW() ) > :expire_time
";
$connectionsData = $pfDB->exec($sql, [
'deleteExpiredConnections' => 1,
'scope' => 'wh',
'expire_time' => $whExpire
]);
$connectionsData = $pfDB->exec($sql, [
'deleteExpiredConnections' => 1,
'scope' => 'wh',
'expire_time' => $whExpire
]);
if($connectionsData){
/**
* @var $connection Model\ConnectionModel
*/
$connection = Model\BasicModel::getNew('ConnectionModel');
foreach($connectionsData as $data){
$connection->getById( (int)$data['id'] );
if( !$connection->dry() ){
$connection->erase();
if($connectionsData){
/**
* @var $connection Model\ConnectionModel
*/
$connection = Model\BasicModel::getNew('ConnectionModel');
foreach($connectionsData as $data){
$connection->getById( (int)$data['id'] );
if( !$connection->dry() ){
$connection->erase();
}
}
}
}
@@ -159,8 +176,8 @@ class MapUpdate {
if($signatureExpire > 0){
$pfDB = DB\Database::instance()->getDB('PF');
$sqlDeleteExpiredSignatures = "DELETE `sigs` FROM
if($pfDB){
$sqlDeleteExpiredSignatures = "DELETE `sigs` FROM
`system_signature` `sigs` INNER JOIN
`system` ON
`system`.`id` = `sigs`.`systemId`
@@ -169,7 +186,8 @@ class MapUpdate {
TIMESTAMPDIFF(SECOND, `sigs`.`updated`, NOW() ) > :lifetime
";
$pfDB->exec($sqlDeleteExpiredSignatures, ['lifetime' => $signatureExpire]);
$pfDB->exec($sqlDeleteExpiredSignatures, ['lifetime' => $signatureExpire]);
}
}
}

View File

@@ -0,0 +1,90 @@
<?php
/**
* Created by PhpStorm.
* User: exodu
* Date: 06.08.2017
* Time: 18:47
*/
namespace data\file;
class FileHandler extends \Prefab {
const ERROR_STREAM_READABLE = 'Stream is not readable "%s"';
const LOG_FILE_OFFSET_MIN = 0;
const LOG_FILE_OFFSET = 0;
const LOG_FILE_OFFSET_MAX = 10000;
const LOG_FILE_LIMIT_MIN = 1;
const LOG_FILE_LIMIT = 100;
const Log_File_LIMIT_MAX = 100;
/**
* parse local log file from end to first line
* -> Each row is a JSON object
* @param string $sourceFile
* @param int $offset
* @param int $limit
* @param null|callable $formatter
* @return array
*/
public static function readLogFile(
string $sourceFile,
int $offset = self::LOG_FILE_OFFSET,
int $limit = self::LOG_FILE_LIMIT,
$formatter = null
): array {
$data = [];
if(is_file($sourceFile)){
if(is_readable($sourceFile)){
$file = new ReverseSplFileObject($sourceFile, $offset);
$file->setFlags(\SplFileObject::DROP_NEW_LINE | \SplFileObject::READ_AHEAD | \SplFileObject::SKIP_EMPTY);
foreach( new \LimitIterator($file, 0, $limit) as $i => $rowData){
if( !empty($rowDataObj = (array)json_decode($rowData, true)) ){
if(is_callable($formatter)){
$formatter($rowDataObj);
}
$data[] = $rowDataObj;
}
}
}else{
\Base::instance()->error(500, sprintf(self::ERROR_STREAM_READABLE, $sourceFile));
}
}
return $data;
}
/**
* validate offset
* @param int $offset
* @return int
*/
public static function validateOffset(int $offset): int{
if(
$offset < self::LOG_FILE_OFFSET_MIN ||
$offset > self::LOG_FILE_OFFSET_MAX
){
$offset = self::LOG_FILE_OFFSET;
}
return $offset;
}
/**
* validate limit
* @param int $limit
* @return int
*/
public static function validateLimit(int $limit): int{
if(
$limit < self::LOG_FILE_LIMIT_MIN ||
$limit > self::Log_File_LIMIT_MAX
){
$limit = self::LOG_FILE_LIMIT;
}
return $limit;
}
}

View File

@@ -0,0 +1,219 @@
<?php
/**
* Created by PhpStorm.
* User: exodu
* Date: 06.08.2017
* Time: 18:42
*/
namespace data\file;
class ReverseSplFileObject extends \SplFileObject{
/**
* pointer position indicates file start
* @var int
*/
protected $begin = 0;
/**
* line offset from file end (1 => 'start with 2nd last line')
* @var int
*/
protected $offset = 0;
/**
* total lines found in file
* @var int
*/
protected $lineCount = 0;
/**
* empty lines found in file
* @var int
*/
protected $lineCountEmpty = 0;
/**
* current pointer position
* @var int
*/
protected $pointer = 0;
/**
* position increments when valid row data found
* @var
*/
protected $position;
/**
* control characters
* @var array
*/
protected $eol = ["\r", "\n"];
public function __construct($sourceFile, $offset = 0){
parent::__construct($sourceFile);
// set total line count of the file
$this->setLineCount();
//Seek to the first position of the file and record its position
//Should be 0
$this->fseek(0);
$this->begin = $this->ftell();
$this->offset = $offset;
//Seek to the last position from the end of the file
//This varies depending on the file
$this->fseek($this->pointer, SEEK_END);
}
/**
* reverse rewind file.
*/
public function rewind(){
//Set the line position to 0 - First Line
$this->position = 0;
//Reset the file pointer to the end of the file minus 1 character. "0" == false
$this->fseek(-1, SEEK_END);
$this->findLineBegin();
//... File pointer is now at the beginning of the last line that contains data
// add custom line offset
if($this->offset){
// calculate offset start line
$offsetLine = $this->lineCount - $this->lineCountEmpty - $this->offset;
if($offsetLine > 0){
// row is zero based
$offsetIndex = $offsetLine - 1;
parent::seek($offsetIndex);
// seek() sets pointer to next line... set it back to previous
$this->fseek(-2, SEEK_CUR);
$this->findLineBegin();
//... File pointer is now at the beginning of the last line that contains data from $offset
}else{
// negative offsetLine -> invalid!
$this->pointer = $this->begin -1;
}
}
}
/**
* Return the current line after the file pointer
* @return string
*/
public function current(){
return trim($this->fgets());
}
/**
* Return the current key of the line we're on
* These go in reverse order
* @return mixed
*/
public function key(){
return $this->position;
}
/**
* move one line up
*/
public function next(){
//Step the file pointer back one step to the last letter of the previous line
--$this->pointer;
if($this->pointer < $this->begin){
return;
}
$this->fseek($this->pointer);
$this->findLineBegin();
//File pointer is now on the next previous line
//Increment the line position
++$this->position;
}
/**
* Check the current file pointer to make sure we are not at the beginning of the file
* @return bool
*/
public function valid(){
return ($this->pointer >= $this->begin);
}
/**
* seek to previous lines
* @param int $lineCount
*/
public function seek($lineCount){
for($i = 0; $i < $lineCount; $i++){
$this->next();
}
}
/**
* move pointer to line begin
* -> skip line breaks
*/
private function findLineBegin(){
//Check the character over and over till we hit another new line
$c = $this->fgetc();
// skip empty lines
while(in_array($c, $this->eol)){
$this->fseek(-2, SEEK_CUR);
if(!$this->pointer = $this->ftell()){
break;
}
$c = $this->fgetc();
$this->lineCountEmpty++;
}
//Check the last character to make sure it is not a new line
while(!in_array($c, $this->eol)){
$this->fseek(-2, SEEK_CUR);
if(!$this->pointer = $this->ftell()){
break;
}
$c = $this->fgetc();
}
}
/**
* set total line count. No matter if there are empty lines in between
*/
private function setLineCount(){
// Store flags and position
$flags = $this->getFlags();
$currentPointer = $this->ftell();
// Prepare count by resetting flags as READ_CSV for example make the tricks very slow
$this->setFlags(null);
// Go to the larger INT we can as seek will not throw exception, errors, notice if we go beyond the bottom line
//$this->seek(PHP_INT_MAX);
parent::seek(PHP_INT_MAX);
// We store the key position
// As key starts at 0, we add 1
$this->lineCount = parent::key() + 1;
// We move to old position
// As seek method is longer with line number < to the max line number, it is better to count at the beginning of iteration
//parent::seek($currentPointer);
$this->fseek($currentPointer);
// Re set flags
$this->setFlags($flags);
}
}

View File

@@ -11,20 +11,26 @@ namespace data\filesystem;
class Search {
/**
* max file count that should be deleted in this session
*/
const DEFAULT_FILE_LIMIT = 1000;
/**
* timestamp (seconds) filter files by mTime()
* -> default = "no filter"
* @var int
*/
static $filterTime = 0;
static $filterTime = 0;
/**
* recursive file filter by mTime
* @param string $dir
* @param int $mTime
* @return array|\RecursiveCallbackFilterIterator
* @param int $limit
* @return array|\LimitIterator
*/
static function getFilesByMTime($dir, $mTime = null){
static function getFilesByMTime(string $dir, $mTime = null, $limit = self::DEFAULT_FILE_LIMIT){
$files = [];
if(is_dir($dir)){
@@ -53,7 +59,8 @@ class Search {
return false;
});
$files = new \RecursiveIteratorIterator($files);
// limit max files
$files = new \LimitIterator($files, 0, $limit);
}
return $files;

View File

@@ -7,45 +7,71 @@
*/
namespace DB;
use Controller;
use controller\LogController;
use lib\Config;
class Database extends \Prefab {
/**
* if true, errors will not get logged
* @var bool
*/
private $silent = false;
function __construct($database = 'PF'){
// set database
$this->setDB($database);
/**
* @var array
*/
private $errors = [];
/**
* connect to the DB server itself -> NO database is used
* -> can be used to check if a certain DB exists without connecting to it directly
* @param string $dbKey
* @return SQL|null
*/
public function connectToServer(string $dbKey = 'PF'){
$dbConfig = Config::getDatabaseConfig($dbKey);
$dbConfig['DNS'] = str_replace(';dbname=', '', $dbConfig['DNS'] );
$dbConfig['NAME'] = '';
return call_user_func_array([$this, 'connect'], $dbConfig);
}
/**
* set database
* @param string $database
* @return SQL
* tries to create a database if not exists
* -> DB user needs rights to create a DB
* @param string $dbKey
* @return SQL|null
*/
public function setDB($database = 'PF'){
$f3 = \Base::instance();
public function createDB(string $dbKey = 'PF'){
$db = null;
$dbConfig = Config::getDatabaseConfig($dbKey);
// remove database from $dsn (we want to crate it)
$newDbName = $dbConfig['NAME'];
if(!empty($newDbName)){
$dbConfig['NAME'] = '';
$dbConfig['DNS'] = str_replace(';dbname=', '', $dbConfig['DNS'] );
// "Hive" Key for DB storage
$dbHiveKey = $this->getDbHiveKey($database);
$db = call_user_func_array([$this, 'connect'], $dbConfig);
// check if DB connection already exists
if( !$f3->exists($dbHiveKey, $db) ){
$dbConfig = Config::getDatabaseConfig($database);
if(!is_null($db)){
$schema = new SQL\Schema($db);
if(!in_array($newDbName, $schema->getDatabases())){
$db->exec("CREATE DATABASE IF NOT EXISTS
`" . $newDbName . "` DEFAULT CHARACTER SET utf8
COLLATE utf8_general_ci;");
$db->exec("USE `" . $newDbName . "`");
$db = call_user_func_array([$this, 'connect'], $dbConfig);
if( !is_null($db) ){
// set DB timezone to UTC +00:00 (eve server time)
$db->exec('SET @@session.time_zone = "+00:00";');
// set default storage engine
$db->exec('SET @@session.default_storage_engine = "' .
Controller\Controller::getRequiredMySqlVariables('DEFAULT_STORAGE_ENGINE') . '"');
// store DB object
$f3->set($dbHiveKey, $db);
// check if DB create was successful
$dbCheck = $db->exec("SELECT DATABASE()");
if(
!empty($dbCheck[0]) &&
!empty($checkDbName = reset($dbCheck[0])) &&
$checkDbName == $newDbName
){
self::prepareDBConnection($db);
self::prepareDatabase($db);
}
}
}
}
@@ -54,14 +80,20 @@ class Database extends \Prefab {
/**
* get database
* @param string $database
* @return SQL
* @param string $dbKey
* @return SQL|null
*/
public function getDB($database = 'PF'){
public function getDB(string $dbKey = 'PF'){
$f3 = \Base::instance();
$dbHiveKey = $this->getDbHiveKey($database);
// "Hive" Key for DB object cache
$dbHiveKey = $this->getDbHiveKey($dbKey);
if( !$f3->exists($dbHiveKey, $db) ){
$db = $this->setDB($database);
$dbConfig = Config::getDatabaseConfig($dbKey);
$db = call_user_func_array([$this, 'connect'], $dbConfig);
if(!is_null($db)){
self::prepareDBConnection($db);
$f3->set($dbHiveKey, $db);
}
}
return $db;
@@ -69,23 +101,23 @@ class Database extends \Prefab {
/**
* get a unique hive key for each DB connection
* @param $database
* @param $dbKey
* @return string
*/
protected function getDbHiveKey($database){
return 'DB_' . $database;
protected function getDbHiveKey($dbKey){
return 'DB_' . $dbKey;
}
/**
* connect to a database
* @param $dns
* @param $name
* @param $user
* @param $password
* @return SQL
* @param string $dns
* @param string $name
* @param string $user
* @param string $password
* @param string $alias
* @return SQL|null
*/
protected function connect($dns, $name, $user, $password){
protected function connect($dns, $name, $user, $password, $alias){
$db = null;
$f3 = \Base::instance();
@@ -109,9 +141,10 @@ class Database extends \Prefab {
$options
);
}catch(\PDOException $e){
// DB connection error
// -> log it
self::getLogger()->write($e->getMessage());
$this->pushError($alias, $e);
if(!$this->isSilent()){
self::getLogger()->write($e);
}
}
return $db;
@@ -119,22 +152,22 @@ class Database extends \Prefab {
/**
* get all table names from a DB
* @param string $database
* @param string $dbKey
* @return array|bool
*/
public function getTables($database = 'PF'){
$schema = new SQL\Schema( $this->getDB($database) );
public function getTables($dbKey = 'PF'){
$schema = new SQL\Schema( $this->getDB($dbKey) );
return $schema->getTables();
}
/**
* checks whether a table exists on a DB or not
* @param $table
* @param string $database
* @param string $dbKey
* @return bool
*/
public function tableExists($table, $database = 'PF'){
$tableNames = $this->getTables($database);
public function tableExists($table, $dbKey = 'PF'){
$tableNames = $this->getTables($dbKey);
return in_array($table, $tableNames);
}
@@ -142,13 +175,13 @@ class Database extends \Prefab {
* get current row (data) count for an existing table
* -> returns 0 if table not exists or empty
* @param $table
* @param string $database
* @param string $dbKey
* @return int
*/
public function getRowCount($table, $database = 'PF') {
public function getRowCount($table, $dbKey = 'PF') {
$count = 0;
if( $this->tableExists($table, $database) ){
$db = $this->getDB($database);
if( $this->tableExists($table, $dbKey) ){
$db = $this->getDB($dbKey);
$countRes = $db->exec("SELECT COUNT(*) `num` FROM " . $db->quotekey($table));
if(isset($countRes[0]['num'])){
$count = (int)$countRes[0]['num'];
@@ -157,6 +190,98 @@ class Database extends \Prefab {
return $count;
}
/**
* @return bool
*/
public function isSilent() : bool{
return $this->silent;
}
/**
* set "silent" mode (no error logging)
* -> optional clear $this->errors
* @param bool $silent
* @param bool $clearErrors
*/
public function setSilent(bool $silent, bool $clearErrors = false){
$this->silent = $silent;
if($clearErrors){
$this->errors = [];
}
}
/**
* push new Exception into static error history
* @param string $alias
* @param \PDOException $e
*/
protected function pushError(string $alias, \PDOException $e){
if(!is_array($this->errors[$alias])){
$this->errors[$alias] = [];
}
// prevent adding same errors twice
if(!empty($this->errors[$alias])){
$lastError = array_values($this->errors[$alias])[0];
if($lastError->getMessage() === $e->getMessage()){
return;
}
}
array_unshift($this->errors[$alias], $e);
if(count($this->errors[$alias]) > 5){
$this->errors[$alias] = array_pop($this->errors[$alias]);
}
}
/**
* get last recent Exceptions from error history
* @param string $alias
* @param int $limit
* @return \PDOException[]
*/
public function getErrors(string $alias, int $limit = 1){
return array_slice((array)$this->errors[$alias] , 0, $limit);
}
/**
* prepare current DB
* -> set session connection variables
* @param SQL $db
*/
public static function prepareDBConnection(SQL &$db){
// set DB timezone to UTC +00:00 (eve server time)
$db->exec('SET @@session.time_zone = "+00:00";');
// set default storage engine
$db->exec('SET @@session.default_storage_engine = "' .
self::getRequiredMySqlVariables('DEFAULT_STORAGE_ENGINE') . '"');
}
/**
* set some default config for current DB
* @param SQL $db
*/
public static function prepareDatabase(SQL &$db){
if($db->name()){
// set/change default "character set" and "collation"
$db->exec('ALTER DATABASE ' . $db->quotekey($db->name())
. ' CHARACTER SET ' . self::getRequiredMySqlVariables('CHARACTER_SET_DATABASE')
. ' COLLATE ' . self::getRequiredMySqlVariables('COLLATION_DATABASE')
);
}
}
/**
* get required MySQL variable value
* @param string $key
* @return string|null
*/
public static function getRequiredMySqlVariables(string $key){
\Base::instance()->exists('REQUIREMENTS[MYSQL][VARS][' . $key . ']', $data);
return $data;
}
/**
* get logger for DB logging
* @return \Log

View File

@@ -11,11 +11,12 @@ namespace Exception;
class BaseException extends \Exception {
const VALIDATION_FAILED = 403;
const REGISTRATION_FAILED = 403;
const CONFIGURATION_FAILED = 500;
const VALIDATION_EXCEPTION = 403;
const REGISTRATION_EXCEPTION = 403;
const CONFIG_VALUE_EXCEPTION = 500;
const DB_EXCEPTION = 500;
public function __construct($message, $code = 0){
public function __construct(string $message, int $code = 0){
parent::__construct($message, $code);
}

View File

@@ -0,0 +1,16 @@
<?php
/**
* Created by PhpStorm.
* User: exodu
* Date: 25.08.2017
* Time: 20:31
*/
namespace Exception;
class DatabaseException extends BaseException {
public function __construct(string $message){
parent::__construct($message, self::DB_EXCEPTION);
}
}

View File

@@ -12,6 +12,6 @@ namespace Exception;
class PathfinderException extends BaseException{
public function __construct($message){
parent::__construct($message, self::CONFIGURATION_FAILED);
parent::__construct($message, self::CONFIG_VALUE_EXCEPTION);
}
}

View File

@@ -32,9 +32,7 @@ class RegistrationException extends BaseException{
}
public function __construct($message, $field = ''){
parent::__construct($message, self::REGISTRATION_FAILED);
parent::__construct($message, self::REGISTRATION_EXCEPTION);
$this->setField($field);
}
}

View File

@@ -11,27 +11,41 @@ namespace Exception;
class ValidationException extends BaseException {
/**
* table column that triggers the exception
* @var string
*/
private $field;
/**
* @return mixed
* @return string
*/
public function getField(){
public function getField(): string {
return $this->field;
}
/**
* @param mixed $field
* @param string $field
*/
public function setField($field){
public function setField(string $field){
$this->field = $field;
}
public function __construct($message, $field = 0){
parent::__construct($message, self::VALIDATION_FAILED);
public function __construct(string $message, string $field = ''){
parent::__construct($message, self::VALIDATION_EXCEPTION);
$this->setField($field);
}
/**
* get error object
* @return \stdClass
*/
public function getError(){
$error = (object) [];
$error->type = 'error';
$error->field = $this->getField();
$error->message = $this->getMessage();
return $error;
}
}

216
app/main/lib/Monolog.php Normal file
View File

@@ -0,0 +1,216 @@
<?php
/**
* Created by PhpStorm.
* User: exodu
* Date: 04.08.2017
* Time: 20:17
*/
namespace Lib;
use controller\LogController;
use Lib\Logging;
use Monolog\Registry;
use Monolog\Formatter\FormatterInterface;
use Monolog\Handler\HandlerInterface;
use Monolog\Handler\BufferHandler;
use Monolog\Logger;
class Monolog extends \Prefab {
const ERROR_FORMATTER = 'Unknown log formatter for key "%s"';
const ERROR_HANDLER = 'Unknown log handler for key "%s"';
const ERROR_PROCESSOR = 'Unknown log processor for key "%s"';
const FORMATTER = [
'line' => 'Monolog\Formatter\LineFormatter',
'json' => 'Monolog\Formatter\JsonFormatter',
'html' => 'Monolog\Formatter\HtmlFormatter',
'mail' => 'Lib\Logging\Formatter\MailFormatter'
];
const HANDLER = [
'stream' => 'Monolog\Handler\StreamHandler',
'mail' => 'Monolog\Handler\SwiftMailerHandler',
'slackMap' => 'Lib\Logging\Handler\SlackMapWebhookHandler',
'slackRally' => 'Lib\Logging\Handler\SlackRallyWebhookHandler',
'zmq' => 'Lib\Logging\Handler\ZMQHandler'
];
const PROCESSOR = [
'psr' => 'Monolog\Processor\PsrLogMessageProcessor'
];
/**
* @var Logging\LogCollection[][]|Logging\MapLog[][]
*/
private $logs = [
'solo' => [],
'groups' => []
];
public function __construct(){
// set timezone for all Logger instances
if(class_exists(Logger::class)){
if( is_callable($getTimezone = \Base::instance()->get('getTimeZone')) ){
Logger::setTimezone($getTimezone());
};
}else{
LogController::getLogger('ERROR')->write(sprintf(Config::ERROR_CLASS_NOT_EXISTS_COMPOSER, Logger::class));
}
}
/**
* buffer log object, add to objectStorage collection
* -> this buffered data can be stored/logged somewhere (e.g. DB/file) at any time
* -> should be cleared afterwards!
* @param Logging\AbstractLog $log
*/
public function push(Logging\AbstractLog $log){
// check whether $log should be "grouped" by common handlers
if($log->isGrouped()){
$groupHash = $log->getGroupHash();
if(!isset($this->logs['groups'][$groupHash])){
// create new log collection
// $this->logs['groups'][$groupHash] = new Logging\LogCollection($log->getChannelName());
$this->logs['groups'][$groupHash] = new Logging\LogCollection('mapDelete');
}
$this->logs['groups'][$groupHash]->addLog($log);
// remove "group" handler from $log
// each log should only be logged once per handler!
$log->removeHandlerGroups();
}
$this->logs['solo'][] = $log;
}
/**
* bulk process all stored logs -> send to Monolog lib
*/
public function log(){
foreach($this->logs as $logType => $logs){
foreach($logs as $logKey => $log){
$groupHash = $log->getGroupHash();
$level = Logger::toMonologLevel($log->getLevel());
// add new logger to Registry if not already exists
if(Registry::hasLogger($groupHash)){
$logger = Registry::getInstance($groupHash);
}else{
$logger = new Logger($log->getChannelName());
// disable microsecond timestamps (seconds should be fine)
$logger->useMicrosecondTimestamps(true);
// configure new $logger --------------------------------------------------------------------------
// get Monolog Handler with Formatter config
// -> $log could have multiple handler with different Formatters
$handlerConf = $log->getHandlerConfig();
foreach($handlerConf as $handlerKey => $formatterKey){
// get Monolog Handler class
$handlerParams = $log->getHandlerParams($handlerKey);
$handler = $this->getHandler($handlerKey, $handlerParams);
// get Monolog Formatter
$formatter = $this->getFormatter((string)$formatterKey);
if( $formatter instanceof FormatterInterface){
$handler->setFormatter($formatter);
}
if($log->hasBuffer()){
// wrap Handler into bufferHandler
// -> bulk save all logs for this $logger
$bufferHandler = new BufferHandler($handler);
$logger->pushHandler($bufferHandler);
}else{
$logger->pushHandler($handler);
}
}
// get Monolog Processor config
$processorConf = $log->getProcessorConfig();
foreach($processorConf as $processorKey => $processorCallback){
if(is_callable($processorCallback)){
// custom Processor callback function
$logger->pushProcessor($processorCallback);
}else{
// get Monolog Processor class
$processor = $this->getProcessor($processorKey);
$logger->pushProcessor($processor);
}
}
Registry::addLogger($logger, $groupHash);
}
$logger->addRecord($level, $log->getMessage(), $log->getContext());
}
}
// clear log object storage
$this->logs['groups'] = [];
$this->logs['solo'] = [];
}
/**
* get Monolog Formatter instance by key
* @param string $formatKey
* @return FormatterInterface|null
* @throws \Exception
*/
private function getFormatter(string $formatKey){
$formatter = null;
if(!empty($formatKey)){
if(array_key_exists($formatKey, self::FORMATTER)){
$formatClass = self::FORMATTER[$formatKey];
$formatter = new $formatClass();
}else{
throw new \Exception(sprintf(self::ERROR_FORMATTER, $formatKey));
}
}
return $formatter;
}
/**
* get Monolog Handler instance by key
* @param string $handlerKey
* @param array $handlerParams
* @return HandlerInterface
* @throws \Exception
*/
private function getHandler(string $handlerKey, array $handlerParams = []): HandlerInterface{
if(array_key_exists($handlerKey, self::HANDLER)){
$handlerClass = self::HANDLER[$handlerKey];
$handler = new $handlerClass(...$handlerParams);
}else{
throw new \Exception(sprintf(self::ERROR_HANDLER, $handlerKey));
}
return $handler;
}
/**
* get Monolog Processor instance by key
* @param string $processorKey
* @return callable
* @throws \Exception
*/
private function getProcessor(string $processorKey): callable {
if(array_key_exists($processorKey, self::PROCESSOR)){
$ProcessorClass = self::PROCESSOR[$processorKey];
$processor = new $ProcessorClass();
}else{
throw new \Exception(sprintf(self::ERROR_PROCESSOR, $processorKey));
}
return $processor;
}
}

View File

@@ -16,24 +16,26 @@ class CcpClient extends \Prefab {
private $apiClient;
public function __construct(\Base $f3){
$this->apiClient = $this->getClient();
$this->apiClient = $this->getClient($f3);
$f3->set('ccpClient', $this);
}
/**
* get ApiClient instance
* @param \Base $f3
* @return ApiClient|null
*/
protected function getClient(){
protected function getClient(\Base $f3){
$client = null;
if( !class_exists(ApiClient::class) ){
LogController::getLogger('ERROR')->write($this->getMissingClientError());
}else{
if(class_exists(ApiClient::class)){
$client = new ApiClient();
$client->setUrl( Config::getEnvironmentData('CCP_ESI_URL') );
$client->setDatasource( Config::getEnvironmentData('CCP_ESI_DATASOURCE') );
$client->setUserAgent($this->getUserAgent());
$client->setDebugLevel($f3->get('DEBUG'));
}else{
LogController::getLogger('ERROR')->write(sprintf(Config::ERROR_CLASS_NOT_EXISTS_COMPOSER, ApiClient::class));
}
return $client;
@@ -52,14 +54,6 @@ class CcpClient extends \Prefab {
return $userAgent;
}
/**
* get error msg for failed ApiClient() class -> Composer package not found
* @return string
*/
protected function getMissingClientError(){
return "Class '" . ApiClient::class . "' not found. -> Check installed Composer packages.'";
}
/**
* get error msg for undefined method in ApiClient() class
* @param $method
@@ -86,8 +80,8 @@ class CcpClient extends \Prefab {
\Base::instance()->error(501, $this->getMissingMethodError($name));
}
}else{
LogController::getLogger('ERROR')->write($this->getMissingClientError());
\Base::instance()->error(501, $this->getMissingClientError());
LogController::getLogger('ERROR')->write(sprintf(Config::ERROR_CLASS_NOT_EXISTS_COMPOSER, ApiClient::class));
\Base::instance()->error(501, sprintf(Config::ERROR_CLASS_NOT_EXISTS_COMPOSER, ApiClient::class));
}
return $return;

View File

@@ -17,8 +17,11 @@ class Config extends \Prefab {
const ARRAY_DELIMITER = '-';
const HIVE_KEY_PATHFINDER = 'PATHFINDER';
const HIVE_KEY_ENVIRONMENT = 'ENVIRONMENT';
const CACHE_KEY_SOCKET_VALID = 'CACHED_SOCKET_VALID';
const CACHE_TTL_SOCKET_VALID = 60;
const ERROR_CONF_PATHFINDER = 'Config value missing in pathfinder.ini file [%s]';
const ERROR_CLASS_NOT_EXISTS_COMPOSER = 'Class "%s" not found. -> Check installed Composer packages';
/**
@@ -43,6 +46,11 @@ class Config extends \Prefab {
// set hive configuration variables
// -> overwrites default configuration
$this->setHiveVariables($f3);
// set global function for current DateTimeZone()
$f3->set('getTimeZone', function() use ($f3){
return new \DateTimeZone( $f3->get('TZ') );
});
}
/**
@@ -164,7 +172,6 @@ class Config extends \Prefab {
static function getEnvironmentData($key){
$hiveKey = self::HIVE_KEY_ENVIRONMENT . '.' . $key;
\Base::instance()->exists($hiveKey, $data);
return $data;
}
@@ -173,16 +180,74 @@ class Config extends \Prefab {
* @param string $dbKey
* @return array
*/
static function getDatabaseConfig($dbKey = 'PF'){
$dbConfKey = ($dbKey === 'PF') ? '' : $dbKey . '_';
static function getDatabaseConfig(string $dbKey = 'PF'){
$dbKey = strtoupper($dbKey);
return [
'DNS' => self::getEnvironmentData('DB_' . $dbConfKey . 'DNS'),
'NAME' => self::getEnvironmentData('DB_' . $dbConfKey . 'NAME'),
'USER' => self::getEnvironmentData('DB_' . $dbConfKey . 'USER'),
'PASS' => self::getEnvironmentData('DB_' . $dbConfKey . 'PASS')
'DNS' => self::getEnvironmentData('DB_' . $dbKey . '_DNS'),
'NAME' => self::getEnvironmentData('DB_' . $dbKey . '_NAME'),
'USER' => self::getEnvironmentData('DB_' . $dbKey . '_USER'),
'PASS' => self::getEnvironmentData('DB_' . $dbKey . '_PASS'),
'ALIAS' => $dbKey
];
}
/**
* get DB config value from PDO connect $dns string
* @param string $dns
* @param string $key
* @return bool
*/
static function getDatabaseDNSValue(string $dns, string $key = 'dbname'){
$value = false;
if(preg_match('/' . preg_quote($key, '/') . '=([[:alnum:]]+)/is', $dns, $parts)){
$value = $parts[1];
}
return $value;
}
/**
* get SMTP config values
* @return \stdClass
*/
static function getSMTPConfig(): \stdClass{
$config = new \stdClass();
$config->host = self::getEnvironmentData('SMTP_HOST');
$config->port = self::getEnvironmentData('SMTP_PORT');
$config->scheme = self::getEnvironmentData('SMTP_SCHEME');
$config->username = self::getEnvironmentData('SMTP_USER');
$config->password = self::getEnvironmentData('SMTP_PASS');
$config->from = [
self::getEnvironmentData('SMTP_FROM') => self::getPathfinderData('name')
];
return $config;
}
/**
* validates an SMTP config
* @param \stdClass $config
* @return bool
*/
static function isValidSMTPConfig(\stdClass $config): bool {
// validate email from either an configured array or plain string
$validateMailConfig = function($mailConf = null): bool {
$email = null;
if(is_array($mailConf)){
reset($mailConf);
$email = key($mailConf);
}elseif(is_string($mailConf)){
$email = $mailConf;
}
return \Audit::instance()->email($email);
};
return (
!empty($config->host) &&
!empty($config->username) &&
$validateMailConfig($config->from) &&
$validateMailConfig($config->to)
);
}
/**
* get email for notifications by hive key
* @param $key
@@ -196,7 +261,7 @@ class Config extends \Prefab {
* get map default config values for map types (private/corp/ally)
* -> read from pathfinder.ini
* @param string $mapType
* @return array
* @return mixed
*/
static function getMapsDefaultConfig($mapType = ''){
if( $mapConfig = self::getPathfinderData('map' . ($mapType ? '.' . $mapType : '')) ){
@@ -206,6 +271,92 @@ class Config extends \Prefab {
return $mapConfig;
}
/**
* get custom $message for a a HTTP $status
* -> use this in addition to the very general Base::HTTP_XXX labels
* @param int $status
* @return string
*/
static function getMessageFromHTTPStatus(int $status): string {
switch($status){
case 403:
$message = 'Access denied: User not found'; break;
default:
$message = '';
}
return $message;
}
/**
* 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 checkSocketRequirements(): bool {
return extension_loaded('zmq') && class_exists('ZMQ');
}
/**
* use this function to "validate" the socket connection.
* The result will be CACHED for a few seconds!
* This function is intended to pre-check a Socket connection if it MIGHT exists.
* No data will be send to the Socket, this function just validates if a socket is available
* -> see pingDomain()
* @return bool
*/
static function validSocketConnect(): bool{
$valid = false;
$f3 = \Base::instance();
if( !$f3->exists(self::CACHE_KEY_SOCKET_VALID, $valid) ){
if(self::checkSocketRequirements() && ($socketUrl = self::getSocketUri()) ){
// get socket URI parts -> not elegant...
$domain = parse_url( $socketUrl, PHP_URL_SCHEME) . '://' . parse_url( $socketUrl, PHP_URL_HOST);
$port = parse_url( $socketUrl, PHP_URL_PORT);
// check connection -> get ms
$status = self::pingDomain($domain, $port);
if($status >= 0){
// connection OK
$valid = true;
}else{
// connection error/timeout
$valid = false;
}
}else{
// requirements check failed or URL not valid
$valid = false;
}
$f3->set(self::CACHE_KEY_SOCKET_VALID, $valid, self::CACHE_TTL_SOCKET_VALID);
}
return $valid;
}
/**
* get response time for a host in ms or -1 on error/timeout
* @param string $domain
* @param int $port
* @param int $timeout
* @return int
*/
static function pingDomain(string $domain, int $port, $timeout = 1): int {
$starttime = microtime(true);
$file = @fsockopen ($domain, $port, $errno, $errstr, $timeout);
$stoptime = microtime(true);
if (!$file){
// Site is down
$status = -1;
}else {
fclose($file);
$status = ($stoptime - $starttime) * 1000;
$status = floor($status);
}
return $status;
}
/**
* get URI for TCP socket
* @return bool|string

View File

@@ -0,0 +1,87 @@
<?php
/**
* Created by PhpStorm.
* User: exodu
* Date: 04.08.2017
* Time: 22:13
*/
namespace Lib\Logging;
abstract class AbstractChannelLog extends AbstractLog {
/**
* @var array
*/
protected $channelData = [];
public function __construct(string $action, array $channelData){
parent::__construct($action);
$this->setChannelData($channelData);
// add log processor -> remove $channelData from log
$processorClearChannelData = function($record){
$record['context'] = array_diff_key($record['context'], $this->getChannelData());
return $record;
};
// init processorConfig. IMPORTANT: first processor gets executed at the end!
$this->processorConfig = ['clearChannelData' => $processorClearChannelData] + $this->processorConfig;
}
/**
* @param array $channelData
*/
protected function setChannelData(array $channelData){
$this->channelData = $channelData;
}
/**
* @return array
*/
public function getChannelData() : array{
return $this->channelData;
}
/**
* @return int
*/
public function getChannelId() : int{
return (int)$this->getChannelData()['channelId'];
}
/**
* @return string
*/
public function getChannelName() : string{
return (string)$this->getChannelData()['channelName'];
}
/**
* @return array
*/
public function getData() : array{
$data['main'] = parent::getData();
if(!empty($channelLogData = $this->getChannelData())){
$channelData['channel'] = $channelLogData;
$data = $channelData + $data;
}
return $data;
}
/**
* @return array
*/
public function getContext(): array{
$context = parent::getContext();
// add temp data (e.g. used for $message placeholder replacement
$context += $this->getChannelData();
return $context;
}
}

View File

@@ -0,0 +1,81 @@
<?php
/**
* Created by PhpStorm.
* User: exodu
* Date: 22.09.2017
* Time: 19:05
*/
namespace Lib\Logging;
use lib\Config;
use Model\CharacterModel;
abstract class AbstractCharacterLog extends AbstractChannelLog{
/**
* @var CharacterModel
*/
private $character = null;
public function __construct(string $action, array $objectData){
parent::__construct($action, $objectData);
// add log processor -> remove $channelData from log
$processorAddThumbData = function($record){
$record['extra']['thumb']['url'] = $this->getThumbUrl();
return $record;
};
// init processorConfig. IMPORTANT: first processor gets executed at the end!
$this->processorConfig = ['addThumbData' => $processorAddThumbData] + $this->processorConfig;
}
/**
* CharacterModel $character
* @param CharacterModel $character
* @return LogInterface
*/
public function setCharacter(CharacterModel $character): LogInterface{
$this->character = $character;
return $this;
}
/**
* @return CharacterModel
*/
public function getCharacter(): CharacterModel{
return $this->character;
}
/**
* @return array
*/
public function getData() : array{
$data = parent::getData();
if(is_object($character = $this->getCharacter())){
$characterData['character'] = [
'id' => $character->_id,
'name' => $character->name
];
$data = $characterData + $data;
}
return $data;
}
/**
* get character thumbnailUrl
* @return string
*/
protected function getThumbUrl(): string {
$url = '';
if(is_object($character = $this->getCharacter())){
$url = Config::getPathfinderData('api.ccp_image_server') . '/Character/' . $character->_id . '_128.jpg';
}
return $url;
}
}

View File

@@ -0,0 +1,549 @@
<?php
/**
* Created by PhpStorm.
* User: exodu
* Date: 04.08.2017
* Time: 22:13
*/
namespace Lib\Logging;
use Lib\Monolog;
use Monolog\Logger;
abstract class AbstractLog implements LogInterface {
const ERROR_LEVEL = 'Invalid log level "%s"';
const ERROR_TAG = 'Invalid log tag "%s"';
const ERROR_HANDLER_KEY = 'Handler key "%s" not found in handlerConfig (%s)';
const ERROR_HANDLER_PARAMS = 'No handler parameters found for handler key "%s"';
/**
* PSR-3 log levels
*/
const LEVEL = ['emergency', 'alert', 'critical', 'error', 'warning', 'notice', 'info', 'debug'];
/**
* log tags
*/
const TAG = ['danger', 'warning', 'information', 'success', 'primary', 'default'];
/**
* @var null|\Base
*/
protected $f3 = null;
/**
* log Handler type with Formatter type
* -> check Monolog::HANDLER and Monolog::FORMATTER
* @var array
*/
protected $handlerConfig = ['stream' => 'line'];
/**
* log Processors, array with either callable functions or Processor class with __invoce() method
* -> functions used to add "extra" data to a log
* @var array
*/
protected $processorConfig = ['psr' => null];
/**
* some handler need individual configuration parameters
* -> see $handlerConfig end getHandlerParams()
* @var array
*/
protected $handlerParamsConfig = [];
/**
* multiple Log() objects can be marked as "grouped"
* -> Logs with Slack Handler should be grouped by map (send multiple log data in once
* @var array
*/
protected $handlerGroups = [];
/**
* @var string
*/
protected $message = '';
/**
* @var string
*/
protected $action = '';
/**
* @var string
*/
protected $channelType = '';
/**
* log level from self::LEVEL
* -> private - use setLevel() to set
* @var string
*/
private $level = 'debug';
/**
* log tag from self::TAG
* -> private - use setTag() to set
* @var string
*/
private $tag = 'default';
/**
* log data (main log data)
* @var array
*/
private $data = [];
/**
* (optional) temp data for logger (will not be stored with the log entry)
* @var array
*/
private $tmpData = [];
/**
* buffer multiple logs with the same chanelType and store all at once
* @var bool
*/
private $buffer = true;
public function __construct(string $action){
$this->setF3();
$this->action = $action;
// add custom log processor callback -> add "extra" (meta) data
$f3 = $this->f3;
$processorExtraData = function($record) use(&$f3){
$record['extra'] = [
'path' => $f3->get('PATH'),
'ip' => $f3->get('IP')
];
return $record;
};
// add log processor -> remove §tempData from log
$processorClearTempData = function($record){
$record['context'] = array_diff_key($record['context'], $this->getTempData());
return $record;
};
// init processorConfig. IMPORTANT: first processor gets executed at the end!
$this->processorConfig = ['cleaTempData' => $processorClearTempData] + [ 'addExtra' => $processorExtraData] + $this->processorConfig;
}
/**
* set $f3 base object
*/
public function setF3(){
$this->f3 = \Base::instance();
}
/**
* @param $message
*/
public function setMessage(string $message){
$this->message = $message;
}
/**
* @param string $level
* @throws \Exception
*/
public function setLevel(string $level){
if( in_array($level, self::LEVEL)){
$this->level = $level;
}else{
throw new \Exception( sprintf(self::ERROR_LEVEL, $level));
}
}
/**
* @param string $tag
* @throws \Exception
*/
public function setTag(string $tag){
if( in_array($tag, self::TAG)){
$this->tag = $tag;
}else{
throw new \Exception( sprintf(self::ERROR_TAG, $tag));
}
}
/**
* @param array $data
* @return LogInterface
*/
public function setData(array $data): LogInterface{
$this->data = $data;
return $this;
}
/**
* @param array $data
* @return LogInterface
*/
public function setTempData(array $data): LogInterface{
$this->tmpData = $data;
return $this;
}
/**
* add new Handler by $handlerKey
* set its default Formatter by $formatterKey
* @param string $handlerKey
* @param string|null $formatterKey
* @param \stdClass|null $handlerParams
* @return LogInterface
*/
public function addHandler(string $handlerKey, string $formatterKey = null, \stdClass $handlerParams = null): LogInterface{
if(!$this->hasHandlerKey($handlerKey)){
$this->handlerConfig[$handlerKey] = $formatterKey;
// add more configuration params for the new handler
if(!is_null($handlerParams)){
$this->handlerParamsConfig[$handlerKey] = $handlerParams;
}
}
return $this;
}
/**
* add new handler for Log() grouping
* @param string $handlerKey
* @return LogInterface
*/
public function addHandlerGroup(string $handlerKey): LogInterface{
if(
$this->hasHandlerKey($handlerKey) &&
!$this->hasHandlerGroupKey($handlerKey)
){
$this->handlerGroups[] = $handlerKey;
}
return $this;
}
/**
* @return array
*/
public function getHandlerConfig(): array{
return $this->handlerConfig;
}
/**
* get __construct() parameters for a given $handlerKey
* @param string $handlerKey
* @return array
* @throws \Exception
*/
public function getHandlerParams(string $handlerKey): array{
$params = [];
if($this->hasHandlerKey($handlerKey)){
switch($handlerKey){
case 'stream': $params = $this->getHandlerParamsStream();
break;
case 'zmq': $params = $this->getHandlerParamsZMQ();
break;
case 'mail': $params = $this->getHandlerParamsMail();
break;
case 'slackMap':
case 'slackRally':
$params = $this->getHandlerParamsSlack($handlerKey);
break;
default:
throw new \Exception( sprintf(self::ERROR_HANDLER_PARAMS, $handlerKey));
}
}else{
throw new \Exception( sprintf(self::ERROR_HANDLER_KEY, $handlerKey, implode(', ', array_flip($this->handlerConfig))));
}
return $params;
}
/**
* @return array
*/
public function getHandlerParamsConfig(): array{
return $this->handlerParamsConfig;
}
/**
* @return array
*/
public function getProcessorConfig(): array{
return $this->processorConfig;
}
/**
* @return string
*/
public function getMessage(): string{
return $this->message;
}
/**
* @return string
*/
public function getAction(): string{
return $this->action;
}
/**
* @return string
*/
public function getChannelType(): string{
return $this->channelType;
}
/**
* @return string
*/
public function getChannelName(): string{
return $this->getChannelType();
}
/**
* @return string
*/
public function getLevel(): string{
return $this->level;
}
/**
* @return string
*/
public function getTag(): string{
return $this->tag;
}
/**
* @return array
*/
public function getData(): array{
return $this->data;
}
/**
* @return array
*/
public function getContext(): array{
$context = [
'data' => $this->getData(),
'tag' => $this->getTag()
];
// add temp data (e.g. used for $message placeholder replacement
$context += $this->getTempData();
return $context;
}
/**
* @return array
*/
protected function getTempData(): array {
return $this->tmpData;
}
/**
* @return array
*/
public function getHandlerGroups(): array{
return $this->handlerGroups;
}
/**
* get unique hash for this kind of logs (channel) and same $handlerGroups
* @return string
*/
public function getGroupHash(): string {
$groupName = $this->getChannelName();
if($this->isGrouped()){
$groupName .= '_' . implode('_', $this->getHandlerGroups());
}
return $this->f3->hash($groupName);
}
/**
* @param string $handlerKey
* @return bool
*/
public function hasHandlerKey(string $handlerKey): bool{
return array_key_exists($handlerKey, $this->handlerConfig);
}
/**
* @param string $handlerKey
* @return bool
*/
public function hasHandlerGroupKey(string $handlerKey): bool{
return in_array($handlerKey, $this->getHandlerGroups());
}
/**
* @return bool
*/
public function hasBuffer(): bool{
return $this->buffer;
}
/**
* @return bool
*/
public function isGrouped(): bool{
return !empty($this->getHandlerGroups());
}
/**
* remove all group handlers and their config params
*/
public function removeHandlerGroups(){
foreach($this->getHandlerGroups() as $handlerKey){
$this->removeHandlerGroup($handlerKey);
}
}
/**
* @param string $handlerKey
*/
public function removeHandlerGroup(string $handlerKey){
unset($this->handlerConfig[$handlerKey]);
unset($this->handlerParamsConfig[$handlerKey]);
}
// Handler parameters for Monolog\Handler\AbstractHandler ---------------------------------------------------------
protected function getHandlerParamsStream(): array{
$params = [];
if( !empty($conf = $this->handlerParamsConfig['stream']) ){
$params[] = $conf->stream;
}
return $params;
}
/**
* get __construct() parameters for ZMQHandler() call
* @return array
*/
protected function getHandlerParamsZMQ(): array {
$params = [];
if( !empty($conf = $this->handlerParamsConfig['zmq']) ){
// meta data (required by receiver socket)
$meta = [
'logType' => 'mapLog',
'stream'=> $conf->streamConf->stream
];
$context = new \ZMQContext();
$pusher = $context->getSocket(\ZMQ::SOCKET_PUSH);
$pusher->connect($conf->uri);
$params[] = $pusher;
$params[] = \ZMQ::MODE_DONTWAIT;
$params[] = false; // multipart
$params[] = Logger::toMonologLevel($this->getLevel()); // min level that is handled
$params[] = true; // bubble
$params[] = $meta;
}
return $params;
}
/**
* get __construct() parameters for SwiftMailerHandler() call
* @return array
*/
protected function getHandlerParamsMail(): array{
$params = [];
if( !empty($conf = $this->handlerParamsConfig['mail']) ){
$transport = (new \Swift_SmtpTransport())
->setHost($conf->host)
->setPort($conf->port)
->setEncryption($conf->scheme)
->setUsername($conf->username)
->setPassword($conf->password)
->setStreamOptions([
'ssl' => [
'allow_self_signed' => true,
'verify_peer' => false
]
]);
$mailer = new \Swift_Mailer($transport);
// callback function used instead of Swift_Message() object
// -> we want the formatted/replaced message as subject
$messageCallback = function($content, $records) use ($conf){
$subject = 'No Subject';
if(!empty($records)){
// build subject from first record -> remove "markdown"
$subject = str_replace(['*', '_'], '', $records[0]['message']);
}
$jsonData = @json_encode($records, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
$message = (new \Swift_Message())
->setSubject($subject)
->addPart($jsonData)
->setFrom($conf->from)
->setTo($conf->to)
->setContentType('text/html')
->setCharset('utf-8')
->setMaxLineLength(1000);
if($conf->addJson){
$jsonAttachment = (new \Swift_Attachment())
->setFilename('data.json')
->setContentType('application/json')
->setBody($jsonData);
$message->attach($jsonAttachment);
}
return $message;
};
$params[] = $mailer;
$params[] = $messageCallback;
$params[] = Logger::toMonologLevel($this->getLevel()); // min level that is handled
$params[] = true; // bubble
}
return $params;
}
/**
* get __construct() params for SlackWebhookHandler() call
* @param string $handlerKey
* @return array
*/
protected function getHandlerParamsSlack(string $handlerKey): array {
$params = [];
if( !empty($conf = $this->handlerParamsConfig[$handlerKey]) ){
$params[] = $conf->slackWebHookURL;
$params[] = $conf->slackChannel;
$params[] = $conf->slackUsername;
$params[] = true; // $useAttachment
$params[] = $conf->slackIcon;
$params[] = true; // $includeContext
$params[] = false; // $includeExtra
$params[] = Logger::toMonologLevel($this->getLevel()); // min level that is handled
$params[] = true; // $bubble
//$params[] = ['extra', 'context.tag']; // $excludeFields
$params[] = []; // $excludeFields
}
return $params;
}
/**
* send this Log to global log buffer storage
*/
public function buffer(){
Monolog::instance()->push($this);
}
}

View File

@@ -0,0 +1,19 @@
<?php
/**
* Created by PhpStorm.
* User: exodu
* Date: 04.08.2017
* Time: 22:08
*/
namespace Lib\Logging;
class DefaultLog extends AbstractLog {
/**
* @var string
*/
protected $channelType = 'default';
}

View File

@@ -0,0 +1,178 @@
<?php
/**
* Created by PhpStorm.
* User: exodu
* Date: 16.09.2017
* Time: 11:23
*/
namespace Lib\Logging;
class LogCollection extends AbstractLog {
const ERROR_EMPTY = __CLASS__ . ' is empty';
/**
* handlers for this collection
* -> no default is set
* @var array
*/
protected $handlerConfig = [];
/**
* processors for this collection
* -> no default is set
* @var array
*/
protected $processorConfig = [];
/**
* @var null|\SplObjectStorage
*/
private $collection = null;
public function __construct(string $action){
parent::__construct($action);
$this->collection = new \SplObjectStorage();
}
/**
* get first Log from Collection
* @return AbstractLog
* @throws \Exception
*/
protected function getPrimaryLog(): AbstractLog{
$this->collection->rewind();
if($this->collection->valid()){
/**
* @var $log AbstractLog
*/
$log = $this->collection->current();
}else{
throw new \Exception( self::ERROR_EMPTY);
}
return $log;
}
/**
* add a new log object to this collection
* @param AbstractLog $log
*/
public function addLog(AbstractLog $log){
if(!$this->collection->contains($log)){
if(!$this->collection->count()){
// first log sets the default for this collection
$this->channelType = $log->getChannelType();
// get relevant handlerKeys for this collection
$handlerGroups = array_flip($log->getHandlerGroups());
// remove handlers that are not relevant for this collection
$handlerConfig = $log->getHandlerConfig();
$handlerConfigGroup = array_intersect_key($handlerConfig, $handlerGroups);
// remove handlersParams that are not relevant for this collection
$handlerParamsConfig = $log->getHandlerParamsConfig();
$handlerParamsConfigGroup = array_intersect_key($handlerParamsConfig, $handlerGroups);
// add all handlers that are relevant for this collection
foreach($handlerConfigGroup as $handlerKey => $formatterKey){
$handlerParams = array_key_exists($handlerKey, $handlerParamsConfigGroup) ? $handlerParamsConfigGroup[$handlerKey] : null;
$this->addHandler($handlerKey, $formatterKey, $handlerParams);
}
// add processors for this collection
$this->processorConfig = $log->getProcessorConfig();
}
$this->setMessage($log->getMessage());
$this->setTag($log->getTag());
$this->collection->attach($log);
}
}
/**
* @param string $message
*/
public function setMessage(string $message){
$currentMessage = parent::getMessage();
if(empty($currentMessage)){
$newMessage = $message;
}elseif($message !== $currentMessage){
$newMessage = 'multi changes';
}else{
$newMessage = $currentMessage ;
}
parent::setMessage($newMessage);
}
/**
* @param string $tag
*/
public function setTag(string $tag){
$currentTag = parent::getTag();
switch($currentTag){
case 'default':
// no specific tag set so far... set new
$newTag = $tag; break;
case 'information':
// do not change "information" tag (mixed tag logs in this collection)
$newTag = $currentTag; break;
default:
// set mixed tag -> "information"
$newTag = ($tag !== $currentTag) ? 'information': $tag;
}
parent::setTag($newTag);
}
/**
* get log data for all logs in this collection
* @return array
*/
public function getData() : array{
$this->collection->rewind();
$data = [];
while($this->collection->valid()){
$data[] = $this->collection->current()->getData();
$this->collection->next();
}
return $data;
}
/**
* @return string
*/
public function getChannelName() : string{
return $this->getPrimaryLog()->getChannelName();
}
/**
* @return string
*/
public function getLevel() : string{
return $this->getPrimaryLog()->getLevel();
}
/**
* @return bool
*/
public function hasBuffer() : bool{
return $this->getPrimaryLog()->hasBuffer();
}
/**
* @return array
*/
public function getTempData() : array{
return $this->getPrimaryLog()->getTempData();
}
}

View File

@@ -0,0 +1,64 @@
<?php
/**
* Created by PhpStorm.
* User: exodu
* Date: 05.08.2017
* Time: 14:10
*/
namespace Lib\Logging;
interface LogInterface {
public function setMessage(string $message);
public function setLevel(string $level);
public function setData(array $data): LogInterface;
public function setTempData(array $data): LogInterface;
public function addHandler(string $handlerKey, string $formatterKey = null, \stdClass $handlerParams = null): LogInterface;
public function addHandlerGroup(string $handlerKey): LogInterface;
public function getHandlerConfig(): array;
public function getHandlerParamsConfig(): array;
public function getProcessorConfig(): array;
public function getHandlerParams(string $handlerKey): array;
public function getMessage(): string;
public function getAction(): string;
public function getChannelType(): string;
public function getChannelName(): string;
public function getLevel(): string;
public function getData(): array;
public function getContext(): array;
public function getHandlerGroups(): array;
public function getGroupHash(): string;
public function hasHandlerKey(string $handlerKey): bool;
public function hasHandlerGroupKey(string $handlerKey): bool;
public function hasBuffer(): bool;
public function isGrouped(): bool;
public function removeHandlerGroups();
public function removeHandlerGroup(string $handlerKey);
}

View File

@@ -0,0 +1,166 @@
<?php
/**
* Created by PhpStorm.
* User: exodu
* Date: 04.08.2017
* Time: 22:08
*/
namespace Lib\Logging;
use controller\LogController;
class MapLog extends AbstractCharacterLog{
/**
* List of possible handlers (tested)
* -> final handler will be set dynamic for per instance
* @var array
*/
protected $handlerConfig = [
//'stream' => 'json',
//'zmq' => 'json',
//'slackMap' => 'json'
];
/**
* @var string
*/
protected $channelType = 'map';
/**
* @var bool
*/
protected $logActivity = false;
public function __construct(string $action, array $objectData){
parent::__construct($action, $objectData);
$this->setLevel('info');
$this->setTag($this->getTagFromAction());
}
/**
* get log tag depending on log action
* @return string
*/
public function getTagFromAction(){
$tag = parent::getTag();
$actionParts = $this->getActionParts();
switch($actionParts[1]){
case 'create': $tag = 'success'; break;
case 'update': $tag = 'warning'; break;
case 'delete': $tag = 'danger'; break;
}
return $tag;
}
/**
* @return string
*/
public function getChannelName(): string{
return $this->getChannelType() . '_' . $this->getChannelId();
}
/**
* @return string
*/
public function getMessage() : string{
return $this->getActionParts()[0] . " '{objName}'";
}
/**
* @return array
*/
public function getData() : array{
$data = parent::getData();
// add system, connection, signature data -------------------------------------------------
if(!empty($tempLogData = $this->getTempData())){
$objectData['object'] = $tempLogData;
$data = $objectData + $data;
}
// add human readable changes to string ---------------------------------------------------
$data['formatted'] = $this->formatData($data);
return $data;
}
/**
* @param array $data
* @return string
*/
protected function formatData(array $data): string{
$actionParts = $this->getActionParts();
$objectString = !empty($data['object']) ? "'" . $data['object']['objName'] . "'" . ' #' . $data['object']['objId'] : '';
$string = ucfirst($actionParts[1]) . 'd ' . $actionParts[0] . " " . $objectString;
// format changed columns (recursive) ---------------------------------------------
switch($actionParts[1]){
case 'create':
case 'update':
$formatChanges = function(array $changes) use ( &$formatChanges ): string{
$string = '';
foreach($changes as $field => $value){
if(is_array($value)){
$string .= $field . ": ";
$string .= $formatChanges($value);
$string .= next( $changes ) ? " , " : '';
}else{
if(is_numeric($value)){
$formattedValue = $value;
}elseif(is_null($value)){
$formattedValue = "NULL";
}elseif(empty($value)){
$formattedValue = "' '";
}elseif(is_string($value)){
$formattedValue = "'" . $value . "'";
}else{
$formattedValue = (string)$value;
}
$string .= $formattedValue;
if($field == 'old'){
$string .= "";
}
}
}
return $string;
};
$string .= ' | ' . $formatChanges($data['main']);
break;
}
return $string;
}
/**
* split $action "CamelCase" wise
* @return array
*/
protected function getActionParts(): array{
return array_map('strtolower', preg_split('/(?=[A-Z])/', $this->getAction()));
}
/**
* @param bool $logActivity
*/
public function logActivity(bool $logActivity){
$this->logActivity = $logActivity;
}
public function buffer(){
parent::buffer();
if($this->logActivity){
// map logs should also used for "activity" logging
LogController::instance()->push($this);
}
}
}

View File

@@ -0,0 +1,105 @@
<?php
/**
* Created by PhpStorm.
* User: exodu
* Date: 22.09.2017
* Time: 16:50
*/
namespace Lib\Logging;
use lib\Config;
class RallyLog extends AbstractCharacterLog{
/**
* List of possible handlers (tested)
* -> final handler will be set dynamic for per instance
* @var array
*/
protected $handlerConfig = [
// 'slackRally' => 'json',
// 'mail' => 'html'
];
/**
* @var string
*/
protected $channelType = 'rally';
public function __construct(string $action, array $objectData){
parent::__construct($action, $objectData);
$this->setLevel('notice');
$this->setTag('information');
}
/**
* @return string
*/
protected function getThumbUrl() : string{
$url = '';
if(is_object($character = $this->getCharacter())){
$characterLog = $character->getLog();
if($characterLog && !empty($characterLog->shipTypeId)){
$url = Config::getPathfinderData('api.ccp_image_server') . '/Render/' . $characterLog->shipTypeId . '_64.png';
}else{
$url = parent::getThumbUrl();
}
}
return $url;
}
/**
* @return string
*/
public function getMessage() : string{
return "*New RallyPoint system '{objName}'* _#{objId}_ *map '{channelName}'* _#{channelId}_ ";
}
/**
* @return array
*/
public function getData() : array{
$data = parent::getData();
// add system -----------------------------------------------------------------------------
if(!empty($tempLogData = $this->getTempData())){
$objectData['object'] = $tempLogData;
$data = $objectData + $data;
}
// add human readable changes to string ---------------------------------------------------
$data['formatted'] =$this->formatData($data);
return $data;
}
/**
* @param array $data
* @return string
*/
protected function formatData(array $data): string{
$string = '';
if(
!empty($data['object']) &&
!empty($data['channel'])
){
$replace = [
'{objName}' => $data['object']['objName'],
'{objId}' => $data['object']['objId'],
'{channelName}' => $data['channel']['channelName'],
'{channelId}' => $data['channel']['channelId']
];
$string = str_replace(array_keys($replace), array_values($replace), $this->getMessage());
}
return $string;
}
}

View File

@@ -0,0 +1,36 @@
<?php
/**
* Created by PhpStorm.
* User: exodu
* Date: 07.10.2017
* Time: 14:15
*/
namespace Lib\Logging;
class UserLog extends AbstractChannelLog {
/**
* List of possible handlers (tested)
* -> final handler will be set dynamic for per instance
* @var array
*/
protected $handlerConfig = [
// 'mail' => 'html'
];
/**
* @var string
*/
protected $channelType = 'user';
public function __construct(string $action, array $objectData){
parent::__construct($action, $objectData);
$this->setLevel('notice');
$this->setTag('information');
}
}

View File

@@ -0,0 +1,46 @@
<?php
/**
* Created by PhpStorm.
* User: exodu
* Date: 07.10.2017
* Time: 14:49
*/
namespace Lib\Logging\Formatter;
use lib\Config;
use Monolog\Formatter;
class MailFormatter implements Formatter\FormatterInterface {
public function format(array $record){
$tplDefaultData = [
'tplPretext' => $record['message'],
'tplGreeting' => \Markdown::instance()->convert(str_replace('*', '', $record['message'])),
'message' => false,
'tplText2' => false,
'tplClosing' => 'Fly save!',
'actionPrimary' => false,
'appName' => Config::getPathfinderData('name'),
'appUrl' => Config::getEnvironmentData('URL'),
'appHost' => $_SERVER['HTTP_HOST'],
'appContact' => Config::getPathfinderData('contact'),
'appMail' => Config::getPathfinderData('email'),
];
$tplData = array_replace_recursive($tplDefaultData, (array)$record['context']['data']['main']);
return \Template::instance()->render('templates/mail/basic_inline.html', 'text/html', $tplData);
}
public function formatBatch(array $records){
$message = '';
foreach ($records as $key => $record) {
$message .= $this->format($record);
}
return $message;
}
}

View File

@@ -0,0 +1,260 @@
<?php
/**
* Created by PhpStorm.
* User: exodu
* Date: 22.09.2017
* Time: 20:08
*/
namespace Lib\Logging\Handler;
use lib\Config;
use Monolog\Handler;
use Monolog\Logger;
abstract class AbstractSlackWebhookHandler extends Handler\AbstractProcessingHandler {
/**
* @var string
*/
private $webhookUrl;
/**
* Slack channel (encoded ID or name)
* @var string|null
*/
private $channel;
/**
* Name of a bot
* @var string|null
*/
private $username;
/**
* User icon e.g. 'ghost', 'http://example.com/user.png'
* @var string
*/
private $userIcon;
/**
* Whether the message should be added to Slack as attachment (plain text otherwise)
* @var bool
*/
protected $useAttachment;
/**
* Whether the attachment should include context
* @var bool
*/
protected $includeContext;
/**
* Whether the attachment should include extra
* @var bool
*/
protected $includeExtra;
/**
* Dot separated list of fields to exclude from slack message. E.g. ['context.field1', 'extra.field2']
* @var array
*/
private $excludeFields;
/**
* Max attachment count per message (20 is max)
* @var int
*/
private $maxAttachments = 15;
/**
* @param string $webhookUrl Slack Webhook URL
* @param string|null $channel Slack channel (encoded ID or name)
* @param string|null $username Name of a bot
* @param bool $useAttachment Whether the message should be added to Slack as attachment (plain text otherwise)
* @param string|null $iconEmoji The emoji name to use (or null)
* @param bool $includeContext Whether the context data added to Slack as attachments are in a short style
* @param bool $includeExtra Whether the extra data added to Slack as attachments are in a short style
* @param int $level The minimum logging level at which this handler will be triggered
* @param bool $bubble Whether the messages that are handled can bubble up the stack or not
* @param array $excludeFields Dot separated list of fields to exclude from slack message. E.g. ['context.field1', 'extra.field2']
*/
public function __construct($webhookUrl, $channel = null, $username = null, $useAttachment = true, $iconEmoji = null, $includeContext = true, $includeExtra = false, $level = Logger::CRITICAL, $bubble = true, array $excludeFields = []){
$this->webhookUrl = $webhookUrl;
$this->channel = $channel;
$this->username = $username;
$this->userIcon = trim($iconEmoji, ':');
$this->useAttachment = $useAttachment;
$this->includeContext = $includeContext;
$this->includeExtra = $includeExtra;
$this->excludeFields = $excludeFields;
parent::__construct($level, $bubble);
}
/**
* format
* @param array $record
* @return array
*/
protected function getSlackData(array $record): array {
$postData = [];
if ($this->username) {
$postData['username'] = $this->username;
}
if ($this->channel) {
$postData['channel'] = $this->channel;
}
$postData['text'] = (string)$record['message'];
if ($this->userIcon) {
if (filter_var($this->userIcon, FILTER_VALIDATE_URL)) {
$postData['icon_url'] = $this->userIcon;
} else {
$postData['icon_emoji'] = ":{$this->userIcon}:";
}
}
return $postData;
}
/**
* {@inheritdoc}
*
* @param array $record
*/
protected function write(array $record){
$record = $this->excludeFields($record);
$postData = $this->getSlackData($record);
$postData = $this->cleanAttachments($postData);
$postString = json_encode($postData);
$ch = curl_init();
$options = [
CURLOPT_URL => $this->webhookUrl,
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => ['Content-type: application/json'],
CURLOPT_POSTFIELDS => $postString
];
if (defined('CURLOPT_SAFE_UPLOAD')) {
$options[CURLOPT_SAFE_UPLOAD] = true;
}
curl_setopt_array($ch, $options);
Handler\Curl\Util::execute($ch);
}
/**
* @param array $postData
* @return array
*/
protected function cleanAttachments(array $postData): array{
$attachmentCount = count($postData['attachments']);
if( $attachmentCount > $this->maxAttachments){
$text = 'To many attachments! ' . ($attachmentCount - $this->maxAttachments) . ' of ' . $attachmentCount . ' attachments not visible';
$postData['attachments'] = array_slice($postData['attachments'], 0, $this->maxAttachments);
$attachment = [
'title' => $text,
'fallback' => $text,
'color' => $this->getAttachmentColor('information')
];
$postData['attachments'][] = $attachment;
}
return $postData;
}
/**
* @param array $attachment
* @param array $characterData
* @return array
*/
protected function setAuthor(array $attachment, array $characterData): array {
if( !empty($characterData['id']) && !empty($characterData['name'])){
$attachment['author_name'] = $characterData['name'] . ' #' . $characterData['id'];
$attachment['author_link'] = Config::getPathfinderData('api.z_killboard') . '/character/' . $characterData['id'] . '/';
$attachment['author_icon'] = Config::getPathfinderData('api.ccp_image_server') . '/Character/' . $characterData['id'] . '_32.jpg';
}
return $attachment;
}
/**
* @param array $attachment
* @param array $thumbData
* @return array
*/
protected function setThumb(array $attachment, array $thumbData): array {
if( !empty($thumbData['url'])) {
$attachment['thumb_url'] = $thumbData['url'];
}
return $attachment;
}
/**
* @param $title
* @param $value
* @param bool $format
* @param bool $short
* @return array
*/
protected function generateAttachmentField($title, $value, $format = false, $short = true){
return [
'title' => $title,
'value' => !empty($value) ? ( $format ? sprintf('`%s`', $value) : $value ) : '',
'short' => $short
];
}
/**
* @param string $tag
* @return string
*/
protected function getAttachmentColor(string $tag): string {
switch($tag){
case 'information': $color = '#428bca'; break;
case 'success': $color = '#4f9e4f'; break;
case 'warning': $color = '#e28a0d'; break;
case 'danger': $color = '#a52521'; break;
default: $color = '#313335'; break;
}
return $color;
}
/**
* Get a copy of record with fields excluded according to $this->excludeFields
* @param array $record
* @return array
*/
private function excludeFields(array $record){
foreach($this->excludeFields as $field){
$keys = explode('.', $field);
$node = &$record;
$lastKey = end($keys);
foreach($keys as $key){
if(!isset($node[$key])){
break;
}
if($lastKey === $key){
unset($node[$key]);
break;
}
$node = &$node[$key];
}
}
return $record;
}
}

View File

@@ -0,0 +1,101 @@
<?php
/**
* Created by PhpStorm.
* User: exodu
* Date: 10.09.2017
* Time: 20:52
*/
namespace Lib\Logging\Handler;
use Lib\Util;
class SlackMapWebhookHandler extends AbstractSlackWebhookHandler {
/**
* @param array $record
* @return array
*/
protected function getSlackData(array $record) : array{
$postData = parent::getSlackData($record);
$tag = (string)$record['context']['tag'];
$timestamp = (int)$record['datetime']->getTimestamp();
$text = '';
if (
$this->useAttachment &&
!empty( $attachmentsData = $record['context']['data'])
) {
// convert non grouped data (associative array) to multi dimensional (sequential) array
// -> see "group" records
$attachmentsData = Util::is_assoc($attachmentsData) ? [$attachmentsData] : $attachmentsData;
$thumbData = (array)$record['extra']['thumb'];
$postData['attachments'] = [];
foreach($attachmentsData as $attachmentData){
$channelData = (array)$attachmentData['channel'];
$characterData = (array)$attachmentData['character'];
$formatted = (string)$attachmentData['formatted'];
// get "message" from $formatted
$msgParts = explode('|', $formatted, 2);
// build main text from first Attachment (they belong to same channel)
if(!empty($channelData)){
$text = "*Map '" . $channelData['channelName'] . "'* _#" . $channelData['channelId'] . "_ *changed*";
}
$attachment = [
'title' => !empty($msgParts[0]) ? $msgParts[0] : 'No Title',
//'pretext' => '',
'text' => !empty($msgParts[1]) ? sprintf('```%s```', $msgParts[1]) : '',
'fallback' => !empty($msgParts[1]) ? $msgParts[1] : 'No Fallback',
'color' => $this->getAttachmentColor($tag),
'fields' => [],
'mrkdwn_in' => ['fields', 'text'],
'footer' => 'Pathfinder API',
//'footer_icon'=> '',
'ts' => $timestamp
];
$attachment = $this->setAuthor($attachment, $characterData);
$attachment = $this->setThumb($attachment, $thumbData);
// set 'field' array ----------------------------------------------------------------------------------
if ($this->includeExtra) {
$attachment['fields'][] = $this->generateAttachmentField('', 'Meta data:', false, false);
if(!empty($record['extra']['path'])){
$attachment['fields'][] = $this->generateAttachmentField('Path', $record['extra']['path'], true);
}
if(!empty($tag)){
$attachment['fields'][] = $this->generateAttachmentField('Tag', $tag, true);
}
if(!empty($record['level_name'])){
$attachment['fields'][] = $this->generateAttachmentField('Level', $record['level_name'], true);
}
if(!empty($record['extra']['ip'])){
$attachment['fields'][] = $this->generateAttachmentField('IP', $record['extra']['ip'], true);
}
}
$postData['attachments'][] = $attachment;
}
}
$postData['text'] = empty($text) ? $postData['text'] : $text;
return $postData;
}
}

View File

@@ -0,0 +1,135 @@
<?php
/**
* Created by PhpStorm.
* User: exodu
* Date: 22.09.2017
* Time: 17:32
*/
namespace Lib\Logging\Handler;
use Lib\Util;
class SlackRallyWebhookHandler extends AbstractSlackWebhookHandler {
/**
* @param array $record
* @return array
*/
protected function getSlackData(array $record) : array{
$postData = parent::getSlackData($record);
$tag = (string)$record['context']['tag'];
$timestamp = (int)$record['datetime']->getTimestamp();
$text = '';
if (
$this->useAttachment &&
!empty( $attachmentsData = $record['context']['data'])
){
// convert non grouped data (associative array) to multi dimensional (sequential) array
// -> see "group" records
$attachmentsData = Util::is_assoc($attachmentsData) ? [$attachmentsData] : $attachmentsData;
$thumbData = (array)$record['extra']['thumb'];
$postData['attachments'] = [];
foreach($attachmentsData as $attachmentData){
$characterData = (array)$attachmentData['character'];
$text = 'No Title';
if( !empty($attachmentData['formatted']) ){
$text = $attachmentData['formatted'];
}
$attachment = [
'title' => !empty($attachmentData['main']['message']) ? 'Message' : '',
//'pretext' => '',
'text' => !empty($attachmentData['main']['message']) ? sprintf('```%s```', $attachmentData['main']['message']) : '',
'fallback' => !empty($attachmentData['main']['message']) ? $attachmentData['main']['message'] : 'No Fallback',
'color' => $this->getAttachmentColor($tag),
'fields' => [],
'mrkdwn_in' => ['fields', 'text'],
'footer' => 'Pathfinder API',
//'footer_icon'=> '',
'ts' => $timestamp
];
$attachment = $this->setAuthor($attachment, $characterData);
$attachment = $this->setThumb($attachment, $thumbData);
// set 'field' array ----------------------------------------------------------------------------------
if ($this->includeContext) {
if(!empty($objectData = $attachmentData['object'])){
if(!empty($objectData['objAlias'])){
// System alias
$attachment['fields'][] = $this->generateAttachmentField('Alias', $objectData['objAlias']);
}
if(!empty($objectData['objName'])){
// System name
$attachment['fields'][] = $this->generateAttachmentField('System', $objectData['objName']);
}
if(!empty($objectData['objRegion'])){
// System region
$attachment['fields'][] = $this->generateAttachmentField('Region', $objectData['objRegion']);
}
if(isset($objectData['objIsWormhole'])){
// Is wormhole
$attachment['fields'][] = $this->generateAttachmentField('Wormhole', $objectData['objIsWormhole'] ? 'Yes' : 'No');
}
if(!empty($objectData['objSecurity'])){
// System security
$attachment['fields'][] = $this->generateAttachmentField('Security', $objectData['objSecurity']);
}
if(!empty($objectData['objEffect'])){
// System effect
$attachment['fields'][] = $this->generateAttachmentField('Effect', $objectData['objEffect']);
}
if(!empty($objectData['objTrueSec'])){
// System trueSec
$attachment['fields'][] = $this->generateAttachmentField('TrueSec', $objectData['objTrueSec']);
}
if(!empty($objectData['objDescription'])){
// System trueSec
$attachment['fields'][] = $this->generateAttachmentField('System description', '```' . $objectData['objDescription'] . '```', false, false);
}
}
}
if($this->includeExtra){
if(!empty($record['extra']['path'])){
$attachment['fields'][] = $this->generateAttachmentField('Path', $record['extra']['path'], true);
}
if(!empty($tag)){
$attachment['fields'][] = $this->generateAttachmentField('Tag', $tag, true);
}
if(!empty($record['level_name'])){
$attachment['fields'][] = $this->generateAttachmentField('Level', $record['level_name'], true);
}
if(!empty($record['extra']['ip'])){
$attachment['fields'][] = $this->generateAttachmentField('IP', $record['extra']['ip'], true);
}
}
$postData['attachments'][] = $attachment;
}
}
$postData['text'] = empty($text) ? $postData['text'] : $text;
return $postData;
}
}

View File

@@ -0,0 +1,65 @@
<?php
/**
* Created by PhpStorm.
* User: exodu
* Date: 03.09.2017
* Time: 17:39
*/
namespace Lib\Logging\Handler;
use Monolog\Logger;
class ZMQHandler extends \Websoftwares\Monolog\Handler\ZMQHandler {
/**
* some meta data (additional processing information)
* @var array|string
*/
protected $metaData = [];
public function __construct(
\zmqSocket $zmqSocket,
$zmqMode = \ZMQ::MODE_DONTWAIT,
$multipart = false,
$level = Logger::DEBUG,
$bubble = true,
$metaData = []
){
$this->metaData = $metaData;
parent::__construct($zmqSocket, $zmqMode, $multipart, $level, $bubble);
}
/**
* overwrite default handle()
* -> change data structure after processor() calls and before formatter() calls
* @param array $record
* @return bool
* @throws \Exception
*/
public function handle(array $record){
if (!$this->isHandling($record)) {
return false;
}
$record = $this->processRecord($record);
$record = [
'task' => 'logData',
'load' => [
'meta' => $this->metaData,
'log' => $record
]
];
$record['formatted'] = $this->getFormatter()->format($record);
$this->write($record);
return false === $this->bubble;
}
}

View File

@@ -79,27 +79,19 @@ class Socket {
/**
* 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()){
if(Config::checkSocketRequirements()){
$context = new \ZMQContext();
$this->socket = $context->getSocket(\ZMQ::SOCKET_PUSH);
}
}
public function sendData($task, $load = ''){
/**
* @param $task
* @param string $load
* @return bool|string
*/
public function sendData(string $task, $load = ''){
$response = false;
$this->initSocket();
@@ -122,7 +114,7 @@ class Socket {
//$this->socket->send(json_encode($send), \ZMQ::MODE_DONTWAIT);
$this->socket->send(json_encode($send));
$this->socket->disconnect($this->socketUri);
// $this->socket->disconnect($this->socketUri);
$response = 'OK';
@@ -230,24 +222,5 @@ class Socket {
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;
}
}

View File

@@ -17,11 +17,32 @@ class Util {
* @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));
if(is_array($arr)){
$arr = array_map( function($item){
if( is_array($item) )
$item = self::arrayChangeKeyCaseRecursive($item);
return $item;
}, array_change_key_case((array)$arr, $case));
}
return $arr;
}
/**
* checks whether an array is associative or not (sequential)
* @param mixed $array
* @return bool
*/
static function is_assoc($array): bool {
$isAssoc = false;
if(
is_array($array) &&
array_keys($array) !== range(0, count($array) - 1)
){
$isAssoc = true;
}
return $isAssoc;
}
/**
@@ -59,6 +80,23 @@ class Util {
return $scopes;
}
/**
* obsucre string e.g. password (hide last characters)
* @param string $string
* @param int $maxHideChars
* @return string
*/
static function obscureString(string $string, int $maxHideChars = 10): string {
$formatted = '';
$length = mb_strlen((string)$string);
if($length > 0){
$hideChars = ($length < $maxHideChars) ? $length : $maxHideChars;
$formatted = substr_replace($string, str_repeat('_', min(3, $length)), -$hideChars) .
' [' . $length . ']';
}
return $formatted;
}
/**
* get hash from an array of ESI scopes
* @param array $scopes

View File

@@ -0,0 +1,139 @@
<?php
/**
* Created by PhpStorm.
* User: exodu
* Date: 25.08.2017
* Time: 18:45
*/
namespace Model;
use DB\SQL\Schema;
abstract class AbstractMapTrackingModel extends BasicModel implements LogModelInterface {
private $trackingFieldConf = [
'createdCharacterId' => [
'type' => Schema::DT_INT,
'index' => true,
'belongs-to-one' => 'Model\CharacterModel',
'constraint' => [
[
'table' => 'character',
'on-delete' => 'CASCADE'
]
],
'validate' => 'validate_notDry'
],
'updatedCharacterId' => [
'type' => Schema::DT_INT,
'index' => true,
'belongs-to-one' => 'Model\CharacterModel',
'constraint' => [
[
'table' => 'character',
'on-delete' => 'CASCADE'
]
],
'validate' => 'validate_notDry'
]
];
/**
* get static character fields for this model instance
* @return array
*/
protected function getStaticFieldConf(): array{
return array_merge(parent::getStaticFieldConf(), $this->trackingFieldConf);
}
/**
* validates a model field to be a valid relational model
* @param $key
* @param $val
* @return bool
*/
protected function validate_notDry($key, $val): bool {
$valid = true;
if($colConf = $this->fieldConf[$key]){
if(isset($colConf['belongs-to-one'])){
if( (is_int($val) || ctype_digit($val)) && (int)$val > 0){
$valid = true;
}elseif( is_a($val, $colConf['belongs-to-one']) && !$val->dry() ){
$valid = true;
}else{
$valid = false;
$msg = 'Validation failed: "' . get_class($this) . '->' . $key . '" must be a valid instance of ' . $colConf['belongs-to-one'];
$this->throwValidationException($key, $msg);
}
}
}
return $valid;
}
/**
* log character activity create/update/delete events
* @param string $action
*/
protected function logActivity($action){
// check if activity logging is enabled for this object
if($this->enableActivityLogging){
// check for field changes
if(
mb_stripos(mb_strtolower($action), 'delete') !== false ||
!empty($this->fieldChanges)
){
$this->newLog($action)->setCharacter($this->updatedCharacterId)->setData($this->fieldChanges)->buffer();
}
}
}
/**
* validates all required columns of this class
* @return bool
* @throws \Exception\ValidationException
*/
public function isValid(): bool {
if($valid = parent::isValid()){
foreach($this->trackingFieldConf as $key => $colConf){
if($this->exists($key)){
$valid = $this->validateField($key, $this->$key);
if(!$valid){
break;
}
}else{
$valid = false;
$this->throwDbException('Missing table column "' . $this->getTable(). '.' . $key . '"');
break;
}
}
}
return $valid;
}
/**
* get log file data
* @return array
*/
public function getLogData(): array {
return [];
}
/**
* save connection
* @param CharacterModel $characterModel
* @return ConnectionModel|false
*/
public function save(CharacterModel $characterModel = null){
if($this->dry()){
$this->createdCharacterId = $characterModel;
}
$this->updatedCharacterId = $characterModel;
return parent::save();
}
}

View File

@@ -44,22 +44,46 @@ class ActivityLogModel extends BasicModel {
]
],
// map actions -----------------------------------------------------
'mapCreate' => [
'type' => Schema::DT_SMALLINT,
'nullable' => false,
'default' => 0,
'counter' => true
],
'mapUpdate' => [
'type' => Schema::DT_SMALLINT,
'nullable' => false,
'default' => 0,
'counter' => true
],
'mapDelete' => [
'type' => Schema::DT_SMALLINT,
'nullable' => false,
'default' => 0,
'counter' => true
],
// system actions -----------------------------------------------------
'systemCreate' => [
'type' => Schema::DT_SMALLINT,
'nullable' => false,
'default' => 0,
'counter' => true
],
'systemUpdate' => [
'type' => Schema::DT_SMALLINT,
'nullable' => false,
'default' => 0,
'counter' => true
],
'systemDelete' => [
'type' => Schema::DT_SMALLINT,
'nullable' => false,
'default' => 0,
'counter' => true
],
// connection actions -------------------------------------------------
@@ -68,16 +92,19 @@ class ActivityLogModel extends BasicModel {
'type' => Schema::DT_SMALLINT,
'nullable' => false,
'default' => 0,
'counter' => true
],
'connectionUpdate' => [
'type' => Schema::DT_SMALLINT,
'nullable' => false,
'default' => 0,
'counter' => true
],
'connectionDelete' => [
'type' => Schema::DT_SMALLINT,
'nullable' => false,
'default' => 0,
'counter' => true
],
// signature actions -------------------------------------------------
@@ -86,16 +113,19 @@ class ActivityLogModel extends BasicModel {
'type' => Schema::DT_SMALLINT,
'nullable' => false,
'default' => 0,
'counter' => true
],
'signatureUpdate' => [
'type' => Schema::DT_SMALLINT,
'nullable' => false,
'default' => 0,
'counter' => true
],
'signatureDelete' => [
'type' => Schema::DT_SMALLINT,
'nullable' => false,
'default' => 0,
'counter' => true
],
];
@@ -128,6 +158,20 @@ class ActivityLogModel extends BasicModel {
}
}
/**
* get all table columns that are used as "counter" columns
* @return array
*/
public function getCountableColumnNames(): array {
$fieldConf = $this->getFieldConfiguration();
$filterCounterColumns = function($key, $value){
return isset($value['counter']) ? $key : false;
};
return array_values(array_filter(array_map($filterCounterColumns, array_keys($fieldConf), $fieldConf)));
}
/**
* overwrites parent
* @param null $db

View File

@@ -9,6 +9,7 @@
namespace Model;
use DB\SQL\Schema;
use lib\Config;
class AllianceModel extends BasicModel {
@@ -60,8 +61,6 @@ class AllianceModel extends BasicModel {
public function getMaps(){
$maps = [];
$f3 = self::getF3();
$this->filter('mapAlliances',
['active = ?', 1],
['order' => 'created']
@@ -72,7 +71,7 @@ class AllianceModel extends BasicModel {
foreach($this->mapAlliances as $mapAlliance){
if(
$mapAlliance->mapId->isActive() &&
$mapCount < $f3->get('PATHFINDER.MAP.ALLIANCE.MAX_COUNT')
$mapCount < Config::getMapsDefaultConfig('alliance')['max_count']
){
$maps[] = $mapAlliance->mapId;
$mapCount++;

View File

@@ -9,9 +9,11 @@
namespace Model;
use DB\SQL\Schema;
use Exception;
use Controller;
use DB;
use Lib\Logging;
use Exception\ValidationException;
use Exception\DatabaseException;
abstract class BasicModel extends \DB\Cortex {
@@ -19,7 +21,7 @@ abstract class BasicModel extends \DB\Cortex {
* Hive key with DB object
* @var string
*/
protected $db = 'DB_PF';
protected $db = 'DB_PF';
/**
* caching time of field schema - seconds
@@ -27,26 +29,20 @@ abstract class BasicModel extends \DB\Cortex {
* -> leave this at a higher value
* @var int
*/
protected $ttl = 120;
protected $ttl = 60;
/**
* caching for relational data
* @var int
*/
protected $rel_ttl = 0;
protected $rel_ttl = 0;
/**
* ass static columns for this table
* -> can be overwritten in child models
* @var bool
*/
protected $addStaticFields = true;
/**
* field validation array
* @var array
*/
protected $validate = [];
protected $addStaticFields = true;
/**
* enables check for $fieldChanges on update/insert
@@ -54,7 +50,7 @@ abstract class BasicModel extends \DB\Cortex {
* in $fieldConf config
* @var bool
*/
protected $enableActivityLogging = true;
protected $enableActivityLogging = true;
/**
* enables change for "active" column
@@ -62,40 +58,53 @@ abstract class BasicModel extends \DB\Cortex {
* -> $this->active = false; will NOT work (prevent abuse)!
* @var bool
*/
private $allowActiveChange = false;
private $allowActiveChange = false;
/**
* getData() cache key prefix
* -> do not change, otherwise cached data is lost
* @var string
*/
private $dataCacheKeyPrefix = 'DATACACHE';
private $dataCacheKeyPrefix = 'DATACACHE';
/**
* enables data export for this table
* -> can be overwritten in child models
* @var bool
*/
public static $enableDataExport = false;
public static $enableDataExport = false;
/**
* enables data import for this table
* -> can be overwritten in child models
* @var bool
*/
public static $enableDataImport = false;
public static $enableDataImport = false;
/**
* changed fields (columns) on update/insert
* -> e.g. for character "activity logging"
* @var array
*/
protected $fieldChanges = [];
protected $fieldChanges = [];
/**
* default TTL for getData(); cache
* collection for validation errors
* @var array
*/
const DEFAULT_CACHE_TTL = 120;
protected $validationError = [];
/**
* default caching time of field schema - seconds
*/
const DEFAULT_TTL = 86400;
/**
* default TTL for getData(); cache - seconds
*/
const DEFAULT_CACHE_TTL = 120;
const ERROR_INVALID_MODEL_CLASS = 'Model class (%s) not found';
public function __construct($db = NULL, $table = NULL, $fluid = NULL, $ttl = 0){
@@ -135,8 +144,8 @@ abstract class BasicModel extends \DB\Cortex {
/**
* @param string $key
* @param mixed $val
* @return mixed|void
* @throws Exception\ValidationException
* @return mixed
* @throws ValidationException
*/
public function set($key, $val){
if(
@@ -167,15 +176,13 @@ abstract class BasicModel extends \DB\Cortex {
$val = trim($val);
}
$valid = $this->validateField($key, $val);
if(!$valid){
$this->throwValidationError($key);
if( !$this->validateField($key, $val) ){
$this->throwValidationException($key);
}else{
$this->checkFieldForActivityLogging($key, $val);
return parent::set($key, $val);
}
return parent::set($key, $val);
}
/**
@@ -206,15 +213,25 @@ abstract class BasicModel extends \DB\Cortex {
$val = (int)$val;
}
if(is_object($val)){
$val = $val->_id;
}
if( $fieldConf['type'] === self::DT_JSON){
$currentValue = $this->get($key);
}else{
$currentValue = $this->get($key, true);
}
if($currentValue !== $val){
// field has changed
in_array($key, $this->fieldChanges) ?: $this->fieldChanges[] = $key;
if( !array_key_exists($key, $this->fieldChanges) ){
$this->fieldChanges[$key] = [
'old' => $currentValue,
'new' => $val
];
}
}
}
}
@@ -239,11 +256,12 @@ abstract class BasicModel extends \DB\Cortex {
}
/**
* extent the fieldConf Array with static fields for each table
* get static fields for this model instance
* @return array
*/
private function addStaticFieldConfig(){
protected function getStaticFieldConf(): array {
$staticFieldConfig = [];
// add static fields to this mapper
// static tables (fixed data) do not require them...
if($this->addStaticFields){
$staticFieldConfig = [
@@ -258,61 +276,37 @@ abstract class BasicModel extends \DB\Cortex {
'index' => true
]
];
$this->fieldConf = array_merge($staticFieldConfig, $this->fieldConf);
}
return $staticFieldConfig;
}
/**
* extent the fieldConf Array with static fields for each table
*/
private function addStaticFieldConfig(){
$this->fieldConf = array_merge($this->getStaticFieldConf(), $this->fieldConf);
}
/**
* validates a table column based on validation settings
* @param $col
* @param string $key
* @param $val
* @return bool
*/
private function validateField($col, $val){
protected function validateField(string $key, $val): bool {
$valid = true;
if(array_key_exists($col, $this->validate)){
$fieldValidationOptions = $this->validate[$col];
foreach($fieldValidationOptions as $validateKey => $validateOption ){
if(is_array($fieldValidationOptions[$validateKey])){
$fieldSubValidationOptions = $fieldValidationOptions[$validateKey];
foreach($fieldSubValidationOptions as $validateSubKey => $validateSubOption ){
switch($validateKey){
case 'length':
switch($validateSubKey){
case 'min';
if(strlen($val) < $validateSubOption){
$valid = false;
}
break;
case 'max';
if(strlen($val) > $validateSubOption){
$valid = false;
}
break;
}
break;
}
}
if($fieldConf = $this->fieldConf[$key]){
if($method = $this->fieldConf[$key]['validate']){
if( !is_string($method)){
$method = 'validate_' . $key;
}
if(method_exists($this, $method)){
// validate $key (column) with this method...
$valid = $this->$method($key, $val);
}else{
switch($validateKey){
case 'regex':
$valid = (bool)preg_match($fieldValidationOptions[$validateKey], $val);
break;
}
}
// a validation rule failed
if(!$valid){
break;
}
self::getF3()->error(501, 'Method ' . get_class($this) . '->' . $method . '() is not implemented');
};
}
}
@@ -428,13 +422,22 @@ abstract class BasicModel extends \DB\Cortex {
}
/**
* Throws a validation error for a giben column
* @param $col
* @throws \Exception\ValidationException
* throw validation exception for a model property
* @param string $col
* @param string $msg
* @throws ValidationException
*/
protected function throwValidationError($col){
throw new Exception\ValidationException('Validation failed: "' . $col . '".', $col);
protected function throwValidationException(string $col, string $msg = ''){
$msg = empty($msg) ? 'Validation failed: "' . $col . '".' : $msg;
throw new ValidationException($msg, $col);
}
/**
* @param string $msg
* @throws DatabaseException
*/
protected function throwDbException(string $msg){
throw new DatabaseException($msg);
}
/**
@@ -458,11 +461,11 @@ abstract class BasicModel extends \DB\Cortex {
* get single dataSet by id
* @param $id
* @param int $ttl
* @param bool $isActive
* @return \DB\Cortex
*/
public function getById($id, $ttl = 3) {
return $this->getByForeignKey('id', (int)$id, ['limit' => 1], $ttl);
public function getById(int $id, int $ttl = 3, bool $isActive = true){
return $this->getByForeignKey('id', (int)$id, ['limit' => 1], $ttl, $isActive);
}
/**
@@ -492,10 +495,10 @@ abstract class BasicModel extends \DB\Cortex {
* @param $value
* @param array $options
* @param int $ttl
* @param bool $isActive
* @return \DB\Cortex
*/
public function getByForeignKey($key, $value, $options = [], $ttl = 60){
public function getByForeignKey($key, $value, $options = [], $ttl = 0, $isActive = true){
$querySet = [];
$query = [];
if($this->exists($key)){
@@ -504,7 +507,7 @@ abstract class BasicModel extends \DB\Cortex {
}
// check active column
if($this->exists('active')){
if($isActive && $this->exists('active')){
$query[] = "active = :active";
$querySet[':active'] = 1;
}
@@ -594,7 +597,7 @@ abstract class BasicModel extends \DB\Cortex {
* function should be overwritten in parent classes
* @return bool
*/
public function isValid(){
public function isValid(): bool {
return true;
}
@@ -678,7 +681,7 @@ abstract class BasicModel extends \DB\Cortex {
$status = $this->importStaticData($tableData);
$this->getF3()->status(202);
}else{
$this->getF3()->error(502, 'File could not be read');
$this->getF3()->error(500, 'File could not be read');
}
}else{
$this->getF3()->error(404, 'File not found: ' . $filePath);
@@ -727,15 +730,54 @@ abstract class BasicModel extends \DB\Cortex {
}
/**
* buffer a new activity (action) logging
* -> increment buffered counter
* -> log character activity create/update/delete events
* @param int $characterId
* @param int $mapId
* get "default" logging object for this kind of model
* -> can be overwritten
* @param string $action
* @return Logging\LogInterface
*/
protected function bufferActivity($characterId, $mapId, $action){
Controller\LogController::instance()->bufferActivity($characterId, $mapId, $action);
protected function newLog($action = ''): Logging\LogInterface{
return new Logging\DefaultLog($action);
}
/**
* get formatter callback function for parsed logs
* @return null
*/
protected function getLogFormatter(){
return null;
}
/**
* add new validation error
* @param ValidationException $e
*/
protected function setValidationError(ValidationException $e){
$this->validationError[] = $e->getError();
}
/**
* get all validation errors
* @return array
*/
public function getErrors(): array {
return $this->validationError;
}
public function save(){
try{
return parent::save();
}catch(ValidationException $e){
$this->setValidationError($e);
}catch(DatabaseException $e){
self::getF3()->error($e->getCode(), $e->getMessage(), $e->getTrace());
}
}
/**
* @return string
*/
public function __toString(){
return $this->getTable();
}
/**
@@ -755,14 +797,14 @@ abstract class BasicModel extends \DB\Cortex {
* @return BasicModel
* @throws \Exception
*/
public static function getNew($model, $ttl = 86400){
public static function getNew($model, $ttl = self::DEFAULT_TTL){
$class = null;
$model = '\\' . __NAMESPACE__ . '\\' . $model;
if(class_exists($model)){
$class = new $model( null, null, null, $ttl );
}else{
throw new \Exception('No model class found');
throw new \Exception(sprintf(self::ERROR_INVALID_MODEL_CLASS, $model));
}
return $class;

View File

@@ -57,6 +57,11 @@ class CharacterLogModel extends BasicModel {
'type' => Schema::DT_BIGINT,
'index' => true
],
'shipMass' => [
'type' => Schema::DT_FLOAT,
'nullable' => false,
'default' => 0
],
'shipName' => [
'type' => Schema::DT_VARCHAR128,
'nullable' => false,
@@ -92,11 +97,13 @@ class CharacterLogModel extends BasicModel {
$this->shipTypeName = $logData['ship']['typeName'];
$this->shipId = (int)$logData['ship']['id'];
$this->shipName = $logData['ship']['name'];
$this->shipMass = (float)$logData['ship']['mass'];
}else{
$this->shipTypeId = null;
$this->shipTypeName = '';
$this->shipId = null;
$this->shipName = '';
$this->shipMass = 0;
}
if( isset($logData['station']) ){
@@ -125,6 +132,7 @@ class CharacterLogModel extends BasicModel {
$logData->ship->typeName = $this->shipTypeName;
$logData->ship->id = $this->shipId;
$logData->ship->name = $this->shipName;
$logData->ship->mass = $this->shipMass;
$logData->station = (object) [];
$logData->station->id = (int)$this->stationId;
@@ -191,23 +199,26 @@ class CharacterLogModel extends BasicModel {
* update session data for active character
* @param int $systemId
*/
protected function updateCharacterSessionLocation($systemId){
protected function updateCharacterSessionLocation(int $systemId){
$controller = new Controller();
if(
!empty($sessionCharacter = $controller->getSessionCharacterData()) &&
$sessionCharacter['ID'] === $this->get('characterId', true)
){
$prevSystemId = (int)$sessionCharacter['PREV_SYSTEM_ID'];
if($prevSystemId === 0){
$systemChanged = false;
if((int)$sessionCharacter['PREV_SYSTEM_ID'] === 0){
$sessionCharacter['PREV_SYSTEM_ID'] = (int)$systemId;
}else{
$systemChanged = true;
}elseif((int)$sessionCharacter['PREV_SYSTEM_ID'] !== $this->systemId){
$sessionCharacter['PREV_SYSTEM_ID'] = $this->systemId;
$systemChanged = true;
}
$sessionCharacters = CharacterModel::mergeSessionCharacterData([$sessionCharacter]);
$this->getF3()->set(User::SESSION_KEY_CHARACTERS, $sessionCharacters);
if($systemChanged){
$sessionCharacters = CharacterModel::mergeSessionCharacterData([$sessionCharacter]);
$this->getF3()->set(User::SESSION_KEY_CHARACTERS, $sessionCharacters);
}
}
}

View File

@@ -12,6 +12,8 @@ use Controller\Ccp\Sso as Sso;
use Controller\Api\User as User;
use DB\SQL\Schema;
use Lib\Util;
use lib\Config;
use Model\Universe;
class CharacterModel extends BasicModel {
@@ -279,7 +281,7 @@ class CharacterModel extends BasicModel {
if($minutes){
$seconds = $minutes * 60;
$timezone = new \DateTimeZone( self::getF3()->get('TZ') );
$timezone = self::getF3()->get('getTimeZone')();
$kickedUntil = new \DateTime('now', $timezone);
// add cookie expire time
@@ -306,7 +308,7 @@ class CharacterModel extends BasicModel {
$banned = null;
if($status){
$timezone = new \DateTimeZone( self::getF3()->get('TZ') );
$timezone = self::getF3()->get('getTimeZone')();
$bannedSince = new \DateTime('now', $timezone);
$banned = $bannedSince->format('Y-m-d H:i:s');
}
@@ -479,7 +481,7 @@ class CharacterModel extends BasicModel {
!empty($this->crestAccessToken) &&
!empty($this->crestAccessTokenUpdated)
){
$timezone = new \DateTimeZone( self::getF3()->get('TZ') );
$timezone = self::getF3()->get('getTimeZone')();
$tokenTime = \DateTime::createFromFormat(
'Y-m-d H:i:s',
$this->crestAccessTokenUpdated,
@@ -547,8 +549,8 @@ class CharacterModel extends BasicModel {
if(is_null($this->banned)){
if( !$this->isKicked() ){
$f3 = self::getF3();
$whitelistCorporations = array_filter( array_map('trim', (array)$f3->get('PATHFINDER.LOGIN.CORPORATION') ) );
$whitelistAlliance = array_filter( array_map('trim', (array)$f3->get('PATHFINDER.LOGIN.ALLIANCE') ) );
$whitelistCorporations = array_filter( array_map('trim', (array)Config::getPathfinderData('login.corporation') ) );
$whitelistAlliance = array_filter( array_map('trim', (array)Config::getPathfinderData('login.alliance') ) );
if(
empty($whitelistCorporations) &&
@@ -676,9 +678,7 @@ class CharacterModel extends BasicModel {
if( !empty($locationData['system']['id']) ){
// character is currently in-game
// IDs for "systemId", "stationId and "shipTypeId" that require more data
$lookupIds = [];
// get current $characterLog or get new ---------------------------------------------------
if( !($characterLog = $this->getLog()) ){
// create new log
$characterLog = $this->rel('characterLog');
@@ -687,12 +687,17 @@ class CharacterModel extends BasicModel {
// get current log data and modify on change
$logData = json_decode(json_encode( $characterLog->getData()), true);
// check system and station data for changes ----------------------------------------------
// IDs for "systemId", "stationId" that require more data
$lookupUniverseIds = [];
if(
empty($logData['system']['name']) ||
$logData['system']['id'] !== $locationData['system']['id']
){
// system changed -> request "system name" for current system
$lookupIds[] = $locationData['system']['id'];
$lookupUniverseIds[] = $locationData['system']['id'];
}
if( !empty($locationData['station']['id']) ){
@@ -701,7 +706,7 @@ class CharacterModel extends BasicModel {
$logData['station']['id'] !== $locationData['station']['id']
){
// station changed -> request "station name" for current station
$lookupIds[] = $locationData['station']['id'];
$lookupUniverseIds[] = $locationData['station']['id'];
}
}else{
unset($logData['station']);
@@ -709,30 +714,10 @@ class CharacterModel extends BasicModel {
$logData = array_replace_recursive($logData, $locationData);
// get current ship data
$shipData = self::getF3()->ccpClient->getCharacterShipData($this->_id, $accessToken, $additionalOptions);
if( !empty($shipData['ship']['typeId']) ){
if(
empty($logData['ship']['typeName']) ||
$logData['ship']['typeId'] !== $shipData['ship']['typeId']
){
// ship changed -> request "station name" for current station
$lookupIds[] = $shipData['ship']['typeId'];
}
// "shipName"/"shipId" could have changed...
$logData = array_replace_recursive($logData, $shipData);
}else{
// ship data should never be empty -> keep current one
//unset($logData['ship']);
$invalidResponse = true;
}
if( !empty($lookupIds) ){
// get "more" data for systemId and/or stationId -----------------------------------------
if( !empty($lookupUniverseIds) ){
// get "more" information for some Ids (e.g. name)
$universeData = self::getF3()->ccpClient->getUniverseNamesData($lookupIds, $additionalOptions);
$universeData = self::getF3()->ccpClient->getUniverseNamesData($lookupUniverseIds, $additionalOptions);
if( !empty($universeData) ){
$logData = array_replace_recursive($logData, $universeData);
}else{
@@ -741,6 +726,48 @@ class CharacterModel extends BasicModel {
}
}
// check ship data for changes ------------------------------------------------------------
if( !$deleteLog ){
$shipData = self::getF3()->ccpClient->getCharacterShipData($this->_id, $accessToken, $additionalOptions);
// IDs for "systemId", "stationId" that require more data
$lookupShipTypeId = 0;
if( !empty($shipData['ship']['typeId']) ){
if(
empty($logData['ship']['typeName']) ||
$logData['ship']['typeId'] !== $shipData['ship']['typeId']
){
// ship changed -> request "station name" for current station
$lookupShipTypeId = $shipData['ship']['typeId'];
}
// "shipName"/"shipId" could have changed...
$logData = array_replace_recursive($logData, $shipData);
}else{
// ship data should never be empty -> keep current one
//unset($logData['ship']);
$invalidResponse = true;
}
// get "more" data for shipTypeId ----------------------------------------------------
if($lookupShipTypeId > 0){
/**
* @var $typeModel Universe\TypeModel
*/
$typeModel = Universe\BasicUniverseModel::getNew('TypeModel');
$typeModel->loadById($lookupShipTypeId, $additionalOptions);
if(!$typeModel->dry()){
$shipData['ship'] = (array)$typeModel->getShipData();
$logData = array_replace_recursive($logData, $shipData);
}else{
// this is important! ship data is a MUST HAVE!
$deleteLog = true;
}
}
}
if( !$deleteLog ){
// mark log as "updated" even if no changes were made
if($additionalOptions['markUpdated'] === true){
@@ -921,7 +948,7 @@ class CharacterModel extends BasicModel {
$mapCountPrivate = 0;
foreach($this->characterMaps as $characterMap){
if(
$mapCountPrivate < self::getF3()->get('PATHFINDER.MAP.PRIVATE.MAX_COUNT') &&
$mapCountPrivate < Config::getMapsDefaultConfig('private')['max_count'] &&
$characterMap->mapId->isActive()
){
$maps[] = $characterMap->mapId;
@@ -934,11 +961,19 @@ class CharacterModel extends BasicModel {
}
/**
* character logout
* -> clear authentication data
* delete current location
*/
public function logout(){
if( is_object($this->characterAuthentications) ){
protected function deleteLog(){
if($characterLog = $this->getLog()){
$characterLog->erase();
}
}
/**
* delete authentications data
*/
protected function deleteAuthentications(){
if(is_object($this->characterAuthentications)){
foreach($this->characterAuthentications as $characterAuthentication){
/**
* @var $characterAuthentication CharacterAuthenticationModel
@@ -947,6 +982,40 @@ class CharacterModel extends BasicModel {
}
}
}
/**
* character logout
* @param bool $deleteLog
* @param bool $deleteSession
* @param bool $deleteCookie
*/
public function logout(bool $deleteSession = true, bool $deleteLog = true, bool $deleteCookie = false){
// delete current session data --------------------------------------------------------------------------------
if($deleteSession){
$sessionCharacterData = (array)$this->getF3()->get(User::SESSION_KEY_CHARACTERS);
$sessionCharacterData = array_filter($sessionCharacterData, function($data){
return ($data['ID'] != $this->_id);
});
if(empty($sessionCharacterData)){
// no active characters logged in -> log user out
$this->getF3()->clear(User::SESSION_KEY_USER);
$this->getF3()->clear(User::SESSION_KEY_CHARACTERS);
}else{
// update remaining active characters
$this->getF3()->set(User::SESSION_KEY_CHARACTERS, $sessionCharacterData);
}
}
// delete current location data -------------------------------------------------------------------------------
if($deleteLog){
$this->deleteLog();
}
// delete auth cookie data ------------------------------------------------------------------------------------
if($deleteCookie ){
$this->deleteAuthentications();
}
}
/**
* merges two multidimensional characterSession arrays by checking characterID

View File

@@ -9,10 +9,10 @@
namespace Model;
use DB\SQL\Schema;
use Controller;
use Controller\Api\Route;
use Lib\Logging;
class ConnectionModel extends BasicModel{
class ConnectionModel extends AbstractMapTrackingModel {
protected $table = 'connection';
@@ -79,10 +79,16 @@ class ConnectionModel extends BasicModel{
/**
* set an array with all data for a system
* @param $systemData
* @param array $data
*/
public function setData($systemData){
foreach((array)$systemData as $key => $value){
public function setData($data){
unset($data['id']);
unset($data['created']);
unset($data['updated']);
unset($data['createdCharacterId']);
unset($data['updatedCharacterId']);
foreach((array)$data as $key => $value){
if( !is_array($value) ){
if( $this->exists($key) ){
$this->$key = $value;
@@ -100,7 +106,6 @@ class ConnectionModel extends BasicModel{
* @return \stdClass
*/
public function getData($addSignatureData = false){
$connectionData = (object) [];
$connectionData->id = $this->id;
$connectionData->source = $this->source->id;
@@ -189,21 +194,21 @@ class ConnectionModel extends BasicModel{
* check whether this model is valid or not
* @return bool
*/
public function isValid(){
$isValid = true;
// check if source/target system are not equal
// check if source/target belong to same map
if(
is_object($this->source) &&
is_object($this->target) &&
$this->get('source', true) === $this->get('target', true) ||
$this->source->get('mapId', true) !== $this->target->get('mapId', true)
){
$isValid = false;
public function isValid(): bool {
if($valid = parent::isValid()){
// check if source/target system are not equal
// check if source/target belong to same map
if(
is_object($this->source) &&
is_object($this->target) &&
$this->get('source', true) === $this->get('target', true) ||
$this->source->get('mapId', true) !== $this->target->get('mapId', true)
){
$valid = false;
}
}
return $isValid;
return $valid;
}
/**
@@ -218,7 +223,6 @@ class ConnectionModel extends BasicModel{
// check for "default" connection type and add them if missing
// -> get() with "true" returns RAW data! important for JSON table column check!
$types = (array)json_decode( $this->get('type', true) );
if(
!$this->scope ||
empty($types)
@@ -226,7 +230,7 @@ class ConnectionModel extends BasicModel{
$this->setDefaultTypeData();
}
return parent::beforeInsertEvent($self, $pkeys);
return $this->isValid() ? parent::beforeInsertEvent($self, $pkeys) : false;
}
/**
@@ -263,35 +267,18 @@ class ConnectionModel extends BasicModel{
}
/**
* log character activity create/update/delete events
* @param string $action
* @return Logging\LogInterface
*/
protected function logActivity($action){
if(
$this->enableActivityLogging &&
(
$action === 'connectionDelete' ||
!empty($this->fieldChanges)
) &&
$this->get('mapId')->isActivityLogEnabled()
){
// TODO implement "dependency injection" for active character object...
$controller = new Controller\Controller();
$currentActiveCharacter = $controller->getCharacter();
$characterId = is_null($currentActiveCharacter) ? 0 : $currentActiveCharacter->_id;
$mapId = $this->get('mapId', true);
parent::bufferActivity($characterId, $mapId, $action);
}
public function newLog($action = ''): Logging\LogInterface{
return $this->getMap()->newLog($action)->setTempData($this->getLogObjectData());
}
/**
* save connection and check if obj is valid
* @return ConnectionModel|false
* @return MapModel
*/
public function save(){
return ( $this->isValid() ) ? parent::save() : false;
public function getMap(): MapModel{
return $this->get('mapId');
}
/**
@@ -307,6 +294,17 @@ class ConnectionModel extends BasicModel{
}
}
/**
* get object relevant data for model log
* @return array
*/
public function getLogObjectData() : array{
return [
'objId' => $this->_id,
'objName' => $this->scope
];
}
/**
* see parent
*/

View File

@@ -9,6 +9,7 @@
namespace Model;
use DB\SQL\Schema;
use lib\Config;
class CorporationModel extends BasicModel {
@@ -131,8 +132,6 @@ class CorporationModel extends BasicModel {
public function getMaps(){
$maps = [];
$f3 = self::getF3();
$this->filter('mapCorporations',
['active = ?', 1],
['order' => 'created']
@@ -143,7 +142,7 @@ class CorporationModel extends BasicModel {
foreach($this->mapCorporations as $mapCorporation){
if(
$mapCorporation->mapId->isActive() &&
$mapCount < $f3->get('PATHFINDER.MAP.CORPORATION.MAX_COUNT')
$mapCount < Config::getMapsDefaultConfig('corporation')['max_count']
){
$maps[] = $mapCorporation->mapId;
$mapCount++;

View File

@@ -0,0 +1,19 @@
<?php
/**
* Created by PhpStorm.
* User: exodu
* Date: 19.08.2017
* Time: 04:10
*/
namespace Model;
interface LogModelInterface {
public function getLogObjectData(): array;
public function getMap(): MapModel;
public function getLogData(): array;
}

View File

@@ -10,20 +10,21 @@ namespace Model;
use Controller\Api\System;
use DB\SQL\Schema;
use data\file\FileHandler;
use lib\Config;
use Lib\Logging;
use Exception\PathfinderException;
class MapModel extends BasicModel {
class MapModel extends AbstractMapTrackingModel {
protected $table = 'map';
/**
* cache key prefix for getCharactersData();
*/
const DATA_CACHE_KEY_CHARACTER = 'CHARACTERS';
const DATA_CACHE_KEY_CHARACTER = 'CHARACTERS';
/**
* default TTL for getData(); cache
*/
const DEFAULT_CACHE_TTL = 60;
const ERROR_SLACK_CHANNEL = 'Invalid #Slack channel column [%s]';
protected $fieldConf = [
'active' => [
@@ -31,7 +32,7 @@ class MapModel extends BasicModel {
'nullable' => false,
'default' => 1,
'index' => true,
'after' => 'updated'
'activity-log' => true
],
'scopeId' => [
'type' => Schema::DT_INT,
@@ -42,7 +43,9 @@ class MapModel extends BasicModel {
'table' => 'map_scope',
'on-delete' => 'CASCADE'
]
]
],
'validate' => 'validate_notDry',
'activity-log' => true
],
'typeId' => [
'type' => Schema::DT_INT,
@@ -53,32 +56,82 @@ class MapModel extends BasicModel {
'table' => 'map_type',
'on-delete' => 'CASCADE'
]
]
],
'validate' => 'validate_notDry',
'activity-log' => true
],
'name' => [
'type' => Schema::DT_VARCHAR128,
'nullable' => false,
'default' => ''
'default' => '',
'activity-log' => true,
'validate' => true
],
'icon' => [
'type' => Schema::DT_VARCHAR128,
'nullable' => false,
'default' => ''
'default' => '',
'activity-log' => true
],
'deleteExpiredConnections' => [
'type' => Schema::DT_BOOL,
'nullable' => false,
'default' => 1
'default' => 1,
'activity-log' => true
],
'deleteEolConnections' => [
'type' => Schema::DT_BOOL,
'nullable' => false,
'default' => 1
'default' => 1,
'activity-log' => true
],
'persistentAliases' => [
'type' => Schema::DT_BOOL,
'nullable' => false,
'default' => 1
'default' => 1,
'activity-log' => true
],
'logActivity' => [
'type' => Schema::DT_BOOL,
'nullable' => false,
'default' => 1,
'activity-log' => true
],
'logHistory' => [
'type' => Schema::DT_BOOL,
'nullable' => false,
'default' => 0,
'activity-log' => true
],
'slackWebHookURL' => [
'type' => Schema::DT_VARCHAR128,
'nullable' => false,
'default' => '',
'validate' => true
],
'slackUsername' => [
'type' => Schema::DT_VARCHAR128,
'nullable' => false,
'default' => '',
'activity-log' => true
],
'slackIcon' => [
'type' => Schema::DT_VARCHAR128,
'nullable' => false,
'default' => '',
'activity-log' => true
],
'slackChannelHistory' => [
'type' => Schema::DT_VARCHAR128,
'nullable' => false,
'default' => '',
'activity-log' => true
],
'slackChannelRally' => [
'type' => Schema::DT_VARCHAR128,
'nullable' => false,
'default' => '',
'activity-log' => true
],
'systems' => [
'has-many' => ['Model\SystemModel', 'mapId']
@@ -97,37 +150,18 @@ class MapModel extends BasicModel {
]
];
protected $validate = [
'name' => [
'length' => [
'min' => 3
]
],
'icon' => [
'length' => [
'min' => 3
]
],
'scopeId' => [
'regex' => '/^[1-9]+$/'
],
'typeId' => [
'regex' => '/^[1-9]+$/'
]
];
/**
* set map data by an associative array
* @param $data
* @param array $data
*/
public function setData($data){
unset($data['id']);
unset($data['created']);
unset($data['updated']);
unset($data['createdCharacterId']);
unset($data['updatedCharacterId']);
foreach((array)$data as $key => $value){
if($key == 'created'){
continue;
}
if(!is_array($value)){
if($this->exists($key)){
$this->$key = $value;
@@ -155,35 +189,62 @@ class MapModel extends BasicModel {
if(is_null($mapDataAll)){
// no cached map data found
$mapData = (object) [];
$mapData->id = $this->id;
$mapData->name = $this->name;
$mapData->icon = $this->icon;
$mapData->deleteExpiredConnections = $this->deleteExpiredConnections;
$mapData->deleteEolConnections = $this->deleteEolConnections;
$mapData->persistentAliases = $this->persistentAliases;
$mapData->created = strtotime($this->created);
$mapData->updated = strtotime($this->updated);
$mapData = (object) [];
$mapData->id = $this->id;
$mapData->name = $this->name;
$mapData->icon = $this->icon;
$mapData->deleteExpiredConnections = $this->deleteExpiredConnections;
$mapData->deleteEolConnections = $this->deleteEolConnections;
$mapData->persistentAliases = $this->persistentAliases;
// map scope
$mapData->scope = (object) [];
$mapData->scope->id = $this->scopeId->id;
$mapData->scope->name = $this->scopeId->name;
$mapData->scope->label = $this->scopeId->label;
$mapData->scope = (object) [];
$mapData->scope->id = $this->scopeId->id;
$mapData->scope->name = $this->scopeId->name;
$mapData->scope->label = $this->scopeId->label;
// map type
$mapData->type = (object) [];
$mapData->type->id = $this->typeId->id;
$mapData->type->name = $this->typeId->name;
$mapData->type->classTab = $this->typeId->classTab;
$mapData->type = (object) [];
$mapData->type->id = $this->typeId->id;
$mapData->type->name = $this->typeId->name;
$mapData->type->classTab = $this->typeId->classTab;
// map logging
$mapData->logging = (object) [];
$mapData->logging->activity = $this->isActivityLogEnabled();
$mapData->logging->history = $this->isHistoryLogEnabled();
// map Slack logging
$mapData->logging->slackHistory = $this->isSlackChannelEnabled('slackChannelHistory');
$mapData->logging->slackRally = $this->isSlackChannelEnabled('slackChannelRally');
$mapData->logging->slackWebHookURL = $this->slackWebHookURL;
$mapData->logging->slackUsername = $this->slackUsername;
$mapData->logging->slackIcon = $this->slackIcon;
$mapData->logging->slackChannelHistory = $this->slackChannelHistory;
$mapData->logging->slackChannelRally = $this->slackChannelRally;
// map mail logging
$mapData->logging->mailRally = $this->isMailSendEnabled('RALLY_SET');
// map access
$mapData->access = (object) [];
$mapData->access->character = [];
$mapData->access->corporation = [];
$mapData->access->alliance = [];
$mapData->access = (object) [];
$mapData->access->character = [];
$mapData->access->corporation = [];
$mapData->access->alliance = [];
// get access object data -------------------------------------
$mapData->created = (object) [];
$mapData->created->created = strtotime($this->created);
if(is_object($this->createdCharacterId)){
$mapData->created->character = $this->createdCharacterId->getData();
}
$mapData->updated = (object) [];
$mapData->updated->updated = strtotime($this->updated);
if(is_object($this->updatedCharacterId)){
$mapData->updated->character = $this->updatedCharacterId->getData();
}
// get access object data ---------------------------------------------------------------------------------
if($this->isPrivate()){
$characters = $this->getCharacters();
$characterData = [];
@@ -209,14 +270,14 @@ class MapModel extends BasicModel {
$mapData->access->alliance = $allianceData;
}
// merge all data ---------------------------------------------
// merge all data -----------------------------------------------------------------------------------------
$mapDataAll = (object) [];
$mapDataAll->mapData = $mapData;
// map system data --------------------------------------------
// map system data ----------------------------------------------------------------------------------------
$mapDataAll->systems = $this->getSystemData();
// map connection data ----------------------------------------
// map connection data ------------------------------------------------------------------------------------
$mapDataAll->connections = $this->getConnectionData();
// max caching time for a map
@@ -228,6 +289,70 @@ class MapModel extends BasicModel {
return $mapDataAll;
}
/**
* validate name column
* @param string $key
* @param string $val
* @return bool
*/
protected function validate_name(string $key, string $val): bool {
$valid = true;
if(mb_strlen($val) < 3){
$valid = false;
$this->throwValidationException($key);
}
return $valid;
}
/**
* validate Slack WebHook URL
* @param string $key
* @param string $val
* @return bool
*/
protected function validate_slackWebHookURL(string $key, string $val): bool {
$valid = true;
if( !empty($val) ){
if(
!\Audit::instance()->url($val) ||
parse_url($val, PHP_URL_HOST) !== 'hooks.slack.com'
){
$valid = false;
$this->throwValidationException($key);
}
}
return $valid;
}
/**
* @param $channel
* @return string
*/
protected function set_slackChannelHistory($channel){
return $this->formatSlackChannelName($channel);
}
/**
* @param $channel
* @return string
*/
protected function set_slackChannelRally($channel){
return $this->formatSlackChannelName($channel);
}
/**
* convert a Slack channel name into correct format
* @param $channel
* @return string
*/
private function formatSlackChannelName($channel){
$channel = strtolower(str_replace(' ','', trim(trim((string)$channel), '#@')));
if($channel){
$channel = '#' . $channel;
}
return $channel;
}
/**
* Event "Hook" function
* @param self $self
@@ -235,6 +360,7 @@ class MapModel extends BasicModel {
*/
public function afterInsertEvent($self, $pkeys){
$self->clearCacheData();
$self->logActivity('mapCreate');
}
/**
@@ -244,6 +370,9 @@ class MapModel extends BasicModel {
*/
public function afterUpdateEvent($self, $pkeys){
$self->clearCacheData();
$activity = ($self->isActive()) ? 'mapUpdate' : 'mapDelete';
$self->logActivity($activity);
}
/**
@@ -253,6 +382,8 @@ class MapModel extends BasicModel {
*/
public function afterEraseEvent($self, $pkeys){
$self->clearCacheData();
$self->logActivity('mapDelete');
$self->deleteLogFile();
}
/**
@@ -701,49 +832,185 @@ class MapModel extends BasicModel {
}
/**
* delete this map and all dependencies
* @param CharacterModel $characterModel
* @param null $callback
* @param string $action
* @return Logging\LogInterface
*/
public function delete(CharacterModel $characterModel, $callback = null){
public function newLog($action = ''): Logging\LogInterface{
$logChannelData = $this->getLogChannelData();
$logObjectData = $this->getLogObjectData();
$log = (new Logging\MapLog($action, $logChannelData))->setTempData($logObjectData);
if( !$this->dry() ){
// check if character has access
if($this->hasAccess($characterModel)){
// all map related tables will be deleted on cascade
if(
$this->erase() &&
is_callable($callback)
){
$callback($this->_id);
}
// update map history *.log files -----------------------------------------------------------------------------
if($this->isHistoryLogEnabled()){
// check socket config
if(Config::validSocketConnect()){
$log->addHandler('zmq', 'json', $this->getSocketConfig());
}else{
// update log file local (slow)
$log->addHandler('stream', 'json', $this->getStreamConfig());
}
}
// send map history to Slack channel --------------------------------------------------------------------------
$slackChannelKey = 'slackChannelHistory';
if($this->isSlackChannelEnabled($slackChannelKey)){
$log->addHandler('slackMap', null, $this->getSlackWebHookConfig($slackChannelKey));
$log->addHandlerGroup('slackMap');
}
// update map activity ----------------------------------------------------------------------------------------
$log->logActivity($this->isActivityLogEnabled());
return $log;
}
/**
* @return MapModel
*/
public function getMap(): MapModel{
return $this;
}
/**
* get object relevant data for model log channel
* @return array
*/
public function getLogChannelData() : array{
return [
'channelId' => $this->_id,
'channelName' => $this->name
];
}
/**
* get object relevant data for model log object
* @return array
*/
public function getLogObjectData() : array{
return [
'objId' => $this->_id,
'objName' => $this->name
];
}
protected function getLogFormatter(){
return function(&$rowDataObj){
unset($rowDataObj['extra']);
};
}
/**
* check if "activity logging" is enabled for this map type
* @return bool
*/
public function isActivityLogEnabled(){
$f3 = self::getF3();
$activityLogEnabled = false;
public function isActivityLogEnabled(): bool {
return $this->logActivity && (bool) Config::getMapsDefaultConfig($this->typeId->name)['log_activity_enabled'];
}
if( $this->isAlliance() ){
if( $f3->get('PATHFINDER.MAP.ALLIANCE.ACTIVITY_LOGGING') ){
$activityLogEnabled = true;
/**
* check if "history logging" is enabled for this map type
* @return bool
*/
public function isHistoryLogEnabled(): bool {
return $this->logHistory && (bool) Config::getMapsDefaultConfig($this->typeId->name)['log_history_enabled'];
}
/**
* check if "Slack WebHook" is enabled for this map type
* @param string $channel
* @return bool
* @throws PathfinderException
*/
public function isSlackChannelEnabled(string $channel): bool {
$enabled = false;
// check global Slack status
if((bool)Config::getPathfinderData('slack.status')){
// check global map default config for this channel
switch($channel){
case 'slackChannelHistory': $defaultMapConfigKey = 'send_history_slack_enabled'; break;
case 'slackChannelRally': $defaultMapConfigKey = 'send_rally_slack_enabled'; break;
default: throw new PathfinderException(sprintf(self::ERROR_SLACK_CHANNEL, $channel));
}
}elseif( $this->isCorporation() ){
if( $f3->get('PATHFINDER.MAP.CORPORATION.ACTIVITY_LOGGING') ){
$activityLogEnabled = true;
}
}elseif( $this->isPrivate() ){
if( $f3->get('PATHFINDER.MAP.PRIVATE.ACTIVITY_LOGGING') ){
$activityLogEnabled = true;
if((bool) Config::getMapsDefaultConfig($this->typeId->name)[$defaultMapConfigKey]){
$config = $this->getSlackWebHookConfig($channel);
if($config->slackWebHookURL && $config->slackChannel){
$enabled = true;
}
}
}
return $activityLogEnabled;
return $enabled;
}
/**
* check if "E-Mail" Log is enabled for this map
* @param string $type
* @return bool
*/
public function isMailSendEnabled(string $type): bool{
$enabled = false;
if((bool) Config::getMapsDefaultConfig($this->typeId->name)['send_rally_mail_enabled']){
$enabled = Config::isValidSMTPConfig($this->getSMTPConfig($type));
}
return $enabled;
}
/**
* get config for stream logging
* @param bool $abs absolute path
* @return \stdClass
*/
public function getStreamConfig(bool $abs = false): \stdClass{
$config = (object) [];
$config->stream = '';
if( $this->getF3()->exists('PATHFINDER.HISTORY.LOG', $dir) ){
$config->stream .= $abs ? $this->getF3()->get('ROOT') . '/' : './';
$config->stream .= $dir . 'map/map_' . $this->_id . '.log';
$config->stream = $this->getF3()->fixslashes($config->stream);
}
return $config;
}
/**
* get config for Socket connection (e.g. where to send log data)
* @return \stdClass
*/
public function getSocketConfig(): \stdClass{
$config = (object) [];
$config->uri = Config::getSocketUri();
$config->streamConf = $this->getStreamConfig(true);
return $config;
}
/**
* get Config for Slack WebHook cURL calls
* -> https://api.slack.com/incoming-webhooks
* @param string $channel
* @return \stdClass
*/
public function getSlackWebHookConfig(string $channel = ''): \stdClass{
$config = (object) [];
$config->slackWebHookURL = $this->slackWebHookURL;
$config->slackUsername = $this->slackUsername;
$config->slackIcon = $this->slackIcon;
if($channel && $this->exists($channel)){
$config->slackChannel = $this->$channel;
}
return $config;
}
/**
* get Config for SMTP connection and recipient address
* @param string $type
* @param bool $addJson
* @return \stdClass
*/
public function getSMTPConfig(string $type, bool $addJson = true): \stdClass{
$config = Config::getSMTPConfig();
$config->to = Config::getNotificationMail($type);
$config->addJson = $addJson;
return $config;
}
/**
@@ -782,22 +1049,31 @@ class MapModel extends BasicModel {
return $scope;
}
/**
* get log file data
* @param int $offset
* @param int $limit
* @return array
*/
public function getLogData(int $offset = FileHandler::LOG_FILE_OFFSET, int $limit = FileHandler::LOG_FILE_LIMIT): array {
$streamConf = $this->getStreamConfig();
return FileHandler::readLogFile($streamConf->stream, $offset, $limit, $this->getLogFormatter());
}
/**
* save a system to this map
* @param SystemModel $system
* @param CharacterModel $character
* @param int $posX
* @param int $posY
* @param null|CharacterModel $character
* @return mixed
* @return false|ConnectionModel
*/
public function saveSystem( SystemModel $system, $posX = 10, $posY = 0, $character = null){
public function saveSystem( SystemModel $system, CharacterModel $character, $posX = 10, $posY = 0){
$system->setActive(true);
$system->mapId = $this->id;
$system->posX = $posX;
$system->posY = $posY;
$system->createdCharacterId = $character;
$system->updatedCharacterId = $character;
return $system->save();
return $system->save($character);
}
/**
@@ -839,11 +1115,26 @@ class MapModel extends BasicModel {
* save new connection
* -> connection scope/type is automatically added
* @param ConnectionModel $connection
* @param CharacterModel $character
* @return false|ConnectionModel
*/
public function saveConnection(ConnectionModel $connection){
public function saveConnection(ConnectionModel $connection, CharacterModel $character){
$connection->mapId = $this;
return $connection->save();
return $connection->save($character);
}
/**
* delete existing log file
*/
protected function deleteLogFile(){
$config = $this->getStreamConfig();
if(is_file($config->stream)){
// try to set write access
if(!is_writable($config->stream)){
chmod($config->stream, 0666);
}
@unlink($config->stream);
}
}
/**
@@ -909,12 +1200,11 @@ class MapModel extends BasicModel {
}
/**
* save a map
* @return mixed
* @param CharacterModel|null $characterModel
* @return false|ConnectionModel
*/
public function save(){
$mapModel = parent::save();
public function save(CharacterModel $characterModel = null){
$mapModel = parent::save($characterModel);
// check if map type has changed and clear access objects
if( !$mapModel->dry() ){

View File

@@ -8,11 +8,10 @@
namespace Model;
use controller\MailController;
use DB\SQL\Schema;
use lib\Config;
use Lib\Logging;
class SystemModel extends BasicModel {
class SystemModel extends AbstractMapTrackingModel {
const MAX_POS_X = 2300;
const MAX_POS_Y = 498;
@@ -124,7 +123,8 @@ class SystemModel extends BasicModel {
'rallyPoke' => [
'type' => Schema::DT_BOOL,
'nullable' => false,
'default' => 0
'default' => 0,
'activity-log' => true
],
'description' => [
'type' => Schema::DT_VARCHAR512,
@@ -142,28 +142,6 @@ class SystemModel extends BasicModel {
'nullable' => false,
'default' => 0
],
'createdCharacterId' => [
'type' => Schema::DT_INT,
'index' => true,
'belongs-to-one' => 'Model\CharacterModel',
'constraint' => [
[
'table' => 'character',
'on-delete' => 'CASCADE'
]
]
],
'updatedCharacterId' => [
'type' => Schema::DT_INT,
'index' => true,
'belongs-to-one' => 'Model\CharacterModel',
'constraint' => [
[
'table' => 'character',
'on-delete' => 'CASCADE'
]
]
],
'signatures' => [
'has-many' => ['Model\SystemSignatureModel', 'systemId']
],
@@ -177,16 +155,16 @@ class SystemModel extends BasicModel {
/**
* set an array with all data for a system
* @param array $systemData
* @param array $data
*/
public function setData($systemData){
foreach((array)$systemData as $key => $value){
if($key == 'created'){
continue;
}
public function setData($data){
unset($data['id']);
unset($data['created']);
unset($data['updated']);
unset($data['createdCharacterId']);
unset($data['updatedCharacterId']);
foreach((array)$data as $key => $value){
if(!is_array($value)){
if($this->exists($key)){
$this->$key = $value;
@@ -361,8 +339,6 @@ class SystemModel extends BasicModel {
case 1:
// new rally point set
$rally = date('Y-m-d H:i:s', time());
// flag system for mail poke -> after save()
$this->virtual('newRallyPointSet', true);
break;
default:
$rally = date('Y-m-d H:i:s', $rally);
@@ -417,15 +393,6 @@ class SystemModel extends BasicModel {
*/
public function afterUpdateEvent($self, $pkeys){
$self->clearCacheData();
// check if rally point mail should be send
if(
$self->newRallyPointSet &&
$self->rallyPoke
){
$self->sendRallyPointMail();
}
$activity = ($self->isActive()) ? 'systemUpdate' : 'systemDelete';
$self->logActivity($activity);
}
@@ -441,23 +408,18 @@ class SystemModel extends BasicModel {
}
/**
* log character activity create/update/delete events
* @param string $action
* @return Logging\LogInterface
*/
protected function logActivity($action){
if(
$this->enableActivityLogging &&
(
$action === 'systemDelete' ||
!empty($this->fieldChanges)
) &&
$this->get('mapId')->isActivityLogEnabled()
){
$characterId = $this->get('updatedCharacterId', true);
$mapId = $this->get('mapId', true);
public function newLog($action = ''): Logging\LogInterface{
return $this->getMap()->newLog($action)->setTempData($this->getLogObjectData());
}
parent::bufferActivity($characterId, $mapId, $action);
}
/**
* @return MapModel
*/
public function getMap(): MapModel{
return $this->get('mapId');
}
/**
@@ -599,6 +561,52 @@ class SystemModel extends BasicModel {
return ($this->isWormhole() && $this->security === 'SH');
}
/**
* send rally point poke to various "APIs"
* -> send to a Slack channel
* -> send to an Email
* @param array $rallyData
* @param CharacterModel $characterModel
*/
public function sendRallyPoke(array $rallyData, CharacterModel $characterModel){
// rally log needs at least one handler to be valid
$isValidLog = false;
$log = new Logging\RallyLog('rallySet', $this->getMap()->getLogChannelData());
// Slack poke -----------------------------------------------------------------------------
$slackChannelKey = 'slackChannelRally';
if(
$rallyData['pokeSlack'] === true &&
$this->getMap()->isSlackChannelEnabled($slackChannelKey)
){
$isValidLog = true;
$log->addHandler('slackRally', null, $this->getMap()->getSlackWebHookConfig($slackChannelKey));
}
// Mail poke ------------------------------------------------------------------------------
$mailAddressKey = 'RALLY_SET';
if(
$rallyData['pokeMail'] === true &&
$this->getMap()->isMailSendEnabled('RALLY_SET')
){
$isValidLog = true;
$mailConf = $this->getMap()->getSMTPConfig($mailAddressKey, false);
$log->addHandler('mail', 'mail', $mailConf);
}
// Buffer log -----------------------------------------------------------------------------
if($isValidLog){
$log->setTempData($this->getLogObjectData(true));
$log->setCharacter($characterModel);
if( !empty($rallyData['message']) ){
$log->setData([
'message' => $rallyData['message']
]);
}
$log->buffer();
}
}
/**
* get static WH data for this system
* -> any WH system has at least one static WH
@@ -641,34 +649,27 @@ class SystemModel extends BasicModel {
}
/**
* send rally point information by mail
* get object relevant data for model log
* @param bool $fullData
* @return array
*/
protected function sendRallyPointMail(){
$recipient = Config::getNotificationMail('RALLY_SET');
public function getLogObjectData($fullData = false) : array{
$objectData = [
'objId' => $this->_id,
'objName' => $this->name
];
if(
$recipient &&
\Audit::instance()->email($recipient)
){
$updatedCharacterId = (int) $this->get('updatedCharacterId', true);
/**
* @var $character CharacterModel
*/
$character = $this->rel('updatedCharacterId');
$character->getById( $updatedCharacterId );
if( !$character->dry() ){
$body = [];
$body[] = "Map:\t\t" . $this->mapId->name;
$body[] = "System:\t\t" . $this->name;
$body[] = "Region:\t\t" . $this->region;
$body[] = "Security:\t" . $this->security;
$body[] = "Character:\t" . $character->name;
$body[] = "Time:\t\t" . date('g:i a; F j, Y', strtotime($this->rallyUpdated) );
$bodyMsg = implode("\r\n", $body);
(new MailController())->sendRallyPoint($recipient, $bodyMsg);
}
if($fullData){
$objectData['objAlias'] = $this->alias;
$objectData['objRegion'] = $this->region;
$objectData['objIsWormhole'] = $this->isWormhole();
$objectData['objEffect'] = $this->effect;
$objectData['objSecurity'] = $this->security;
$objectData['objTrueSec'] = $this->trueSec;
$objectData['objDescription'] = $this->description;
}
return $objectData;
}
/**

View File

@@ -9,8 +9,9 @@
namespace Model;
use DB\SQL\Schema;
use Lib\Logging;
class SystemSignatureModel extends BasicModel {
class SystemSignatureModel extends AbstractMapTrackingModel {
protected $table = 'system_signature';
@@ -62,52 +63,29 @@ class SystemSignatureModel extends BasicModel {
'type' => Schema::DT_VARCHAR128,
'nullable' => false,
'default' => '',
'activity-log' => true
'activity-log' => true,
'validate' => true
],
'description' => [
'type' => Schema::DT_VARCHAR512,
'nullable' => false,
'default' => '',
'activity-log' => true
],
'createdCharacterId' => [
'type' => Schema::DT_INT,
'index' => true,
'belongs-to-one' => 'Model\CharacterModel',
'constraint' => [
[
'table' => 'character',
'on-delete' => 'CASCADE'
]
]
],
'updatedCharacterId' => [
'type' => Schema::DT_INT,
'index' => true,
'belongs-to-one' => 'Model\CharacterModel',
'constraint' => [
[
'table' => 'character',
'on-delete' => 'CASCADE'
]
]
]
];
protected $validate = [
'name' => [
'length' => [
'min' => 3
]
]
];
/**
* set an array with all data for a system
* @param $signatureData
* @param $data
*/
public function setData($signatureData){
foreach((array)$signatureData as $key => $value){
public function setData($data){
unset($data['id']);
unset($data['created']);
unset($data['updated']);
unset($data['createdCharacterId']);
unset($data['updatedCharacterId']);
foreach((array)$data as $key => $value){
if(!is_array($value)){
if($this->exists($key)){
$this->$key = $value;
@@ -121,7 +99,6 @@ class SystemSignatureModel extends BasicModel {
* @return \stdClass
*/
public function getData(){
$signatureData = (object) [];
$signatureData->id = $this->id;
@@ -187,6 +164,36 @@ class SystemSignatureModel extends BasicModel {
return $validConnectionId;
}
/**
* validate name column
* @param string $key
* @param string $val
* @return bool
*/
protected function validate_name(string $key, string $val): bool {
$valid = true;
if(mb_strlen($val) < 3){
$valid = false;
$this->throwValidationException($key);
}
return $valid;
}
/**
* @param string $action
* @return Logging\LogInterface
*/
public function newLog($action = ''): Logging\LogInterface{
return $this->getMap()->newLog($action)->setTempData($this->getLogObjectData());
}
/**
* @return MapModel
*/
public function getMap(): MapModel{
return $this->get('systemId')->getMap();
}
/**
* get the connection (if attached)
* @return \Model\ConnectionModel|null
@@ -270,29 +277,14 @@ class SystemSignatureModel extends BasicModel {
}
/**
* log character activity create/update/delete events
* @param string $action
* get object relevant data for model log
* @return array
*/
protected function logActivity($action){
if($this->enableActivityLogging){
/**
* @var $map MapModel
*/
$map = $this->get('systemId')->get('mapId');
if(
(
$action === 'signatureDelete' ||
!empty($this->fieldChanges)
) &&
$map->isActivityLogEnabled()
){
$characterId = $this->get('updatedCharacterId', true);
$mapId = $map->_id;
parent::bufferActivity($characterId, $mapId, $action);
}
}
public function getLogObjectData() : array{
return [
'objId' => $this->_id,
'objName' => $this->name
];
}
/**

View File

@@ -9,9 +9,30 @@
namespace Model\Universe;
use DB\Database;
use Model\BasicModel;
class BasicUniverseModel extends BasicModel {
abstract class BasicUniverseModel extends BasicModel {
/**
* data from Universe tables is static and does not change frequently
* -> refresh static data after X days
*/
const CACHE_MAX_DAYS = 7;
protected $db = 'DB_UNIVERSE';
public static function getNew($model, $ttl = self::DEFAULT_TTL){
$class = null;
$model = '\\' . __NAMESPACE__ . '\\' . $model;
if(class_exists($model)){
$db = Database::instance()->getDB('UNIVERSE');
$class = new $model($db, null, null, $ttl);
}else{
throw new \Exception(sprintf(self::ERROR_INVALID_MODEL_CLASS, $model));
}
return $class;
}
}

View File

@@ -0,0 +1,145 @@
<?php
/**
* Created by PhpStorm.
* User: exodu
* Date: 14.10.2017
* Time: 15:56
*/
namespace Model\Universe;
use DB\SQL\Schema;
class TypeModel extends BasicUniverseModel {
protected $table = 'type';
protected $fieldConf = [
'name' => [
'type' => Schema::DT_VARCHAR128,
'nullable' => false,
'default' => ''
],
'description' => [
'type' => Schema::DT_TEXT
],
'published' => [
'type' => Schema::DT_BOOL,
'nullable' => false,
'default' => 1,
'index' => true
],
'radius' => [
'type' => Schema::DT_FLOAT,
'nullable' => false,
'default' => 0
],
'volume' => [
'type' => Schema::DT_FLOAT,
'nullable' => false,
'default' => 0
],
'capacity' => [
'type' => Schema::DT_FLOAT,
'nullable' => false,
'default' => 0
],
'mass' => [
'type' => Schema::DT_FLOAT,
'nullable' => false,
'default' => 0
],
'groupId' => [
'type' => Schema::DT_INT,
'nullable' => false,
'default' => 0
],
'marketGroupId' => [
'type' => Schema::DT_INT,
'nullable' => false,
'default' => 0
],
'packagedVolume' => [
'type' => Schema::DT_FLOAT,
'nullable' => false,
'default' => 0
],
'portionSize' => [
'type' => Schema::DT_INT,
'nullable' => false,
'default' => 0
],
'graphicId' => [
'type' => Schema::DT_INT,
'nullable' => false,
'default' => 0
]
];
/**
* get shipData from object
* -> more fields can be added in here if needed
* @return \stdClass
*/
public function getShipData(): \stdClass {
$shipData = (object) [];
if(!$this->dry()){
$shipData->typeId = $this->_id;
$shipData->typeName = $this->name;
$shipData->mass = $this->mass;
}
return $shipData;
}
/**
* load data from API into $this and save $this
* @param int $id
* @param array $additionalOptions
*/
protected function loadData(int $id, array $additionalOptions = []){
$data = self::getF3()->ccpClient->getUniverseTypesData($id, $additionalOptions);
if(!empty($data)){
$this->copyfrom($data);
$this->save();
}
}
/**
* load object by $id
* -> if $id not exists in DB -> query API
* @param int $id
* @param array $additionalOptions
*/
public function loadById(int $id, array $additionalOptions = []){
/**
* @var $model self
*/
$model = parent::getById($id);
if($model->isOutdated()){
$model->loadData($id, $additionalOptions);
}
}
/**
* checks whether data is outdated and should be refreshed
* @return bool
*/
protected function isOutdated(): bool {
$outdated = true;
if(!$this->dry()){
$timezone = $this->getF3()->get('getTimeZone')();
$currentTime = new \DateTime('now', $timezone);
$updateTime = \DateTime::createFromFormat(
'Y-m-d H:i:s',
$this->updated,
$timezone
);
$interval = $updateTime->diff($currentTime);
if($interval->days < self::CACHE_MAX_DAYS ){
$outdated = false;
}
}
return $outdated;
}
}

View File

@@ -12,6 +12,8 @@ use DB\SQL\Schema;
use Controller;
use Controller\Api\User as User;
use Exception;
use lib\Config;
use Lib\Logging;
class UserModel extends BasicModel {
@@ -28,27 +30,20 @@ class UserModel extends BasicModel {
'type' => Schema::DT_VARCHAR128,
'nullable' => false,
'default' => '',
'index' => true
'index' => true,
'validate' => true
],
'email' => [
'type' => Schema::DT_VARCHAR128,
'nullable' => false,
'default' => ''
'default' => '',
'validate' => true
],
'userCharacters' => [
'has-many' => ['Model\UserCharacterModel', 'userId']
]
];
protected $validate = [
'name' => [
'length' => [
'min' => 3,
'max' => 50
]
]
];
/**
* get all data for this user
* -> ! caution ! this function returns sensitive data! (e.g. email,..)
@@ -93,23 +88,6 @@ class UserModel extends BasicModel {
return $userData;
}
/**
* validate and set a email address for this user
* -> empty email is allowed!
* @param string $email
* @return string
*/
public function set_email($email){
if (
!empty($email) &&
\Audit::instance()->email($email) == false
) {
// no valid email address
$this->throwValidationError('email');
}
return $email;
}
/**
* check if new user registration is allowed
* @param UserModel $self
@@ -119,10 +97,8 @@ class UserModel extends BasicModel {
*/
public function beforeInsertEvent($self, $pkeys){
$registrationStatus = Controller\Controller::getRegistrationStatus();
switch($registrationStatus){
case 0:
$f3 = self::getF3();
throw new Exception\RegistrationException('User registration is currently not allowed');
break;
case 1:
@@ -133,6 +109,80 @@ class UserModel extends BasicModel {
}
}
/**
* @param BasicModel $self
* @param $pkeys
*/
public function afterEraseEvent($self, $pkeys){
$this->sendDeleteMail();
}
/**
* send delete confirm mail to this user
*/
protected function sendDeleteMail(){
if($this->isMailSendEnabled()){
$log = new Logging\UserLog('userDelete', $this->getLogChannelData());
$log->addHandler('mail', 'mail', $this->getSMTPConfig());
$log->setMessage('Delete Account - {channelName}');
$log->setData([
'message' =>'Your account was successfully deleted.'
]);
$log->buffer();
}
}
/**
* checks whether user has a valid email address and pathfinder has a valid SMTP config
* @return bool
*/
protected function isMailSendEnabled() : bool{
return Config::isValidSMTPConfig($this->getSMTPConfig());
}
/**
* get SMTP config for this user
* @return \stdClass
*/
protected function getSMTPConfig() : \stdClass{
$config = Config::getSMTPConfig();
$config->to = $this->email;
return $config;
}
/**
* validate name column
* @param string $key
* @param string $val
* @return bool
*/
protected function validate_name(string $key, string $val): bool {
$valid = true;
if(
mb_strlen($val) < 3 ||
mb_strlen($val) > 80
){
$valid = false;
$this->throwValidationException($key);
}
return $valid;
}
/**
* validate email column
* @param string $key
* @param string $val
* @return bool
*/
protected function validate_email(string $key, string $val): bool {
$valid = true;
if ( !empty($val) && \Audit::instance()->email($val) == false ){
$valid = false;
$this->throwValidationException($key);
}
return $valid;
}
/**
* check whether this character has already a user assigned to it
* @return bool
@@ -145,10 +195,10 @@ class UserModel extends BasicModel {
/**
* search for user by unique username
* @param $name
* @return array|FALSE
* @return \DB\Cortex
*/
public function getByName($name){
return $this->getByForeignKey('name', $name, [], 0);
return $this->getByForeignKey('name', $name, []);
}
/**
@@ -165,17 +215,9 @@ class UserModel extends BasicModel {
if($this->_id === $currentSessionUser['ID']){
// user matches session data
$sessionCharacters = (array)$this->getF3()->get(User::SESSION_KEY_CHARACTERS);
if($characterId > 0){
// search for specific characterData
foreach($sessionCharacters as $characterData){
if($characterId === (int)$characterData['ID']){
$data = $characterData;
break;
}
}
}elseif( !empty($sessionCharacters) ){
$data = $this->findSessionCharacterData($characterId);
}elseif( !empty($sessionCharacters = (array)$this->getF3()->get(User::SESSION_KEY_CHARACTERS)) ){
// no character was requested ($requestedCharacterId = 0) AND session characters were found
// -> get first matched character (e.g. user open browser tab)
$data = $sessionCharacters[0];
@@ -206,6 +248,26 @@ class UserModel extends BasicModel {
return $data;
}
/**
* search in session data for $characterId
* @param int $characterId
* @return array
*/
public function findSessionCharacterData(int $characterId): array{
$data = [];
if($characterId){
$sessionCharacters = (array)$this->getF3()->get(User::SESSION_KEY_CHARACTERS);
// search for specific characterData
foreach($sessionCharacters as $characterData){
if($characterId === (int)$characterData['ID']){
$data = $characterData;
break;
}
}
}
return $data;
}
/**
* get all userCharacters models for a user
* characters will be checked/updated on login by CCP API call
@@ -292,4 +354,16 @@ class UserModel extends BasicModel {
return $activeCharacters;
}
/**
* get object relevant data for model log channel
* @return array
*/
public function getLogChannelData() : array{
return [
'channelId' => $this->_id,
'channelName' => $this->name
];
}
}

View File

@@ -3,7 +3,7 @@
[PATHFINDER]
NAME = Pathfinder
; installed version (used for CSS/JS cache busting)
VERSION = v1.2.5
VERSION = v1.3.0
; contact information [optional]
CONTACT = https://github.com/exodus4d
; public contact email [optional]
@@ -31,6 +31,11 @@ MODE_MAINTENANCE = 0
CORPORATION =
ALLIANCE =
; Slack API integration ===========================================================================
[PATHFINDER.SLACK]
; Global Slack API status, check PATHFINDER.MAP section for individual control (0=disabled, 1=enabled)
STATUS = 1
; View ============================================================================================
[PATHFINDER.VIEW]
; static page templates
@@ -55,29 +60,51 @@ ADMIN = templates/view/admin.html
; - 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
; LOG_ACTIVITY_ENABLED (0: disable, 1: enable):
; - Whether user activity statistics can be anabled for a map type
; - E.g. create/update/delete of systems/connections/signatures/...
; LOG_HISTORY_ENABLED (0: disable, 1: enable):
; - Whether map change history should be logged to separat *.log files
; - see: [PATHFINDER.HISTORY] config section below
; SEND_HISTORY_SLACK_ENABLED (0: disable, 1: enable):
; - Send map updates to a Slack channel per map
; SEND_RALLY_SLACK_ENABLED (0: disable, 1: enable):
; - Send rally point pokes to a Slack channel per map
; SEND_RALLY_Mail_ENABLED (0: disable, 1: enable):
; - Send rally point pokes by mail
; - see: [PATHFINDER.NOTIFICATION] section below
[PATHFINDER.MAP.PRIVATE]
LIFETIME = 30
MAX_COUNT = 3
MAX_SHARED = 10
MAX_SYSTEMS = 50
ACTIVITY_LOGGING = 1
LIFETIME = 60
MAX_COUNT = 3
MAX_SHARED = 10
MAX_SYSTEMS = 50
LOG_ACTIVITY_ENABLED = 1
LOG_HISTORY_ENABLED = 1
SEND_HISTORY_SLACK_ENABLED = 0
SEND_RALLY_SLACK_ENABLED = 1
SEND_RALLY_Mail_ENABLED = 0
[PATHFINDER.MAP.CORPORATION]
LIFETIME = 99999
MAX_COUNT = 3
MAX_SHARED = 3
MAX_SYSTEMS = 100
ACTIVITY_LOGGING = 1
LIFETIME = 99999
MAX_COUNT = 3
MAX_SHARED = 3
MAX_SYSTEMS = 100
LOG_ACTIVITY_ENABLED = 1
LOG_HISTORY_ENABLED = 1
SEND_HISTORY_SLACK_ENABLED = 1
SEND_RALLY_SLACK_ENABLED = 1
SEND_RALLY_Mail_ENABLED = 0
[PATHFINDER.MAP.ALLIANCE]
LIFETIME = 99999
MAX_COUNT = 3
MAX_SHARED = 2
MAX_SYSTEMS = 100
ACTIVITY_LOGGING = 0
LIFETIME = 99999
MAX_COUNT = 3
MAX_SHARED = 2
MAX_SYSTEMS = 100
LOG_ACTIVITY_ENABLED = 0
LOG_HISTORY_ENABLED = 1
SEND_HISTORY_SLACK_ENABLED = 1
SEND_RALLY_SLACK_ENABLED = 1
SEND_RALLY_Mail_ENABLED = 0
; Route search ====================================================================================
[PATHFINDER.ROUTE]
@@ -87,7 +114,7 @@ SEARCH_DEPTH = 7000
; default count of routes that will be checked (initial) when a system is selected (default: 2)
SEARCH_DEFAULT_COUNT = 2
; max count of routes that can be selected in "route settings" dialog (default: 4)
MAX_Default_COUNT = 4
MAX_DEFAULT_COUNT = 4
; max count of routes that will be checked (MAX_COUNT + custom routes ) (default: 6)
LIMIT = 6
@@ -151,8 +178,6 @@ LOGIN = login
SESSION_SUSPECT = session_suspect
; account deleted
DELETE_ACCOUNT = account_delete
; unauthorized request (HTTP 401)
UNAUTHORIZED = unauthorized
; admin action (e.g. kick, bann) log
ADMIN = admin
; TCP socket errors
@@ -160,7 +185,15 @@ SOCKET_ERROR = socket_error
; debug log for development
DEBUG = debug
[PATHFINDER.HISTORY]
; cache time for parsed log files (seconds) (default: 5)
CACHE = 5
; file folder for 'history' logs (e.g. map history) (default: history/)
LOG = history/
; API =============================================================================================
[PATHFINDER.API]
CCP_IMAGE_SERVER = https://image.eveonline.com
Z_KILLBOARD = https://zkillboard.com/api
; GitHub Developer API
GIT_HUB = https://api.github.com

View File

@@ -30,7 +30,7 @@ ZMQ = 1.1.3
; https://pecl.php.net/package/event
EVENT = 2.3.0
; max execution time for requests
; max execution time for requests (seconds)
MAX_EXECUTION_TIME = 10
; max variable size for $_GET, $_POST and $_COOKIE
@@ -39,6 +39,9 @@ MAX_EXECUTION_TIME = 10
; PHP default = 1000
MAX_INPUT_VARS = 3000
; Formatted HTML StackTraces
HTML_ERRORS = 0
[REQUIREMENTS.LIBS]
ZMQ = 4.1.3

View File

@@ -21,8 +21,12 @@
}],
"require": {
"php-64bit": ">=7.0",
"ext-zmq": "1.1.*",
"ext-curl": ">=7.0",
"ext-zmq": ">=1.1.3",
"react/zmq": "0.3.*",
"monolog/monolog": "1.*",
"websoftwares/monolog-zmq-handler": "0.2.*",
"swiftmailer/swiftmailer": "^6.0",
"exodus4d/pathfinder_esi": "dev-develop as 0.0.x-dev"
}
}

View File

@@ -21,8 +21,12 @@
}],
"require": {
"php-64bit": ">=7.0",
"ext-zmq": "1.1.*",
"ext-curl": ">=7.0",
"ext-zmq": ">=1.1.3",
"react/zmq": "0.3.*",
"exodus4d/pathfinder_esi": "dev-master#v1.1.0"
"monolog/monolog": "1.*",
"websoftwares/monolog-zmq-handler": "0.2.*",
"swiftmailer/swiftmailer": "^6.0",
"exodus4d/pathfinder_esi": "dev-master#v1.2.0"
}
}

View File

@@ -499,7 +499,7 @@ gulp.task('task:hintJS', () => {
* concat/build JS files by modules
*/
gulp.task('task:concatJS', () => {
let modules = ['login', 'mappage', 'setup', 'admin', 'notification'];
let modules = ['login', 'mappage', 'setup', 'admin', 'notification', 'datatables.loader'];
let srcModules = ['./js/app/*(' + modules.join('|') + ').js'];
return gulp.src(srcModules, {base: 'js'})
@@ -854,11 +854,15 @@ gulp.task(
'production',
gulp.series(
'task:configProduction',
'task:cleanJsBuild',
'task:cleanCssBuild',
gulp.parallel(
'task:buildJs',
'task:watchCss'
gulp.series(
'task:cleanJsBuild',
'task:buildJs'
),
gulp.series(
'task:cleanCssBuild',
'task:watchCss'
)
)
)
);

View File

@@ -11,7 +11,7 @@ requirejs.config({
paths: {
layout: 'layout',
config: 'app/config', // path for "configuration" files dir
conf: 'app/conf', // path for "config" files dir
dialog: 'app/ui/dialog', // path for "dialog" files dir
templates: '../../templates', // template dir
img: '../../img', // images dir
@@ -59,11 +59,13 @@ requirejs.config({
tweenLite: 'lib/TweenLite.min',
// datatables // v1.10.12 DataTables - https://datatables.net
'datatables.loader': './app/datatables.loader',
'datatables.net': 'lib/datatables/DataTables-1.10.12/js/jquery.dataTables.min',
'datatables.net-buttons': 'lib/datatables/Buttons-1.2.1/js/dataTables.buttons.min',
'datatables.net-buttons-html': 'lib/datatables/Buttons-1.2.1/js/buttons.html5.min',
'datatables.net-responsive': 'lib/datatables/Responsive-2.1.0/js/dataTables.responsive.min',
'datatables.net-select': 'lib/datatables/Select-1.2.0/js/dataTables.select.min',
'datatables.plugins.render.ellipsis': 'lib/datatables/plugins/render/ellipsis',
// notification plugin
pnotify: 'lib/pnotify/pnotify', // v3.0.0 PNotify - notification core file - https://sciactive.com/pnotify/
@@ -94,6 +96,9 @@ requirejs.config({
customScrollbar: {
deps: ['jquery', 'mousewheel']
},
'datatables.loader': {
deps: ['jquery']
},
'datatables.net': {
deps: ['jquery']
},
@@ -109,6 +114,9 @@ requirejs.config({
'datatables.net-select': {
deps: ['datatables.net']
},
'datatables.plugins.render.ellipsis': {
deps: ['datatables.net']
},
xEditable: {
deps: ['bootstrap']
},

View File

@@ -6,11 +6,7 @@ define([
'jquery',
'app/init',
'app/util',
'datatables.net',
'datatables.net-buttons',
'datatables.net-buttons-html',
'datatables.net-responsive',
'datatables.net-select'
'datatables.loader'
], function($, Init, Util) {
'use strict';

View File

@@ -0,0 +1,11 @@
define([
'datatables.net',
'datatables.net-buttons',
'datatables.net-buttons-html',
'datatables.net-responsive',
'datatables.net-select'
], (a, b) => {
'use strict';
// all Datatables stuff is available...
});

View File

@@ -31,6 +31,7 @@ define(['jquery'], function($) {
deleteMap: 'api/map/delete', // ajax URL - delete map
importMap: 'api/map/import', // ajax URL - import map
getMapConnectionData: 'api/map/getConnectionData', // ajax URL - get connection data
getMapLogData: 'api/map/getLogData', // ajax URL - get logs data
// system API
searchSystem: 'api/system/search', // ajax URL - search system by name
saveSystem: 'api/system/save', // ajax URL - saves system to map
@@ -38,6 +39,7 @@ define(['jquery'], function($) {
getSystemGraphData: 'api/system/graphData', // ajax URL - get all system graph data
getConstellationData: 'api/system/constellationData', // ajax URL - get system constellation data
setDestination: 'api/system/setDestination', // ajax URL - set destination
pokeRally: 'api/system/pokeRally', // ajax URL - send rally point pokes
// connection API
saveConnection: 'api/connection/save', // ajax URL - save new connection to map
deleteConnection: 'api/connection/delete', // ajax URL - delete connection from map
@@ -52,10 +54,6 @@ define(['jquery'], function($) {
// GitHub API
gitHubReleases: 'api/github/releases' // ajax URL - get release info from GitHub
},
url: {
ccpImageServer: '//image.eveonline.com/', // CCP image Server
zKillboard: '//zkillboard.com/api/' // killboard api
},
breakpoints: [
{ name: 'desktop', width: Infinity },
{ name: 'tablet', width: 1200 },

View File

@@ -63,7 +63,7 @@ define([
};
/**
* enables some console.log() information
* enables some debug output in console
* @type {boolean}
*/
let debug = false;
@@ -297,20 +297,29 @@ define([
// global dom remove listener -------------------------------------------------------------------
// -> check whether the removed element had an event listener active and removes them.
document.body.addEventListener ('DOMNodeRemoved', function(e){
if(typeof e.target.getAttribute === 'function'){
let eventNames = e.target.getAttribute(dataKeyEvents);
if(eventNames){
eventNames.split(',').forEach((event) => {
let index = allEvents[event].elements.indexOf(e.target);
if(index > -1){
// remove element from event list
allEvents[event].elements.splice(index, 1);
new MutationObserver((mutations) => {
mutations.forEach((mutation) => {
if(mutation.type === 'childList'){
for (let i = 0; i < mutation.removedNodes.length; i++){
let removedNode = mutation.removedNodes[i];
if(typeof removedNode.getAttribute === 'function'){
let eventNames = removedNode.getAttribute(dataKeyEvents);
if(eventNames){
let events = eventNames.split(',');
for(let j = 0; i < events.length; j++){
let event = events[j];
let index = allEvents[event].elements.indexOf(removedNode);
if(index > -1){
// remove element from event list
allEvents[event].elements.splice(index, 1);
}
}
}
}
});
}
}
}
}, false);
});
}).observe(document.body, { childList: true, subtree: true });
isInit = true;
}

View File

@@ -80,7 +80,7 @@ define([
let showDialog = function(){
// dialog content
requirejs(['text!templates/dialog/task_manager.html', 'mustache'], function(templateTaskManagerDialog, Mustache) {
requirejs(['text!templates/dialog/task_manager.html', 'mustache', 'datatables.loader'], function(templateTaskManagerDialog, Mustache) {
let data = {
id: config.taskDialogId,
dialogDynamicAreaClass: config.dialogDynamicAreaClass,

View File

@@ -676,7 +676,8 @@ define([
dataType: 'json',
context: {
cookieName: requestData.cookie,
characterElement: characterElement
characterElement: characterElement,
browserTabId: Util.getBrowserTabId()
}
}).done(function(responseData, textStatus, request){
this.characterElement.hideLoadingAnimation();
@@ -698,9 +699,11 @@ define([
let data = {
link: this.characterElement.data('href'),
cookieName: this.cookieName,
browserTabId: this.browserTabId,
character: responseData.character,
authLabel: getCharacterAuthLabel(responseData.character.authStatus),
authOK: responseData.character.authStatus === 'OK'
authOK: responseData.character.authStatus === 'OK',
hasActiveSession: responseData.character.hasActiveSession === true
};
let content = Mustache.render(template, data);
@@ -766,6 +769,12 @@ define([
* main init "landing" page
*/
$(function(){
// clear sessionStorage
Util.clearSessionStorage();
// set default AJAX config
Util.ajaxSetup();
// set Dialog default config
Util.initDefaultBootboxConfig();

View File

@@ -28,9 +28,9 @@ define([
overlayLocalJumpsClass: 'pf-map-overlay-local-jumps', // class for jump distance for table results
// dataTable
tableImageCellClass: 'pf-table-image-cell', // class for table "image" cells
tableActionCellClass: 'pf-table-action-cell', // class for table "action" cells
tableActionCellIconClass: 'pf-table-action-icon-cell', // class for table "action" icon (icon is part of cell content)
tableCellImageClass: 'pf-table-image-cell', // class for table "image" cells
tableCellActionClass: 'pf-table-action-cell', // class for table "action" cells
tableCellActionIconClass: 'pf-table-action-icon-cell', // class for table "action" icon (icon is part of cell content)
// toolbar
toolbarClass: 'pf-map-overlay-toolbar', // class for toolbar - content
@@ -288,236 +288,240 @@ define([
* @returns {*}
*/
$.fn.initLocalOverlay = function(mapId){
return this.each(function(){
let parentElement = $(this);
let parentElements = $(this);
let overlay = $('<div>', {
class: [config.overlayClass, config.overlayLocalClass].join(' ')
});
require(['datatables.loader'], () => {
parentElements.each(function(){
let parentElement = $(this);
let content = $('<div>', {
class: [ 'text-right', config.overlayLocalContentClass].join(' ')
});
let overlay = $('<div>', {
class: [config.overlayClass, config.overlayLocalClass].join(' ')
});
// crate new route table
let table = $('<table>', {
class: ['compact', 'order-column', config.overlayLocalTableClass].join(' ')
});
let content = $('<div>', {
class: [ 'text-right', config.overlayLocalContentClass].join(' ')
});
let overlayMain = $('<div>', {
text: '',
class: config.overlayLocalMainClass
}).append(
$('<i>', {
class: ['fa', 'fa-chevron-down', 'fa-fw', 'pf-animate-rotate', config.overlayLocalTriggerClass].join(' ')
}),
$('<span>', {
class: ['badge', 'txt-color', 'txt-color-red', config.overlayLocalUsersClass].join(' '),
text: 0
}),
$('<div>', {
class: config.overlayLocalJumpsClass
// crate new route table
let table = $('<table>', {
class: ['compact', 'order-column', config.overlayLocalTableClass].join(' ')
});
let overlayMain = $('<div>', {
text: '',
class: config.overlayLocalMainClass
}).append(
$('<i>', {
class: ['fa', 'fa-chevron-down', 'fa-fw', 'pf-animate-rotate', config.overlayLocalTriggerClass].join(' ')
}),
$('<span>', {
class: ['badge', 'txt-color', 'txt-color-red', config.overlayLocalUsersClass].join(' '),
text: 0
}),
$('<div>', {
class: config.overlayLocalJumpsClass
}).append(
$('<span>', {
class: ['badge', 'txt-color', 'txt-color-grayLight'].join(' '),
text: MapUtil.config.defaultLocalJumpRadius
}).attr('title', 'jumps')
)
);
let headline = $('<div>', {
class: config.overlayLocalHeadlineClass
}).append(
$('<span>', {
class: ['badge', 'txt-color', 'txt-color-grayLight'].join(' '),
text: MapUtil.config.defaultLocalJumpRadius
}).attr('title', 'jumps')
)
);
html: 'Nearby&nbsp;&nbsp;&nbsp;',
class: 'pull-left'
}),
$('<span>'),
$('<span>'),
$('<span>', {
class: ['badge', ' txt-color', 'txt-color-red'].join(' '),
text: 0
})
);
let headline = $('<div>', {
class: config.overlayLocalHeadlineClass
}).append(
$('<span>', {
html: 'Nearby&nbsp;&nbsp;&nbsp;',
class: 'pull-left'
}),
$('<span>'),
$('<span>'),
$('<span>', {
class: ['badge', ' txt-color', 'txt-color-red'].join(' '),
text: 0
})
);
content.append(headline);
content.append(table);
// toolbar not used for now
// content.append(initToolbar());
content.append(headline);
content.append(table);
// toolbar not used for now
// content.append(initToolbar());
overlay.append(overlayMain);
overlay.append(content);
overlay.append(overlayMain);
overlay.append(content);
// set observer
setOverlayObserver(overlay, mapId);
// set observer
setOverlayObserver(overlay, mapId);
parentElement.append(overlay);
parentElement.append(overlay);
// init local table ---------------------------------------------------------------------------------------
// init local table ---------------------------------------------------------------------------------------
table.on('draw.dt', function(e, settings){
// init table tooltips
$(this).find('td').initTooltips({
container: 'body',
placement: 'left'
});
table.on('draw.dt', function(e, settings){
// init table tooltips
$(this).find('td').initTooltips({
container: 'body',
placement: 'left'
});
// hide pagination in case of only one page
let paginationElement = overlay.find('.dataTables_paginate');
let pageElements = paginationElement.find('span .paginate_button');
if(pageElements.length <= 1){
paginationElement.hide();
}else{
paginationElement.show();
}
});
// table init complete
table.on( 'init.dt', function (){
// init table head tooltips
$(this).initTooltips({
container: 'body',
placement: 'top'
});
});
let localTable = table.DataTable( {
pageLength: 13, // hint: if pagination visible => we need space to show it
paging: true,
lengthChange: false,
ordering: true,
order: [ 0, 'asc' ],
info: false,
searching: false,
hover: false,
autoWidth: false,
rowId: function(rowData) {
return 'pf-local-row_' + rowData.id; // characterId
},
language: {
emptyTable: '<span>You&nbsp;are&nbsp;alone</span>'
},
columnDefs: [
{
targets: 0,
orderable: true,
title: '<span title="jumps" data-toggle="tooltip">&nbsp;</span>',
width: '1px',
className: ['pf-help-default', 'text-center'].join(' '),
data: 'jumps',
render: {
_: function(data, type, row, meta){
let value = data;
if(type === 'display'){
if(value === 0){
value = '<i class="fa fa-map-marker"></i>';
}
}
return value;
}
},
createdCell: function(cell, cellData, rowData, rowIndex, colIndex){
let api = this.DataTable();
initCellTooltip(api, cell, 'log.system.name');
}
},{
targets: 1,
orderable: false,
title: '',
width: '26px',
className: ['pf-help-default', 'text-center', config.tableImageCellClass].join(' '),
data: 'log.ship',
render: {
_: function(data, type, row, meta){
let value = data.typeName;
if(type === 'display'){
value = '<img src="' + Init.url.ccpImageServer + 'Render/' + data.typeId + '_32.png"/>';
}
return value;
}
},
createdCell: function(cell, cellData, rowData, rowIndex, colIndex){
let api = this.DataTable();
initCellTooltip(api, cell, 'log.ship.typeName');
}
}, {
targets: 2,
orderable: true,
title: 'ship&nbsp;name',
width: '80px',
data: 'log.ship',
render: {
_: function(data, type, row, meta){
let value = data.name;
if(type === 'display'){
value = '<div class="' + MapUtil.config.tableCellEllipsisClass + ' ' + MapUtil.config.tableCellEllipsis80Class + '">' + data.name + '</div>';
}
return value;
},
sort: 'name'
}
},{
targets: 3,
orderable: true,
title: 'pilot',
data: 'name',
render: {
_: function(data, type, row, meta){
let value = data;
if(type === 'display'){
value = '<div class="' + MapUtil.config.tableCellEllipsisClass + ' ' + MapUtil.config.tableCellEllipsis90Class + '">' + data + '</div>';
}
return value;
}
}
},{
targets: 4,
orderable: false,
title: '<i title="docked station" data-toggle="tooltip" class="fa fa-home text-right"></i>',
width: '10px',
className: ['pf-help-default'].join(' '),
data: 'log.station',
render: {
_: function(data, type, row, meta){
let value = '';
if(
type === 'display' &&
data.id
){
value = '<i class="fa fa-home"></i>';
}
return value;
}
},
createdCell: function(cell, cellData, rowData, rowIndex, colIndex){
let api = this.DataTable();
initCellTooltip(api, cell, 'log.station.name');
}
},{
targets: 5,
orderable: false,
title: '<i title="open ingame" data-toggle="tooltip" class="fa fa-id-card text-right"></i>',
width: '10px',
className: [config.tableActionCellClass].join(' '),
data: 'id',
render: {
_: function(data, type, row, meta){
let value = data;
if(type === 'display'){
value = '<i class="fa fa-id-card ' + config.tableActionCellIconClass + '"></i>';
}
return value;
}
},
createdCell: function(cell, cellData, rowData, rowIndex, colIndex){
// open character information window (ingame)
$(cell).on('click', { tableApi: this.DataTable(), cellData: cellData }, function(e){
let cellData = e.data.tableApi.cell(this).data();
Util.openIngameWindow(e.data.cellData);
});
}
// hide pagination in case of only one page
let paginationElement = overlay.find('.dataTables_paginate');
let pageElements = paginationElement.find('span .paginate_button');
if(pageElements.length <= 1){
paginationElement.hide();
}else{
paginationElement.show();
}
]
});
// table init complete
table.on( 'init.dt', function (){
// init table head tooltips
$(this).initTooltips({
container: 'body',
placement: 'top'
});
});
let localTable = table.DataTable( {
pageLength: 13, // hint: if pagination visible => we need space to show it
paging: true,
lengthChange: false,
ordering: true,
order: [ 0, 'asc' ],
info: false,
searching: false,
hover: false,
autoWidth: false,
rowId: function(rowData) {
return 'pf-local-row_' + rowData.id; // characterId
},
language: {
emptyTable: '<span>You&nbsp;are&nbsp;alone</span>'
},
columnDefs: [
{
targets: 0,
orderable: true,
title: '<span title="jumps" data-toggle="tooltip">&nbsp;</span>',
width: '1px',
className: ['pf-help-default', 'text-center'].join(' '),
data: 'jumps',
render: {
_: function(data, type, row, meta){
let value = data;
if(type === 'display'){
if(value === 0){
value = '<i class="fa fa-map-marker"></i>';
}
}
return value;
}
},
createdCell: function(cell, cellData, rowData, rowIndex, colIndex){
let api = this.DataTable();
initCellTooltip(api, cell, 'log.system.name');
}
},{
targets: 1,
orderable: false,
title: '',
width: '26px',
className: ['pf-help-default', 'text-center', config.tableCellImageClass].join(' '),
data: 'log.ship',
render: {
_: function(data, type, row, meta){
let value = data.typeName;
if(type === 'display'){
value = '<img src="' + Init.url.ccpImageServer + '/Render/' + data.typeId + '_32.png"/>';
}
return value;
}
},
createdCell: function(cell, cellData, rowData, rowIndex, colIndex){
let api = this.DataTable();
initCellTooltip(api, cell, 'log.ship.typeName');
}
}, {
targets: 2,
orderable: true,
title: 'ship&nbsp;name',
width: '80px',
data: 'log.ship',
render: {
_: function(data, type, row, meta){
let value = data.name;
if(type === 'display'){
value = '<div class="' + MapUtil.config.tableCellEllipsisClass + ' ' + MapUtil.config.tableCellEllipsis80Class + '">' + data.name + '</div>';
}
return value;
},
sort: 'name'
}
},{
targets: 3,
orderable: true,
title: 'pilot',
data: 'name',
render: {
_: function(data, type, row, meta){
let value = data;
if(type === 'display'){
value = '<div class="' + MapUtil.config.tableCellEllipsisClass + ' ' + MapUtil.config.tableCellEllipsis90Class + '">' + data + '</div>';
}
return value;
}
}
},{
targets: 4,
orderable: false,
title: '<i title="docked station" data-toggle="tooltip" class="fa fa-home text-right"></i>',
width: '10px',
className: ['pf-help-default'].join(' '),
data: 'log.station',
render: {
_: function(data, type, row, meta){
let value = '';
if(
type === 'display' &&
data.id
){
value = '<i class="fa fa-home"></i>';
}
return value;
}
},
createdCell: function(cell, cellData, rowData, rowIndex, colIndex){
let api = this.DataTable();
initCellTooltip(api, cell, 'log.station.name');
}
},{
targets: 5,
orderable: false,
title: '<i title="open ingame" data-toggle="tooltip" class="fa fa-id-card text-right"></i>',
width: '10px',
className: [config.tableCellActionClass].join(' '),
data: 'id',
render: {
_: function(data, type, row, meta){
let value = data;
if(type === 'display'){
value = '<i class="fa fa-id-card ' + config.tableCellActionIconClass + '"></i>';
}
return value;
}
},
createdCell: function(cell, cellData, rowData, rowIndex, colIndex){
// open character information window (ingame)
$(cell).on('click', { tableApi: this.DataTable(), cellData: cellData }, function(e){
let cellData = e.data.tableApi.cell(this).data();
Util.openIngameWindow(e.data.cellData);
});
}
}
]
});
});
});
};

View File

@@ -945,14 +945,14 @@ define([
mapContainer = $(mapContainer);
// add additional information for this map
if(mapContainer.data('updated') !== mapConfig.config.updated){
if(mapContainer.data('updated') !== mapConfig.config.updated.updated){
mapContainer.data('name', mapConfig.config.name);
mapContainer.data('scopeId', mapConfig.config.scope.id);
mapContainer.data('typeId', mapConfig.config.type.id);
mapContainer.data('typeName', mapConfig.config.type.name);
mapContainer.data('icon', mapConfig.config.icon);
mapContainer.data('created', mapConfig.config.created);
mapContainer.data('updated', mapConfig.config.updated);
mapContainer.data('created', mapConfig.config.created.created);
mapContainer.data('updated', mapConfig.config.updated.updated);
}
// get map data
@@ -1320,11 +1320,10 @@ define([
* @returns {boolean}
*/
let isValidSystem = function(systemData){
let isValid = true;
if(
! systemData.hasOwnProperty('name') ||
!systemData.hasOwnProperty('name') ||
systemData.name.length === 0
){
return false;
@@ -1382,37 +1381,51 @@ define([
/**
* save a new system and add it to the map
* @param map
* @param requestData
* @param sourceSystem
* @param callback
* @param context
*/
let saveSystem = function(map, requestData, sourceSystem, callback){
let saveSystem = function(requestData, context){
$.ajax({
type: 'POST',
url: Init.path.saveSystem,
data: requestData,
dataType: 'json',
context: {
map: map,
sourceSystem: sourceSystem
context: context
}).done(function(responseData){
let newSystemData = responseData.systemData;
if( !$.isEmptyObject(newSystemData) ){
Util.showNotify({title: 'New system', text: newSystemData.name, type: 'success'});
// draw new system to map
drawSystem(this.map, newSystemData, this.sourceSystem);
// re/arrange systems (prevent overlapping)
MagnetizerWrapper.setElements(this.map);
if(this.onSuccess){
this.onSuccess();
}
}
}).done(function(newSystemData){
Util.showNotify({title: 'New system', text: newSystemData.name, type: 'success'});
// draw new system to map
drawSystem(this.map, newSystemData, this.sourceSystem);
// re/arrange systems (prevent overlapping)
MagnetizerWrapper.setElements(this.map);
if(callback){
callback();
// show errors
if(
responseData.error &&
responseData.error.length > 0
){
for(let i = 0; i < responseData.error.length; i++){
let error = responseData.error[i];
Util.showNotify({title: error.field + ' error', text: 'System: ' + error.message, type: error.type});
}
}
}).fail(function( jqXHR, status, error) {
let reason = status + ' ' + error;
Util.showNotify({title: jqXHR.status + ': saveSystem', text: reason, type: 'warning'});
$(document).setProgramStatus('problem');
}).always(function(){
if(this.onAlways){
this.onAlways(this);
}
});
};
@@ -1542,8 +1555,18 @@ define([
}
};
saveSystem(map, requestData, sourceSystem, function(){
bootbox.hideAll();
this.find('.modal-content').showLoadingAnimation();
saveSystem(requestData, {
map: map,
sourceSystem: sourceSystem,
systemDialog: this,
onSuccess: () => {
bootbox.hideAll();
},
onAlways: (context) => {
context.systemDialog.find('.modal-content').hideLoadingAnimation();
}
});
return false;
}
@@ -1694,7 +1717,8 @@ define([
mapId: mapId,
oldConnectionData: connectionData
}
}).done(function(newConnectionData){
}).done(function(responseData){
let newConnectionData = responseData.connectionData;
if( !$.isEmptyObject(newConnectionData) ){
let updateCon = false;
@@ -1739,6 +1763,16 @@ define([
this.map.detach(this.connection, {fireEvent: false});
}
// show errors
if(
responseData.error &&
responseData.error.length > 0
){
for(let i = 0; i < responseData.error.length; i++){
let error = responseData.error[i];
Util.showNotify({title: error.field + ' error', text: 'System: ' + error.message, type: error.type});
}
}
}).fail(function( jqXHR, status, error) {
// remove this connection from map
this.map.detach(this.connection, {fireEvent: false});
@@ -1826,7 +1860,6 @@ define([
let moduleData = {
id: config.mapContextMenuId,
items: [
{icon: 'fa-street-view', action: 'info', text: 'information'},
{icon: 'fa-plus', action: 'add_system', text: 'add system'},
{icon: 'fa-object-ungroup', action: 'select_all', text: 'select all'},
{icon: 'fa-filter', action: 'filter_scope', text: 'filter scope', subitems: [
@@ -1834,6 +1867,10 @@ define([
{subIcon: '', subAction: 'filter_stargate', subText: 'stargate'},
{subIcon: '', subAction: 'filter_jumpbridge', subText: 'jumpbridge'}
]},
{icon: 'fa-sitemap', action: 'map', text: 'map', subitems: [
{subIcon: 'fa-edit', subAction: 'map_edit', subText: 'edit map'},
{subIcon: 'fa-street-view', subAction: 'map_info', subText: 'map info'},
]},
{divider: true, action: 'delete_systems'},
{icon: 'fa-trash', action: 'delete_systems', text: 'delete systems'}
]
@@ -1905,7 +1942,7 @@ define([
items: [
{icon: 'fa-plus', action: 'add_system', text: 'add system'},
{icon: 'fa-lock', action: 'lock_system', text: 'lock system'},
{icon: 'fa-users', action: 'set_rally', text: 'set rally point'},
{icon: 'fa-volume-up', action: 'set_rally', text: 'set rally point'},
{icon: 'fa-tags', text: 'set status', subitems: systemStatus},
{icon: 'fa-reply fa-rotate-180', text: 'waypoints', subitems: [
{subIcon: 'fa-flag-checkered', subAction: 'set_destination', subText: 'set destination'},
@@ -2731,8 +2768,12 @@ define([
let selectedSystems = currentMapElement.getSelectedSystems();
$.fn.showDeleteSystemDialog(currentMap, selectedSystems);
break;
case 'info':
// open map info dialog
case 'map_edit':
// open map edit dialog tab
$(document).triggerMenuEvent('ShowMapSettings', {tab: 'edit'});
break;
case 'map_info':
// open map info dialog tab
$(document).triggerMenuEvent('ShowMapInfo', {tab: 'information'});
break;
@@ -2893,37 +2934,38 @@ define([
let mapElement = $(this);
let mapOverlay = mapElement.getMapOverlay('local');
let currentCharacterLog = Util.getCurrentCharacterLog();
let currentMapData = Util.getCurrentMapData(userData.config.id);
let clearLocal = true;
if(userData && userData.config && userData.config.id){
let currentMapData = Util.getCurrentMapData(userData.config.id);
let currentCharacterLog = Util.getCurrentCharacterLog();
let clearLocal = true;
if(
currentMapData &&
currentCharacterLog &&
currentCharacterLog.system
){
let currentSystemData = currentMapData.data.systems.filter(function (system) {
return system.systemId === currentCharacterLog.system.id;
});
if(
currentMapData &&
currentCharacterLog &&
currentCharacterLog.system
){
let currentSystemData = currentMapData.data.systems.filter(function (system) {
return system.systemId === currentCharacterLog.system.id;
});
if(currentSystemData.length){
// current user system is on this map
currentSystemData = currentSystemData[0];
if(currentSystemData.length){
// current user system is on this map
currentSystemData = currentSystemData[0];
// check for active users "nearby" (x jumps radius)
let nearBySystemData = Util.getNearBySystemData(currentSystemData, currentMapData, MapUtil.config.defaultLocalJumpRadius);
let nearByCharacterData = Util.getNearByCharacterData(nearBySystemData, userData.data.systems);
// check for active users "nearby" (x jumps radius)
let nearBySystemData = Util.getNearBySystemData(currentSystemData, currentMapData, MapUtil.config.defaultLocalJumpRadius);
let nearByCharacterData = Util.getNearByCharacterData(nearBySystemData, userData.data.systems);
// update "local" table in overlay
mapOverlay.updateLocalTable(currentSystemData, nearByCharacterData);
clearLocal = false;
// update "local" table in overlay
mapOverlay.updateLocalTable(currentSystemData, nearByCharacterData);
clearLocal = false;
}
}
if(clearLocal){
mapOverlay.clearLocalTable();
}
}
if(clearLocal){
mapOverlay.clearLocalTable();
}
});
};

View File

@@ -18,7 +18,19 @@ define([
y: 0
},
systemActiveClass: 'pf-system-active' // class for an active system in a map
systemActiveClass: 'pf-system-active', // class for an active system in a map
dialogRallyId: 'pf-rally-dialog', // id for "Rally point" dialog
dialogRallyPokeDesktopId: 'pf-rally-dialog-poke-desktop', // id for "desktop" poke checkbox
dialogRallyPokeSlackId: 'pf-rally-dialog-poke-slack', // id for "Slack" poke checkbox
dialogRallyPokeMailId: 'pf-rally-dialog-poke-mail', // id for "mail" poke checkbox
dialogRallyMessageId: 'pf-rally-dialog-message', // id for "message" textarea
dialogRallyMessageDefault: '' +
'I need some help!\n\n' +
'- Potential PvP options around\n' +
'- DPS and Logistic ships needed'
};
/**
@@ -26,41 +38,105 @@ define([
* @param system
*/
$.fn.showRallyPointDialog = (system) => {
let mapData = Util.getCurrentMapData(system.data('mapid'));
requirejs(['text!templates/dialog/system_rally.html', 'mustache'], function(template, Mustache) {
let setCheckboxObserver = (checkboxes) => {
checkboxes.each(function(){
$(this).on('change', function(){
// check all others
let allUnchecked = true;
checkboxes.each(function(){
if(this.checked){
allUnchecked = false;
}
});
let textareaElement = $('#' + config.dialogRallyMessageId);
if(allUnchecked){
textareaElement.prop('disabled', true);
}else{
textareaElement.prop('disabled', false);
}
});
});
};
let sendPoke = (requestData, context) => {
// lock dialog
let dialogContent = context.rallyDialog.find('.modal-content');
dialogContent.showLoadingAnimation();
$.ajax({
type: 'POST',
url: Init.path.pokeRally,
data: requestData,
dataType: 'json',
context: context
}).done(function(data){
}).fail(function( jqXHR, status, error) {
let reason = status + ' ' + error;
Util.showNotify({title: jqXHR.status + ': sendPoke', text: reason, type: 'warning'});
}).always(function(){
this.rallyDialog.find('.modal-content').hideLoadingAnimation();
});
};
let data = {
notificationStatus: Init.notificationStatus.rallySet
id: config.dialogRallyId,
dialogRallyPokeDesktopId: config.dialogRallyPokeDesktopId,
dialogRallyPokeSlackId: config.dialogRallyPokeSlackId,
dialogRallyPokeMailId: config.dialogRallyPokeMailId,
dialogRallyMessageId: config.dialogRallyMessageId ,
desktopRallyEnabled: true,
slackRallyEnabled: Boolean(Util.getObjVal(mapData, 'config.logging.slackRally')),
mailRallyEnabled: Boolean(Util.getObjVal(mapData, 'config.logging.mailRally')),
dialogRallyMessageDefault: config.dialogRallyMessageDefault,
systemId: system.data('id')
};
let content = Mustache.render(template, data);
let rallyDialog = bootbox.dialog({
message: content,
title: 'Set rally point for "' + system.getSystemInfo( ['alias'] ) + '"',
title: 'Set rally point in "' + system.getSystemInfo( ['alias'] ) + '"',
buttons: {
close: {
label: 'cancel',
className: 'btn-default'
},
setRallyPoke: {
label: '<i class="fa fa-fw fa-volume-up"></i> set rally and poke',
className: 'btn-primary',
callback: function() {
system.setSystemRally(1, {
poke: true
});
system.markAsChanged();
}
},
success: {
label: '<i class="fa fa-fw fa-users"></i> set rally',
label: '<i class="fa fa-fw fa-volume-up"></i> set rally point',
className: 'btn-success',
callback: function() {
system.setSystemRally(1);
let form = $('#' + config.dialogRallyId).find('form');
// get form data
let formData = form.getFormValues();
// update map
system.setSystemRally(1, {
poke: Boolean(formData.pokeDesktop)
});
system.markAsChanged();
// send poke data to server
sendPoke(formData, {
rallyDialog: this
});
}
}
}
});
// after modal is shown ==================================================================================
rallyDialog.on('shown.bs.modal', function(e){
// set event for checkboxes
setCheckboxObserver(rallyDialog.find(':checkbox'));
});
});
};

View File

@@ -23,6 +23,9 @@ define([
$(() => {
Util.initPrototypes();
// clear sessionStorage
//Util.clearSessionStorage();
// set default AJAX config
Util.ajaxSetup();
@@ -63,7 +66,8 @@ define([
Init.systemType = initData.systemType;
Init.characterStatus = initData.characterStatus;
Init.routes = initData.routes;
Init.notificationStatus = initData.notificationStatus;
Init.url = initData.url;
Init.slack = initData.slack;
Init.routeSearch = initData.routeSearch;
Init.programMode = initData.programMode;
@@ -165,8 +169,7 @@ define([
if(jqXHR.responseJSON){
// handle JSON
let errorObj = $.parseJSON(jqXHR.responseText);
let errorObj = jqXHR.responseJSON;
if(
errorObj.error &&
errorObj.error.length > 0
@@ -182,7 +185,6 @@ define([
}
$(document).trigger('pf:shutdown', {status: jqXHR.status, reason: reason, error: errorData});
};
/**

View File

@@ -9,12 +9,7 @@ define([
'app/ui/system_graph',
'app/ui/system_signature',
'app/ui/system_route',
'app/ui/system_killboard',
'datatables.net',
'datatables.net-buttons',
'datatables.net-buttons-html',
'datatables.net-responsive',
'datatables.net-select'
'app/ui/system_killboard'
], function($, Init, Util, Map, MapUtil) {
'use strict';
@@ -106,29 +101,31 @@ define([
* @param tabContentElement
*/
let drawSystemModules = function(tabContentElement){
let currentSystemData = Util.getCurrentSystemData();
require(['datatables.loader'], () => {
let currentSystemData = Util.getCurrentSystemData();
// get Table cell for system Info
let firstCell = $(tabContentElement).find('.' + config.mapTabContentCellFirst);
let secondCell = $(tabContentElement).find('.' + config.mapTabContentCellSecond);
// get Table cell for system Info
let firstCell = $(tabContentElement).find('.' + config.mapTabContentCellFirst);
let secondCell = $(tabContentElement).find('.' + config.mapTabContentCellSecond);
// draw system info module
firstCell.drawSystemInfoModule(currentSystemData.mapId, currentSystemData.systemData);
// draw system info module
firstCell.drawSystemInfoModule(currentSystemData.mapId, currentSystemData.systemData);
// draw system graph module
firstCell.drawSystemGraphModule(currentSystemData.systemData);
// draw system graph module
firstCell.drawSystemGraphModule(currentSystemData.systemData);
// draw signature table module
firstCell.drawSignatureTableModule(currentSystemData.mapId, currentSystemData.systemData);
// draw signature table module
firstCell.drawSignatureTableModule(currentSystemData.mapId, currentSystemData.systemData);
// draw system routes module
secondCell.drawSystemRouteModule(currentSystemData.mapId, currentSystemData.systemData);
// draw system routes module
secondCell.drawSystemRouteModule(currentSystemData.mapId, currentSystemData.systemData);
// draw system killboard module
secondCell.drawSystemKillboardModule(currentSystemData.systemData);
// draw system killboard module
secondCell.drawSystemKillboardModule(currentSystemData.systemData);
// set Module Observer
setModuleObserver();
// set Module Observer
setModuleObserver();
});
};
/**
@@ -295,7 +292,12 @@ define([
let tabElement = $(this);
// set "main" data
tabElement.data('map-id', options.id).data('updated', options.updated);
tabElement.data('map-id', options.id);
// add updated timestamp (not available for "add" tab
if(Util.getObjVal(options, 'updated.updated')){
tabElement.data('updated', options.updated.updated);
}
// change "tab" link
tabElement.attr('href', '#' + config.mapTabIdPrefix + options.id);
@@ -565,7 +567,7 @@ define([
activeMapIds.push(mapId);
// check for map data change and update tab
if(tabMapData.config.updated > tabElement.data('updated')){
if(tabMapData.config.updated.updated > tabElement.data('updated')){
tabElement.updateTabData(tabMapData.config);
}
}else{

View File

@@ -198,8 +198,7 @@ define([
getMenuHeadline('Information')
).append(
$('<a>', {
class: 'list-group-item list-group-item-info',
href: '#'
class: 'list-group-item list-group-item-info'
}).html('&nbsp;&nbsp;Statistics').prepend(
$('<i>',{
class: 'fa fa-line-chart fa-fw'
@@ -209,8 +208,7 @@ define([
})
).append(
$('<a>', {
class: 'list-group-item list-group-item-info',
href: '#'
class: 'list-group-item list-group-item-info'
}).html('&nbsp;&nbsp;Effect info').prepend(
$('<i>',{
class: 'fa fa-crosshairs fa-fw'
@@ -220,8 +218,7 @@ define([
})
).append(
$('<a>', {
class: 'list-group-item list-group-item-info',
href: '#'
class: 'list-group-item list-group-item-info'
}).html('&nbsp;&nbsp;Jump info').prepend(
$('<i>',{
class: 'fa fa-space-shuttle fa-fw'
@@ -233,8 +230,7 @@ define([
getMenuHeadline('Settings')
).append(
$('<a>', {
class: 'list-group-item',
href: '#'
class: 'list-group-item'
}).html('&nbsp;&nbsp;Account').prepend(
$('<i>',{
class: 'fa fa-user fa-fw'
@@ -245,8 +241,7 @@ define([
).append(
$('<a>', {
class: 'list-group-item hide', // trigger by js
id: Util.config.menuButtonFullScreenId,
href: '#'
id: Util.config.menuButtonFullScreenId
}).html('&nbsp;&nbsp;Full screen').prepend(
$('<i>',{
class: 'glyphicon glyphicon-fullscreen',
@@ -265,8 +260,7 @@ define([
})
).append(
$('<a>', {
class: 'list-group-item',
href: '#'
class: 'list-group-item'
}).html('&nbsp;&nbsp;Notification test').prepend(
$('<i>',{
class: 'fa fa-volume-up fa-fw'
@@ -278,8 +272,7 @@ define([
getMenuHeadline('Danger zone')
).append(
$('<a>', {
class: 'list-group-item list-group-item-danger',
href: '#'
class: 'list-group-item list-group-item-danger'
}).html('&nbsp;&nbsp;Delete account').prepend(
$('<i>',{
class: 'fa fa-user-times fa-fw'
@@ -289,8 +282,7 @@ define([
})
).append(
$('<a>', {
class: 'list-group-item list-group-item-warning',
href: '#'
class: 'list-group-item list-group-item-warning'
}).html('&nbsp;&nbsp;Logout').prepend(
$('<i>',{
class: 'fa fa-sign-in fa-fw'
@@ -317,8 +309,7 @@ define([
class: 'list-group'
}).append(
$('<a>', {
class: 'list-group-item',
href: '#'
class: 'list-group-item'
}).html('&nbsp;&nbsp;Information').prepend(
$('<i>',{
class: 'fa fa-street-view fa-fw'
@@ -327,12 +318,11 @@ define([
$(document).triggerMenuEvent('ShowMapInfo', {tab: 'information'});
})
).append(
getMenuHeadline('Settings')
getMenuHeadline('Configuration')
).append(
$('<a>', {
class: 'list-group-item',
href: '#'
}).html('&nbsp;&nbsp;Configuration').prepend(
class: 'list-group-item'
}).html('&nbsp;&nbsp;Settings').prepend(
$('<i>',{
class: 'fa fa-gears fa-fw'
})
@@ -342,8 +332,7 @@ define([
).append(
$('<a>', {
class: 'list-group-item',
id: Util.config.menuButtonGridId,
href: '#'
id: Util.config.menuButtonGridId
}).html('&nbsp;&nbsp;&nbsp;Grid snapping').prepend(
$('<i>',{
class: 'glyphicon glyphicon-th'
@@ -357,8 +346,7 @@ define([
).append(
$('<a>', {
class: 'list-group-item',
id: Util.config.menuButtonMagnetizerId,
href: '#'
id: Util.config.menuButtonMagnetizerId
}).html('&nbsp;&nbsp;&nbsp;Magnetizing').prepend(
$('<i>',{
class: 'fa fa-magnet fa-fw'
@@ -372,8 +360,7 @@ define([
).append(
$('<a>', {
class: 'list-group-item',
id: Util.config.menuButtonEndpointId,
href: '#'
id: Util.config.menuButtonEndpointId
}).html('&nbsp;&nbsp;&nbsp;Signatures').prepend(
$('<i>',{
class: 'fa fa-link fa-fw'
@@ -388,8 +375,7 @@ define([
getMenuHeadline('Help')
).append(
$('<a>', {
class: 'list-group-item list-group-item-info',
href: '#'
class: 'list-group-item list-group-item-info'
}).html('&nbsp;&nbsp;Manual').prepend(
$('<i>',{
class: 'fa fa-book fa-fw'
@@ -399,8 +385,7 @@ define([
})
).append(
$('<a>', {
class: 'list-group-item list-group-item-info',
href: '#'
class: 'list-group-item list-group-item-info'
}).html('&nbsp;&nbsp;Shortcuts').prepend(
$('<i>',{
class: 'fa fa-keyboard-o fa-fw'
@@ -410,8 +395,7 @@ define([
})
).append(
$('<a>', {
class: 'list-group-item list-group-item-info',
href: '#'
class: 'list-group-item list-group-item-info'
}).html('&nbsp;&nbsp;Task-Manager').prepend(
$('<i>',{
class: 'fa fa-tasks fa-fw'
@@ -423,8 +407,7 @@ define([
getMenuHeadline('Danger zone')
).append(
$('<a>', {
class: 'list-group-item list-group-item-danger',
href: '#'
class: 'list-group-item list-group-item-danger'
}).html('&nbsp;&nbsp;Delete map').prepend(
$('<i>',{
class: 'fa fa-trash fa-fw'
@@ -482,11 +465,13 @@ define([
});
// main menus
$('.' + config.headMenuClass).on('click', function() {
$('.' + config.headMenuClass).on('click', function(e) {
e.preventDefault();
slideMenu.slidebars.toggle('left');
});
$('.' + config.headMapClass).on('click', function() {
$('.' + config.headMapClass).on('click', function(e) {
e.preventDefault();
slideMenu.slidebars.toggle('right');
});
@@ -872,7 +857,7 @@ define([
animateHeaderElement(userInfoElement, function(){
if(currentCharacterChanged){
userInfoElement.find('span').text( newCharacterName );
userInfoElement.find('img').attr('src', Init.url.ccpImageServer + 'Character/' + newCharacterId + '_32.jpg' );
userInfoElement.find('img').attr('src', Init.url.ccpImageServer + '/Character/' + newCharacterId + '_32.jpg' );
}
// init "character switch" popover
userInfoElement.initCharacterSwitchPopover(userData);
@@ -894,7 +879,7 @@ define([
// toggle element
animateHeaderElement(userShipElement, function(){
userShipElement.find('span').text( newShipName );
userShipElement.find('img').attr('src', Init.url.ccpImageServer + 'Render/' + newShipId + '_32.png' );
userShipElement.find('img').attr('src', Init.url.ccpImageServer + '/Render/' + newShipId + '_32.png' );
}, showShipElement);
// set new id for next check
@@ -1104,10 +1089,12 @@ define([
if( statusElement.data('status') !== status ){
// status has changed
if(! programStatusInterval){
// check if timer exists if not -> set default (in case of the "init" ajax call failed
let programStatusVisible = Init.timer ? Init.timer.PROGRAM_STATUS_VISIBLE : 5000;
let timer = function(){
// change status on first timer iteration
if(programStatusCounter === Init.timer.PROGRAM_STATUS_VISIBLE){
if(programStatusCounter === programStatusVisible){
statusElement.velocity('stop').velocity('fadeOut', {
duration: Init.animationSpeed.headerLink,
@@ -1134,8 +1121,8 @@ define([
}
};
if(! programStatusInterval){
programStatusCounter = Init.timer.PROGRAM_STATUS_VISIBLE;
if(!programStatusInterval){
programStatusCounter = programStatusVisible;
programStatusInterval = setInterval(timer, 1000);
}
}

View File

@@ -11,7 +11,7 @@ define(['jquery', 'mustache'], function($, Mustache) {
* @param functionName
* @param config
*/
var initModule = function(functionName, config){
let initModule = function(functionName, config){
if(
typeof config.functions === 'object' &&
@@ -26,7 +26,7 @@ define(['jquery', 'mustache'], function($, Mustache) {
* @param config
* @param data
*/
var showModule = function(config, data){
let showModule = function(config, data){
// require module template
requirejs(['text!templates/' + config.name + '.html'], function(template) {
@@ -37,7 +37,7 @@ define(['jquery', 'mustache'], function($, Mustache) {
$('#' + data.id).length === 0
){
var content = Mustache.render(template, data);
let content = Mustache.render(template, data);
// display module
switch(config.link){
@@ -62,8 +62,130 @@ define(['jquery', 'mustache'], function($, Mustache) {
});
};
/**
* convert JSON object into HTML highlighted string
* @param obj
*/
let highlightJson = (obj) => {
let multiplyString = (num, str) => {
let sb = [];
for (let i = 0; i < num; i++) {
sb.push(str);
}
return sb.join('');
};
let dateObj = new Date();
let regexpObj = new RegExp();
let tab = multiplyString(1, ' ');
let isCollapsible = true;
let quoteKeys = false;
let expImageClicked = '(() => {let container=this.parentNode.nextSibling; container.style.display=container.style.display===\'none\'?\'inline\':\'none\'})();';
let checkForArray = function (obj) {
return obj &&
typeof obj === 'object' &&
typeof obj.length === 'number' &&
!(obj.propertyIsEnumerable('length'));
};
let getRow = function (indent, data, isPropertyContent) {
let tabs = '';
for (let i = 0; i < indent && !isPropertyContent; i++) tabs += tab;
if (data !== null && data.length > 0 && data.charAt(data.length - 1) !== '\n')
data = data + '\n';
return tabs + data;
};
let formatLiteral = function (literal, quote, comma, indent, isArray, style) {
if (typeof literal === 'string')
literal = literal.split('<').join('&lt;').split('>').join('&gt;');
let str = '<span class="' + style + '">' + quote + literal + quote + comma + '</span>';
if (isArray) str = getRow(indent, str);
return str;
};
let formatFunction = function (indent, obj) {
let tabs = '';
for (let i = 0; i < indent; i++) tabs += tab;
let funcStrArray = obj.toString().split('\n');
let str = '';
for (let i = 0; i < funcStrArray.length; i++) {
str += ((i === 0) ? '' : tabs) + funcStrArray[i] + '\n';
}
return str;
};
let highlight = (obj, indent, addComma, isArray, isPropertyContent) => {
let html = '';
let comma = (addComma) ? '<span class="pf-code-Comma">,</span> ' : '';
let type = typeof obj;
let clpsHtml = '';
if (checkForArray(obj)) {
if (obj.length === 0) {
html += getRow(indent, '<span class="pf-code-ArrayBrace">[ ]</span>' + comma, isPropertyContent);
} else {
clpsHtml = isCollapsible ? '<span><i class="fa fa-fw fa-plus-square" onClick="' + expImageClicked + '"></i></span><span class="collapsible">' : '';
html += getRow(indent, '<span class="pf-code-ArrayBrace">[</span>' + clpsHtml, isPropertyContent);
for (let i = 0; i < obj.length; i++) {
html += highlight(obj[i], indent + 1, i < (obj.length - 1), true, false);
}
clpsHtml = isCollapsible ? '</span>' : '';
html += getRow(indent, clpsHtml + '<span class="pf-code-ArrayBrace">]</span>' + comma);
}
} else if (type === 'object') {
if (obj === null) {
html += formatLiteral('null', '', comma, indent, isArray, 'pf-code-Null');
} else if (obj.constructor === dateObj.constructor) {
html += formatLiteral('new Date(' + obj.getTime() + ') /*' + obj.toLocaleString() + '*/', '', comma, indent, isArray, 'Date');
} else if (obj.constructor === regexpObj.constructor) {
html += formatLiteral('new RegExp(' + obj + ')', '', comma, indent, isArray, 'RegExp');
} else {
let numProps = 0;
for (let prop in obj) numProps++;
if (numProps === 0) {
html += getRow(indent, '<span class="pf-code-ObjectBrace">{ }</span>' + comma, isPropertyContent);
} else {
clpsHtml = isCollapsible ? '<span><i class="fa fa-fw fa-plus-square" onClick="' + expImageClicked + '"></i></span><span class="collapsible">' : '';
html += getRow(indent, '<span class="pf-code-ObjectBrace">{</span>' + clpsHtml, isPropertyContent);
let j = 0;
for (let prop in obj) {
if (obj.hasOwnProperty(prop)) {
let quote = quoteKeys ? '"' : '';
html += getRow(indent + 1, '<span class="pf-code-PropertyName">' + quote + prop + quote + '</span>: ' + highlight(obj[prop], indent + 1, ++j < numProps, false, true));
}
}
clpsHtml = isCollapsible ? '</span>' : '';
html += getRow(indent, clpsHtml + '<span class="pf-code-ObjectBrace">}</span>' + comma);
}
}
} else if (type === 'number') {
html += formatLiteral(obj, '', comma, indent, isArray, 'pf-code-Number');
} else if (type === 'boolean') {
html += formatLiteral(obj, '', comma, indent, isArray, 'pf-code-Boolean');
} else if (type === 'function') {
if (obj.constructor === regexpObj.constructor) {
html += formatLiteral('new RegExp(' + obj + ')', '', comma, indent, isArray, 'RegExp');
} else {
obj = formatFunction(indent, obj);
html += formatLiteral(obj, '', comma, indent, isArray, 'pf-code-Function');
}
} else if (type === 'undefined') {
html += formatLiteral('undefined', '', comma, indent, isArray, 'pf-code-Null');
} else {
html += formatLiteral(obj.toString().split('\\').join('\\\\').split('"').join('\\"'), '"', comma, indent, isArray, 'pf-code-String');
}
return html;
};
return highlight(obj, 0, false, false, false);
};
return {
showModule: showModule
showModule: showModule,
highlightJson: highlightJson
};
});

View File

@@ -6,9 +6,10 @@ define([
'jquery',
'app/init',
'app/util',
'app/render',
'bootbox',
'app/map/util'
], function($, Init, Util, bootbox, MapUtil) {
], function($, Init, Util, Render, bootbox, MapUtil) {
'use strict';
@@ -19,23 +20,27 @@ define([
// map info dialog/tabs
dialogMapInfoSummaryId: 'pf-map-info-dialog-summary', // id for map "summary" container
dialogMapInfoUsersId: 'pf-map-info-dialog-users', // id for map "user" container
dialogMapInfoLogsId: 'pf-map-info-dialog-logs', // id for map "logs" container
dialogMapInfoRefreshId: 'pf-map-info-dialog-refresh', // id for map "refresh" container
// "summary" container
// dialog containers
mapInfoId: 'pf-map-info', // id for map info
mapInfoSystemsId: 'pf-map-info-systems', // id for map info systems box
mapInfoConnectionsId: 'pf-map-info-connections', // id for map info connections box
mapInfoUsersId: 'pf-map-info-users', // id for map info users box
mapInfoLogsId: 'pf-map-info-logs', // id for map info logs box
mapInfoTableClass: 'pf-map-info-table', // class for data
mapInfoLifetimeCounterClass: 'pf-map-info-lifetime-counter', // class for map lifetime counter
// dataTable
tableImageCellClass: 'pf-table-image-cell', // class for table "image" cells
tableImageSmallCellClass: 'pf-table-image-small-cell', // class for table "small image" cells
tableActionCellClass: 'pf-table-action-cell', // class for table "action" cells
tableCounterCellClass: 'pf-table-counter-cell', // class for table "counter" cells
tableActionCellIconClass: 'pf-table-action-icon-cell', // class for table "action" icon (icon is part of cell content)
tableToolsClass: 'pf-table-tools', // class for table "tools" section (e.g. Buttons)
tableCellImageClass: 'pf-table-image-cell', // class for table "image" cells
tableCellImageSmallClass: 'pf-table-image-small-cell', // class for table "small image" cells
tableCellActionClass: 'pf-table-action-cell', // class for table "action" cells
tableCellLinkClass: 'pf-table-link-cell', // class for table "links" cells
tableCellCounterClass: 'pf-table-counter-cell', // class for table "counter" cells
tableCellEllipsisClass: 'pf-table-cell-ellipses-auto', // class for table "ellipsis" cells
tableCellActionIconClass: 'pf-table-action-icon-cell', // class for table "action" icon (icon is part of cell content)
loadingOptions: { // config for loading overlay
icon: {
@@ -56,16 +61,26 @@ define([
btnOkIcon: 'fa fa-fw fa-close'
};
/**
* get image that marks a table cell as clickable
* @returns {string}
*/
let getIconForInformationWindow = () => {
return '<i class="fa fa-fw fa-id-card ' + config.tableCellActionIconClass + '" title="open ingame" data-toggle="tooltip"></i>';
};
/**
* loads the map info data into an element
* @param mapData
*/
$.fn.loadMapInfoData = function(mapData){
let mapElement = $(this);
$.fn.initMapInfoData = function(mapData){
let mapElement = $(this).empty();
mapElement.empty();
mapElement.showLoadingAnimation(config.loadingOptions);
// get some more config values from this map. Which are not part of "mapData"
let mapDataOrigin = Util.getCurrentMapData(mapData.config.id);
let countSystems = mapData.data.systems.length;
let countConnections = mapData.data.connections.length;
@@ -80,11 +95,11 @@ define([
}
}
// check max map limits (e.g. max systems per map) ============================================================
// 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 ==============================================================================================
// build content ----------------------------------------------------------------------------------------------
let dlElementLeft = $('<dl>', {
class: 'dl-horizontal',
@@ -115,13 +130,6 @@ define([
class: 'dl-horizontal',
css: {'float': 'right'}
}).append(
$('<dt>').text( 'Lifetime' )
).append(
$('<dd>', {
class: config.mapInfoLifetimeCounterClass,
text: mapData.config.created
})
).append(
$('<dt>').text( 'Systems' )
).append(
$('<dd>', {
@@ -131,6 +139,17 @@ define([
$('<dt>').text( 'Connections' )
).append(
$('<dd>').text( countConnections )
).append(
$('<dt>').text( 'Lifetime' )
).append(
$('<dd>', {
class: config.mapInfoLifetimeCounterClass,
text: mapData.config.created
})
).append(
$('<dt>').text( 'Created' )
).append(
$('<dd>').text( mapDataOrigin.config.created.character.name)
);
mapElement.append(dlElementRight);
@@ -145,14 +164,11 @@ define([
* loads system info table into an element
* @param mapData
*/
$.fn.loadSystemInfoTable = function(mapData){
let systemsElement = $(this);
systemsElement.empty();
$.fn.initSystemInfoTable = function(mapData){
let systemsElement = $(this).empty();
let systemTable = $('<table>', {
class: ['compact', 'stripe', 'order-column', 'row-border', config.mapInfoTableClass].join(' ')
class: ['compact', 'stripe', 'order-column', 'row-border'].join(' ')
});
systemsElement.append(systemTable);
@@ -292,7 +308,7 @@ define([
systemsData.push(tempData);
}
let systemsDataTable = systemTable.dataTable( {
let systemsDataTable = systemTable.DataTable( {
pageLength: 20,
paging: true,
lengthMenu: [[5, 10, 20, 50, -1], [5, 10, 20, 50, 'All']],
@@ -343,7 +359,14 @@ define([
}
},{
title: 'system',
data: 'name'
data: 'name',
className: [config.tableCellLinkClass].join(' '),
createdCell: function(cell, cellData, rowData, rowIndex, colIndex){
// select system
$(cell).on('click', function(e){
Util.getMapModule().getActiveMap().triggerMenuEvent('SelectSystem', {systemId: rowData.id });
});
}
},{
title: 'alias',
data: 'alias'
@@ -401,7 +424,7 @@ define([
title: 'updated',
width: '80px',
searchable: false,
className: ['text-right', config.tableCounterCellClass, 'min-desktop'].join(' '),
className: ['text-right', config.tableCellCounterClass, 'min-desktop'].join(' '),
data: 'updated',
createdCell: function(cell, cellData, rowData, rowIndex, colIndex){
$(cell).initTimestampCounter();
@@ -418,7 +441,7 @@ define([
orderable: false,
searchable: false,
width: '10px',
className: ['text-center', config.tableActionCellClass].join(' '),
className: ['text-center', config.tableCellActionClass].join(' '),
data: 'clear',
createdCell: function(cell, cellData, rowData, rowIndex, colIndex) {
let tempTableElement = this;
@@ -444,11 +467,11 @@ define([
Util.showNotify({title: 'System deleted', text: rowData.name, type: 'success'});
// refresh connection table (connections might have changed) ==================
// refresh connection table (connections might have changed) --------------
let connectionsElement = $('#' + config.mapInfoConnectionsId);
let mapDataNew = activeMap.getMapDataFromClient({forceData: true});
connectionsElement.loadConnectionInfoTable(mapDataNew);
connectionsElement.initConnectionInfoTable(mapDataNew);
}else{
// error
Util.showNotify({title: 'Failed to delete system', text: rowData.name, type: 'error'});
@@ -472,13 +495,11 @@ define([
* loads connection info table into an element
* @param mapData
*/
$.fn.loadConnectionInfoTable = function(mapData){
let connectionsElement = $(this);
connectionsElement.empty();
$.fn.initConnectionInfoTable = function(mapData){
let connectionsElement = $(this).empty();
let connectionTable = $('<table>', {
class: ['compact', 'stripe', 'order-column', 'row-border', config.mapInfoTableClass].join(' ')
class: ['compact', 'stripe', 'order-column', 'row-border'].join(' ')
});
connectionsElement.append(connectionTable);
@@ -489,7 +510,7 @@ define([
connectionsElement.hideLoadingAnimation();
});
// connections table ==========================================================================================
// connections table ------------------------------------------------------------------------------------------
// prepare data for dataTables
let connectionData = [];
@@ -505,8 +526,10 @@ define([
scope_sort: tempConnectionData.scope
};
// source system name
tempConData.source = tempConnectionData.sourceName;
tempConData.source = {
id: tempConnectionData.source,
name: tempConnectionData.sourceName,
};
// connection
let connectionClasses = [];
@@ -519,8 +542,10 @@ define([
tempConData.connection = '<div class="pf-fake-connection ' + connectionClasses + '"></div>';
tempConData.target = tempConnectionData.targetName;
tempConData.target = {
id: tempConnectionData.target,
name: tempConnectionData.targetName,
};
tempConData.updated = tempConnectionData.updated;
@@ -557,7 +582,14 @@ define([
}
},{
title: 'source system',
data: 'source'
data: 'source.name',
className: [config.tableCellLinkClass].join(' '),
createdCell: function(cell, cellData, rowData, rowIndex, colIndex){
// select system
$(cell).on('click', function(e){
Util.getMapModule().getActiveMap().triggerMenuEvent('SelectSystem', {systemId: rowData.source.id });
});
}
},{
title: 'connection',
width: '80px',
@@ -567,12 +599,19 @@ define([
data: 'connection'
}, {
title: 'target system',
data: 'target'
data: 'target.name',
className: [config.tableCellLinkClass].join(' '),
createdCell: function(cell, cellData, rowData, rowIndex, colIndex){
// select system
$(cell).on('click', function(e){
Util.getMapModule().getActiveMap().triggerMenuEvent('SelectSystem', {systemId: rowData.target.id });
});
}
},{
title: 'updated',
width: '80px',
searchable: false,
className: ['text-right', config.tableCounterCellClass].join(' '),
className: ['text-right', config.tableCellCounterClass].join(' '),
data: 'updated',
createdCell: function(cell, cellData, rowData, rowIndex, colIndex){
$(cell).initTimestampCounter();
@@ -589,7 +628,7 @@ define([
orderable: false,
searchable: false,
width: '10px',
className: ['text-center', config.tableActionCellClass].join(' '),
className: ['text-center', config.tableCellActionClass].join(' '),
data: 'clear',
createdCell: function(cell, cellData, rowData, rowIndex, colIndex) {
let tempTableElement = this;
@@ -621,13 +660,11 @@ define([
* loads user info table into an element
* @param mapData
*/
$.fn.loadUsersInfoTable = function(mapData){
let usersElement = $(this);
usersElement.empty();
$.fn.initUsersInfoTable = function(mapData){
let usersElement = $(this).empty();
let userTable = $('<table>', {
class: ['compact', 'stripe', 'order-column', 'row-border', config.mapInfoTableClass].join(' ')
class: ['compact', 'stripe', 'order-column', 'row-border'].join(' ')
});
usersElement.append(userTable);
@@ -644,11 +681,7 @@ define([
});
});
let getIconForInformationWindow = () => {
return '<i class="fa fa-fw fa-id-card ' + config.tableActionCellIconClass + '" title="open ingame" data-toggle="tooltip"></i>';
};
// users table ================================================================================================
// users table ------------------------------------------------------------------------------------------------
// prepare users data for dataTables
let currentMapUserData = Util.getCurrentMapUserData( mapData.config.id );
let usersData = [];
@@ -688,13 +721,13 @@ define([
width: '26px',
orderable: false,
searchable: false,
className: ['pf-help-default', 'text-center', config.tableImageCellClass].join(' '),
className: ['pf-help-default', 'text-center', config.tableCellImageClass].join(' '),
data: 'log.ship',
render: {
_: function(data, type, row, meta){
let value = data;
if(type === 'display'){
value = '<img src="' + Init.url.ccpImageServer + 'Render/' + value.typeId + '_32.png" title="' + value.typeName + '" data-toggle="tooltip" />';
value = '<img src="' + Init.url.ccpImageServer + '/Render/' + value.typeId + '_32.png" title="' + value.typeName + '" data-toggle="tooltip" />';
}
return value;
}
@@ -721,13 +754,13 @@ define([
width: '26px',
orderable: false,
searchable: false,
className: [config.tableImageCellClass].join(' '),
className: [config.tableCellImageClass].join(' '),
data: 'id',
render: {
_: function(data, type, row, meta){
let value = data;
if(type === 'display'){
value = '<img src="' + Init.url.ccpImageServer + 'Character/' + value + '_32.jpg" />';
value = '<img src="' + Init.url.ccpImageServer + '/Character/' + value + '_32.jpg" />';
}
return value;
}
@@ -737,7 +770,7 @@ define([
title: 'pilot',
orderable: true,
searchable: true,
className: [config.tableActionCellClass].join(' '),
className: [config.tableCellActionClass].join(' '),
data: 'name',
render: {
_: function(data, type, row, meta){
@@ -761,13 +794,13 @@ define([
width: '26px',
orderable: false,
searchable: false,
className: [config.tableImageCellClass, config.tableImageSmallCellClass].join(' '),
className: [config.tableCellImageClass, config.tableCellImageSmallClass].join(' '),
data: 'corporation',
render: {
_: function(data, type, row, meta){
let value = data;
if(type === 'display'){
value = '<img src="' + Init.url.ccpImageServer + 'Corporation/' + value.id + '_32.png" />';
value = '<img src="' + Init.url.ccpImageServer + '/Corporation/' + value.id + '_32.png" />';
}
return value;
}
@@ -777,7 +810,7 @@ define([
title: 'corporation',
orderable: true,
searchable: true,
className: [config.tableActionCellClass].join(' '),
className: [config.tableCellActionClass].join(' '),
data: 'corporation',
render: {
_: function (data, type, row, meta) {
@@ -821,6 +854,316 @@ define([
};
/**
* loads logs table into an element
* @param mapData
*/
$.fn.initLogsInfoTable = function(mapData){
let logsElement = $(this).empty();
/**
* ajax load function for log fdata
* @param requestData
* @param context
*/
let getLogsData = (requestData, context) => {
context.logsElement.showLoadingAnimation(config.loadingOptions);
$.ajax({
type: 'POST',
url: Init.path.getMapLogData,
data: requestData,
dataType: 'json',
context: context
}).done(function(data){
this.callback(data, context);
}).fail(function( jqXHR, status, error) {
let reason = status + ' ' + error;
Util.showNotify({title: jqXHR.status + ': loadLogs', text: reason, type: 'warning'});
}).always(function(){
this.logsElement.hideLoadingAnimation();
});
};
/**
* callback function after ajax response with log data
* @param responseData
* @param context
*/
let updateTableDataCallback = (responseData, context) => {
let newLogCount = responseData.data.length;
if(newLogCount > 0){
let pageInfoOld = context.tableApi.page.info();
// add new rows
context.tableApi.rows.add(responseData.data).draw();
let newPageIndex = 0;
if(pageInfoOld.recordsDisplay === 0){
Util.showNotify({title: 'New logs loaded', text: newLogCount + ' most recent logs added', type: 'success'});
}else{
// get new pageInfo (new max page count)
let pageInfoNew = context.tableApi.page.info();
newPageIndex = Math.max(0, pageInfoNew.pages - 1);
Util.showNotify({title: 'More logs loaded', text: newLogCount + ' older logs added', type: 'info'});
}
// get to last page (pageIndex starts at zero) -> check if last page > 0
context.tableApi.page(newPageIndex).draw(false);
}else{
Util.showNotify({title: 'No logs found', text: 'No more entries', type: 'danger'});
}
};
// init logs table --------------------------------------------------------------------------------------------
let logTable = $('<table>', {
class: ['compact', 'stripe', 'order-column', 'row-border', 'pf-table-fixed'].join(' ')
});
logsElement.append(logTable);
let serverDate = Util.getServerTime();
let serverHours = serverDate.setHours(0,0,0,0);
let logDataTable = logTable.DataTable({
pageLength: 25,
paging: true,
lengthMenu: [[10, 25, 50, 100], [10, 25, 50, 100]],
pagingType: 'full_numbers',
ordering: false,
autoWidth: false,
searching: true,
hover: false,
data: [],
language: {
emptyTable: 'No logs available',
zeroRecords: 'No logs found',
lengthMenu: 'Show _MENU_ rows',
info: 'Showing _START_ to _END_ of _TOTAL_ rows'
},
columnDefs: [
{
targets: 0,
title: '<span title="action" data-toggle="tooltip">&nbsp;</span>',
width: 12,
data: 'context.tag',
render: {
_: function(data, type, row, meta){
let value = data;
if(type === 'display'){
let className = 'txt-color-' + data;
value = '<i class="fa fa-circle fa-fw txt-color ' + className + '"></i>';
}
return value;
}
}
},{
targets: 1,
name: 'timestamp',
title: '<i class="fa fa-lg fa-fw fa-clock-o"></i>',
width: 100,
className: ['text-right'].join(' '),
data: 'datetime.date',
render: {
_: function(data, type, row, meta){
// strip microseconds
let logDateString = data.substring(0, 19) ;
let logDate = new Date(logDateString.replace(/-/g, '/'));
data = Util.convertDateToString(logDate, true);
// check whether log is new (today) ->
if(logDate.setHours(0,0,0,0) === serverHours) {
// replace dd/mm/YYYY
data = 'today' + data.substring(10);
}
return data;
}
}
},{
targets: 2,
title: 'level',
width: 40,
data: 'level_name'
},{
targets: 3,
title: 'channel',
className: [config.tableCellEllipsisClass].join(' '),
width: 40,
data: 'channel'
},{
targets: 4,
title: 'message',
width: 115,
data: 'message',
render: {
_: function(data, type, row, meta){
let value = data;
if(type === 'display'){
let className = 'txt-color-';
if(Util.getObjVal(row, 'context.tag')){
className += row.context.tag;
}
value = '<span class="txt-color ' + className + '">' + value + '</span>';
}
return value;
}
}
},{
targets: 5,
title: '',
width: 26,
searchable: false,
className: [config.tableCellImageClass].join(' '),
data: 'context.data.character.id' ,
render: {
_: function(data, type, row, meta){
let value = data;
if(type === 'display'){
value = '<img src="' + Init.url.ccpImageServer + '/Character/' + value + '_32.jpg" />';
}
return value;
}
}
},{
targets: 6,
title: 'pilot',
width: 110,
className: [config.tableCellActionClass].join(' '),
data: 'context.data.character.name',
render: {
_: function(data, type, row, meta){
let value = data;
if(type === 'display'){
value += '&nbsp;' + getIconForInformationWindow();
}
return value;
}
},
createdCell: function(cell, cellData, rowData, rowIndex, colIndex){
// open character information window (ingame)
$(cell).on('click', { tableApi: this.DataTable() }, function(e) {
let rowData = e.data.tableApi.row(this).data();
Util.openIngameWindow(rowData.context.data.character.id);
});
}
},{
targets: 7,
title: 'context',
className: [config.tableCellEllipsisClass].join(' '),
data: 'context.data.formatted'
},{
targets: 8,
title: '<i class="fa fa-lg fa-code text-right"></i>',
width: 12,
className: [config.tableCellActionClass].join(' '),
data: 'context.data',
render: {
_: function(data, type, row, meta){
let value = data;
if(type === 'display'){
// txt-color-redDarker
value = '<i class="fa fa-code ' + config.tableCellActionIconClass + '"></i>';
}
return value;
}
},
createdCell: function(cell, cellData, rowData, rowIndex, colIndex){
// unset formatted string (to much content)
if(cellData.formatted){
// clone data before delete() values
cellData = Object.assign({}, cellData);
delete(cellData.formatted);
}
let jsonHighlighted = Render.highlightJson(cellData);
let content = '<pre><code>' + jsonHighlighted + '</code></pre>';
// open popover with raw log data
$(cell).popover({
placement: 'left',
html: true,
trigger: 'hover',
content: content,
container: 'body',
title: 'Raw data',
delay: {
show: 180,
hide: 0
}
});
}
}
],
initComplete: function(settings){
let tableApi = this.api();
// empty table is ready -> load logs
getLogsData({
mapId: mapData.config.id
}, {
tableApi: tableApi,
callback: updateTableDataCallback,
logsElement: logsElement
});
},
drawCallback: function(settings){
let tableApi = this.api();
// en/disable "load more" button ----------------------------------------------------------------------
let tableInfo = tableApi.page.info();
let isLastPage = (tableInfo.pages === 0 || tableInfo.page === tableInfo.pages - 1);
tableApi.button(0).enable(isLastPage);
// adjust "timestamp" column width --------------------------------------------------------------------
let timestampColumn = tableApi.column('timestamp:name').header();
let timestampColumnCells = tableApi.cells(undefined, 'timestamp:name', {page: 'current', order:'current'});
let hasOldLogs = timestampColumnCells.render( 'display' ).reduce((hasOldLogs, cellValue) => {
return (hasOldLogs === false && !cellValue.startsWith('today')) ? true : hasOldLogs;
}, false);
if(hasOldLogs){
$(timestampColumn).css({width: '100px'});
}else{
$(timestampColumn).css({width: '80px'});
}
}
});
// ------------------------------------------------------------------------------------------------------------
// add dataTable buttons (extension)
logsElement.append($('<div>', {
class: config.tableToolsClass
}));
let buttons = new $.fn.dataTable.Buttons( logDataTable, {
buttons: [
{
className: 'btn btn-sm btn-default',
text: '<i class="fa fa-fw fa-plus"></i>&nbsp;load more',
enabled: false,
action: function ( e, dt, node, config ) {
let pageInfo = dt.page.info();
getLogsData({
mapId: mapData.config.id,
limit: pageInfo.length,
offset: pageInfo.recordsTotal
}, {
tableApi: dt,
callback: updateTableDataCallback,
logsElement: logsElement
});
}
}
]
} );
logDataTable.buttons().container().appendTo( $(this).find('.' + config.tableToolsClass));
};
/**
* shows the map information modal dialog
* @param options
@@ -830,21 +1173,29 @@ define([
let mapData = activeMap.getMapDataFromClient({forceData: true});
if(mapData !== false){
requirejs(['text!templates/dialog/map_info.html', 'mustache'], function(template, Mustache) {
// "log" tab -> get "Origin", not all config options are set in mapData
let mapDataOrigin = Util.getCurrentMapData(mapData.config.id);
requirejs(['text!templates/dialog/map_info.html', 'mustache', 'datatables.loader'], (template, Mustache) => {
let data = {
dialogSummaryContainerId: config.dialogMapInfoSummaryId,
dialogUsersContainerId: config.dialogMapInfoUsersId,
dialogLogsContainerId: config.dialogMapInfoLogsId,
dialogRefreshContainerId: config.dialogMapInfoRefreshId,
dialogNavigationClass: config.dialogNavigationClass,
mapInfoId: config.mapInfoId,
mapInfoSystemsId: config.mapInfoSystemsId,
mapInfoConnectionsId: config.mapInfoConnectionsId,
mapInfoUsersId: config.mapInfoUsersId,
mapInfoLogsId: config.mapInfoLogsId,
logHistoryEnabled: Boolean(Util.getObjVal(mapDataOrigin, 'config.logging.history')),
// default open tab ----------
openTabInformation: options.tab === 'information',
openTabActivity: options.tab === 'activity'
openTabActivity: options.tab === 'activity',
openTabLog: options.tab === 'log'
};
let content = Mustache.render(template, data);
@@ -865,39 +1216,51 @@ define([
});
mapInfoDialog.on('shown.bs.modal', function(e) {
// modal on open
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(){
$('#' + config.dialogMapInfoRefreshId).on('click', function(e){
let menuAction = $(this).attr('data-action');
if(menuAction === 'refresh'){
// get new map data
let mapData = activeMap.getMapDataFromClient({forceData: true});
// find active tab
let activeTabLink = $(this).parents('.navbar').find('.navbar-header.pull-left li.active a');
if(activeTabLink.attr('href') === '#' + config.dialogMapInfoLogsId){
$('#' + config.mapInfoLogsId).initLogsInfoTable(mapDataOrigin);
}
mapElement.loadMapInfoData(mapData);
systemsElement.loadSystemInfoTable(mapData);
connectionsElement.loadConnectionInfoTable(mapData);
usersElement.loadUsersInfoTable(mapData);
mapElement.initMapInfoData(mapData);
systemsElement.initSystemInfoTable(mapData);
connectionsElement.initConnectionInfoTable(mapData);
usersElement.initUsersInfoTable(mapData);
}
});
// load map data
mapElement.loadMapInfoData(mapData);
mapElement.initMapInfoData(mapData);
// load system table
systemsElement.loadSystemInfoTable(mapData);
systemsElement.initSystemInfoTable(mapData);
// load connection table
connectionsElement.loadConnectionInfoTable(mapData);
connectionsElement.initConnectionInfoTable(mapData);
// load users table
usersElement.loadUsersInfoTable(mapData);
usersElement.initUsersInfoTable(mapData);
});
// events for tab change
mapInfoDialog.find('.navbar a').on('shown.bs.tab', function(e){
if($(e.target).attr('href') === '#' + config.dialogMapInfoLogsId){
// "log" tab
let mapDataOrigin = Util.getCurrentMapData(mapData.config.id);
$('#' + config.mapInfoLogsId).initLogsInfoTable(mapDataOrigin);
}
});
});

View File

@@ -24,6 +24,15 @@ define([
deleteEolConnectionsId: 'pf-map-dialog-delete-connections-eol', // id for "deleteEOLConnections" checkbox
persistentAliasesId: 'pf-map-dialog-persistent-aliases', // id for "persistentAliases" checkbox
logHistoryId: 'pf-map-dialog-history', // id for "history logging" checkbox
logActivityId: 'pf-map-dialog-activity', // id for "activity" checkbox
slackWebHookURLId: 'pf-map-dialog-slack-url', // id for Slack "webHookUrl"
slackUsernameId: 'pf-map-dialog-slack-username', // id for Slack "username"
slackIconId: 'pf-map-dialog-slack-icon', // id for Slack "icon"
slackChannelHistoryId: 'pf-map-dialog-slack-channel-history', // id for Slack channel "history"
slackChannelRallyId: 'pf-map-dialog-slack-channel-rally', // id for Slack channel "rally"
characterSelectId: 'pf-map-dialog-character-select', // id for "character" select
corporationSelectId: 'pf-map-dialog-corporation-select', // id for "corporation" select
allianceSelectId: 'pf-map-dialog-alliance-select', // id for "alliance" select
@@ -67,7 +76,7 @@ define([
requirejs([
'text!templates/dialog/map.html',
'text!templates/form/map_settings.html',
'text!templates/form/map.html',
'mustache'
], function(templateMapDialog, templateMapSettings, Mustache) {
@@ -96,10 +105,10 @@ define([
formInfoContainerClass: Util.config.formInfoContainerClass
};
// render "new map" tab content -------------------------------------------
// render "new map" tab content -----------------------------------------------------------------------
let contentNewMap = Mustache.render(templateMapSettings, data);
// render "edit map" tab content ------------------------------------------
// render "edit map" tab content ----------------------------------------------------------------------
let contentEditMap = Mustache.render(templateMapSettings, data);
contentEditMap = $(contentEditMap);
@@ -111,6 +120,19 @@ define([
let deleteEolConnections = true;
let persistentAliases = true;
let logActivity = true;
let logHistory = true;
let slackWebHookURL = '';
let slackUsername = '';
let slackIcon = '';
let slackChannelHistory = '';
let slackChannelRally = '';
let slackEnabled = false;
let slackHistoryEnabled = false;
let slackRallyEnabled = false;
let slackSectionShow = false;
if(mapData !== false){
// set current map information
contentEditMap.find('input[name="id"]').val( mapData.config.id );
@@ -126,9 +148,26 @@ define([
deleteExpiredConnections = mapData.config.deleteExpiredConnections;
deleteEolConnections = mapData.config.deleteEolConnections;
persistentAliases = mapData.config.persistentAliases;
logActivity = mapData.config.logging.activity;
logHistory = mapData.config.logging.history;
slackWebHookURL = mapData.config.logging.slackWebHookURL;
slackUsername = mapData.config.logging.slackUsername;
slackIcon = mapData.config.logging.slackIcon;
slackChannelHistory = mapData.config.logging.slackChannelHistory;
slackChannelRally = mapData.config.logging.slackChannelRally;
slackEnabled = Boolean(Util.getObjVal(Init, 'slack.status'));
slackHistoryEnabled = slackEnabled && Boolean(Util.getObjVal(Init.mapTypes, mapData.config.type.name + '.defaultConfig.send_history_slack_enabled'));
slackRallyEnabled = slackEnabled && Boolean(Util.getObjVal(Init.mapTypes, mapData.config.type.name + '.defaultConfig.send_rally_slack_enabled'));
slackSectionShow = (slackEnabled && slackWebHookURL.length > 0);
// remove "#" from Slack channels
slackChannelHistory = slackChannelHistory.indexOf('#') === 0 ? slackChannelHistory.substr(1) : slackChannelHistory;
slackChannelRally = slackChannelRally.indexOf('#') === 0 ? slackChannelRally.substr(1) : slackChannelRally;
}
// render main dialog -----------------------------------------------------
// render main dialog ---------------------------------------------------------------------------------
data = {
id: config.newMapDialogId,
mapData: mapData,
@@ -162,6 +201,26 @@ define([
deleteEolConnections: deleteEolConnections,
persistentAliases: persistentAliases,
logHistoryId: config.logHistoryId,
logActivityId: config.logActivityId,
logActivity: logActivity,
logHistory: logHistory,
slackWebHookURLId: config.slackWebHookURLId,
slackUsernameId: config.slackUsernameId,
slackIconId: config.slackIconId,
slackChannelHistoryId: config.slackChannelHistoryId,
slackChannelRallyId: config.slackChannelRallyId,
slackWebHookURL: slackWebHookURL,
slackUsername: slackUsername,
slackIcon: slackIcon,
slackChannelHistory: slackChannelHistory,
slackChannelRally: slackChannelRally,
slackEnabled: slackEnabled,
slackHistoryEnabled: slackHistoryEnabled,
slackRallyEnabled: slackRallyEnabled,
slackSectionShow: slackSectionShow,
characterSelectId: config.characterSelectId,
corporationSelectId: config.corporationSelectId,
allianceSelectId: config.allianceSelectId,
@@ -244,6 +303,15 @@ define([
// get form data
let formData = form.getFormValues();
// add value prefixes (Slack channels)
let tmpVal;
if(typeof (tmpVal = Util.getObjVal(formData, 'slackChannelHistory')) === 'string' && tmpVal.length){
formData.slackChannelHistory = '#' + tmpVal;
}
if(typeof (tmpVal = Util.getObjVal(formData, 'slackChannelRally')) === 'string' && tmpVal.length){
formData.slackChannelRally = '#' + tmpVal;
}
// checkbox fix -> settings tab
if( form.find('#' + config.deleteExpiredConnectionsId).length ){
formData.deleteExpiredConnections = formData.hasOwnProperty('deleteExpiredConnections') ? parseInt( formData.deleteExpiredConnections ) : 0;
@@ -254,6 +322,15 @@ define([
if( form.find('#' + config.persistentAliasesId).length ){
formData.persistentAliases = formData.hasOwnProperty('persistentAliases') ? parseInt( formData.persistentAliases ) : 0;
}
if( form.find('#' + config.persistentAliasesId).length ){
formData.persistentAliases = formData.hasOwnProperty('persistentAliases') ? parseInt( formData.persistentAliases ) : 0;
}
if( form.find('#' + config.logHistoryId).length ){
formData.logHistory = formData.hasOwnProperty('logHistory') ? parseInt( formData.logHistory ) : 0;
}
if( form.find('#' + config.logActivityId).length ){
formData.logActivity = formData.hasOwnProperty('logActivity') ? parseInt( formData.logActivity ) : 0;
}
let requestData = {formData: formData};
@@ -264,8 +341,6 @@ define([
dataType: 'json'
}).done(function(responseData){
dialogContent.hideLoadingAnimation();
if(responseData.error.length){
form.showFormMessage(responseData.error);
}else{
@@ -287,6 +362,8 @@ define([
Util.showNotify({title: jqXHR.status + ': saveMap', text: reason, type: 'warning'});
$(document).setProgramStatus('problem');
}).always(function() {
dialogContent.hideLoadingAnimation();
});
}
@@ -297,10 +374,13 @@ define([
});
// after modal is shown =======================================================================
// after modal is shown ===============================================================================
mapInfoDialog.on('shown.bs.modal', function(e){
mapInfoDialog.initTooltips();
// manually trigger the "show" event for the initial active tab (not triggered by default...)
mapInfoDialog.find('.navbar li.active a[data-toggle=tab]').trigger('shown.bs.tab');
// prevent "disabled" tabs from being clicked... "bootstrap" bugFix...
mapInfoDialog.find('.navbar a[data-toggle=tab]').on('click', function(e){
if ($(this).hasClass('disabled')){
@@ -323,17 +403,12 @@ define([
form.showFormMessage([{type: 'warning', message: 'No maps found. Create a new map before you can start'}]);
}
// init select fields in case "settings" tab is open by default
if(options.tab === 'settings'){
initSettingsSelectFields(mapInfoDialog);
}
// init "download tab" ========================================================================
// init "download tab" ============================================================================
let downloadTabElement = mapInfoDialog.find('#' + config.dialogMapDownloadContainerId);
if(downloadTabElement.length){
// tab exists
// export map data ------------------------------------------------------------------------
// export map data ----------------------------------------------------------------------------
downloadTabElement.find('#' + config.buttonExportId).on('click', { mapData: mapData }, function(e){
let exportForm = $('#' + config.dialogMapExportFormId);
@@ -364,7 +439,7 @@ define([
}
});
// import map data ------------------------------------------------------------------------
// import map data ----------------------------------------------------------------------------
// check if "FileReader" API is supported
let importFormElement = downloadTabElement.find('#' + config.dialogMapImportFormId);
if(window.File && window.FileReader && window.FileList && window.Blob){
@@ -477,16 +552,22 @@ define([
}
});
// events for tab change
// events for tab change ------------------------------------------------------------------------------
mapInfoDialog.find('.navbar a').on('shown.bs.tab', function(e){
let modalDialog = mapInfoDialog.find('div.modal-dialog');
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
// "settings" tab -> resize modal
modalDialog.toggleClass('modal-lg', true);
initSettingsSelectFields(mapInfoDialog);
}else{
// resize modal
modalDialog.toggleClass('modal-lg', false);
if( $(selectElementCharacter).data('select2') !== undefined ){
$(selectElementCharacter).select2('destroy');
}
@@ -611,29 +692,38 @@ define([
* @param mapData
*/
$.fn.showDeleteMapDialog = function(mapData){
let mapName = mapData.config.name;
let mapNameStr = '<span class="txt-color txt-color-danger">' + mapName + '</span>';
let mapDeleteDialog = bootbox.confirm('Delete map "' + mapName + '"?', function(result){
if(result){
let data = {mapData: mapData.config};
let mapDeleteDialog = bootbox.confirm({
message: 'Delete map "' + mapNameStr + '"?',
buttons: {
confirm: {
label: '<i class="fa fa-trash fa-fw"></i>&nbsp;delete map',
className: 'btn-danger'
}
},
callback: function(result){
if(result){
let data = {mapData: mapData.config};
$.ajax({
type: 'POST',
url: Init.path.deleteMap,
data: data,
dataType: 'json'
}).done(function(data){
Util.showNotify({title: 'Map deleted', text: 'Map: ' + mapName, type: 'success'});
}).fail(function( jqXHR, status, error) {
let reason = status + ' ' + error;
Util.showNotify({title: jqXHR.status + ': deleteMap', text: reason, type: 'warning'});
$(document).setProgramStatus('problem');
}).always(function() {
$(mapDeleteDialog).modal('hide');
});
$.ajax({
type: 'POST',
url: Init.path.deleteMap,
data: data,
dataType: 'json'
}).done(function(data){
Util.showNotify({title: 'Map deleted', text: 'Map: ' + mapName, type: 'success'});
}).fail(function( jqXHR, 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;
return false;
}
}
});

View File

@@ -8,7 +8,8 @@ define([
'app/init',
'app/util',
'app/render',
'bootbox'
'bootbox',
'peityInlineChart'
], function($, Init, Util, Render, bootbox) {
'use strict';
@@ -25,7 +26,7 @@ define([
// stats/dataTable
statsContainerId: 'pf-stats-dialog-container', // class for statistics container (dynamic ajax content)
statsTableId: 'pf-stats-table', // id for statistics table element
tableImageCellClass: 'pf-table-image-cell', // class for table "image" cells
tableCellImageClass: 'pf-table-image-cell', // class for table "image" cells
// charts
statsLineChartClass: 'pf-line-chart' // class for inline chart elements
@@ -36,7 +37,9 @@ define([
* @param dialogElement
*/
let initStatsTable = function(dialogElement){
let columnNumberWidth = 35;
let columnNumberWidth = 28;
let cellPadding = 4;
let lineChartWidth = columnNumberWidth + (2 * cellPadding);
let lineColor = '#477372';
// render function for inline-chart columns
@@ -71,7 +74,7 @@ define([
lengthMenu: [[10, 20, 30, 50], [10, 20, 30, 50]],
paging: true,
ordering: true,
order: [ 16, 'desc' ],
order: [ 20, 'desc' ],
info: true,
searching: true,
hover: false,
@@ -97,11 +100,11 @@ define([
orderable: false,
searchable: false,
width: 26,
className: ['text-center', config.tableImageCellClass].join(' '),
className: ['text-center', config.tableCellImageClass].join(' '),
data: 'character',
render: {
_: function(data, type, row, meta){
return '<img src="' + Init.url.ccpImageServer + 'Character/' + data.id + '_32.jpg" />';
return '<img src="' + Init.url.ccpImageServer + '/Character/' + data.id + '_32.jpg" />';
}
}
},{
@@ -134,7 +137,7 @@ define([
searchable: false,
width: columnNumberWidth,
className: ['text-right', 'hidden-xs', 'hidden-sm'].join(' '),
data: 'systemCreate',
data: 'mapCreate',
render: {
_: renderInlineChartColumn
}
@@ -145,7 +148,7 @@ define([
searchable: false,
width: columnNumberWidth,
className: ['text-right', 'hidden-xs', 'hidden-sm'].join(' '),
data: 'systemUpdate',
data: 'mapUpdate',
render: {
_: renderInlineChartColumn
}
@@ -156,7 +159,7 @@ define([
searchable: false,
width: columnNumberWidth,
className: ['text-right', 'hidden-xs', 'hidden-sm'].join(' '),
data: 'systemDelete',
data: 'mapDelete',
render: {
_: renderInlineChartColumn
}
@@ -166,7 +169,7 @@ define([
searchable: false,
width: 20,
className: ['text-right', 'separator-right'].join(' ') ,
data: 'systemSum',
data: 'mapSum',
render: {
_: renderNumericColumn
}
@@ -177,7 +180,7 @@ define([
searchable: false,
width: columnNumberWidth,
className: ['text-right', 'hidden-xs', 'hidden-sm'].join(' '),
data: 'connectionCreate',
data: 'systemCreate',
render: {
_: renderInlineChartColumn
}
@@ -188,7 +191,7 @@ define([
searchable: false,
width: columnNumberWidth,
className: ['text-right', 'hidden-xs', 'hidden-sm'].join(' '),
data: 'connectionUpdate',
data: 'systemUpdate',
render: {
_: renderInlineChartColumn
}
@@ -199,7 +202,7 @@ define([
searchable: false,
width: columnNumberWidth,
className: ['text-right', 'hidden-xs', 'hidden-sm'].join(' '),
data: 'connectionDelete',
data: 'systemDelete',
render: {
_: renderInlineChartColumn
}
@@ -208,8 +211,8 @@ define([
title: 'Σ&nbsp;&nbsp;',
searchable: false,
width: 20,
className: ['text-right', 'separator-right'].join(' '),
data: 'connectionSum',
className: ['text-right', 'separator-right'].join(' ') ,
data: 'systemSum',
render: {
_: renderNumericColumn
}
@@ -220,7 +223,7 @@ define([
searchable: false,
width: columnNumberWidth,
className: ['text-right', 'hidden-xs', 'hidden-sm'].join(' '),
data: 'signatureCreate',
data: 'connectionCreate',
render: {
_: renderInlineChartColumn
}
@@ -231,7 +234,7 @@ define([
searchable: false,
width: columnNumberWidth,
className: ['text-right', 'hidden-xs', 'hidden-sm'].join(' '),
data: 'signatureUpdate',
data: 'connectionUpdate',
render: {
_: renderInlineChartColumn
}
@@ -242,7 +245,7 @@ define([
searchable: false,
width: columnNumberWidth,
className: ['text-right', 'hidden-xs', 'hidden-sm'].join(' '),
data: 'signatureDelete',
data: 'connectionDelete',
render: {
_: renderInlineChartColumn
}
@@ -252,12 +255,55 @@ define([
searchable: false,
width: 20,
className: ['text-right', 'separator-right'].join(' '),
data: 'signatureSum',
data: 'connectionSum',
render: {
_: renderNumericColumn
}
},{
targets: 16,
title: '<span title="created" data-toggle="tooltip">C&nbsp;&nbsp;</span>',
orderable: false,
searchable: false,
width: columnNumberWidth,
className: ['text-right', 'hidden-xs', 'hidden-sm'].join(' '),
data: 'signatureCreate',
render: {
_: renderInlineChartColumn
}
},{
targets: 17,
title: '<span title="updated" data-toggle="tooltip">U&nbsp;&nbsp;</span>',
orderable: false,
searchable: false,
width: columnNumberWidth,
className: ['text-right', 'hidden-xs', 'hidden-sm'].join(' '),
data: 'signatureUpdate',
render: {
_: renderInlineChartColumn
}
},{
targets: 18,
title: '<span title="deleted" data-toggle="tooltip">D&nbsp;&nbsp;</span>',
orderable: false,
searchable: false,
width: columnNumberWidth,
className: ['text-right', 'hidden-xs', 'hidden-sm'].join(' '),
data: 'signatureDelete',
render: {
_: renderInlineChartColumn
}
},{
targets: 19,
title: 'Σ&nbsp;&nbsp;',
searchable: false,
width: 20,
className: ['text-right', 'separator-right'].join(' '),
data: 'signatureSum',
render: {
_: renderNumericColumn
}
},{
targets: 20,
title: 'Σ&nbsp;&nbsp;',
searchable: false,
width: 20,
@@ -277,17 +323,17 @@ define([
},
drawCallback: function(settings){
this.api().rows().nodes().to$().each(function(i, row){
$(row).find('.' + config.statsLineChartClass).peity('line', {
$($(row).find('.' + config.statsLineChartClass)).peity('line', {
fill: 'transparent',
height: 18,
min: 0,
width: 50
width: lineChartWidth
});
});
},
footerCallback: function ( row, data, start, end, display ) {
let api = this.api();
let sumColumnIndexes = [7, 11, 15, 16];
let sumColumnIndexes = [7, 11, 15, 19, 20];
// column data for "sum" columns over this page
let pageTotalColumns = api
@@ -310,7 +356,16 @@ define([
statsTable.on('order.dt search.dt', function(){
statsTable.column(0, {search:'applied', order:'applied'}).nodes().each(function(cell, i){
$(cell).html( (i + 1) + '.&nbsp;&nbsp;');
let rowCount = i + 1;
let content = '';
switch(rowCount){
case 1: content = '<i class="fa fa-fw fa-trophy txt-color txt-color-gold"></i>'; break;
case 2: content = '<i class="fa fa-fw fa-trophy txt-color txt-color-silver"></i>'; break;
case 3: content = '<i class="fa fa-fw fa-trophy txt-color txt-color-bronze"></i>'; break;
default: content = rowCount + '.&nbsp;&nbsp;';
}
$(cell).html(content);
});
}).draw();
@@ -413,6 +468,9 @@ define([
let currentWeek = weekStart;
let formattedWeeksData = {
mapCreate: [],
mapUpdate: [],
mapDelete: [],
systemCreate: [],
systemUpdate: [],
systemDelete: [],
@@ -422,6 +480,7 @@ define([
signatureCreate: [],
signatureUpdate: [],
signatureDelete: [],
mapSum: 0,
systemSum: 0,
connectionSum: 0,
signatureSum: 0
@@ -433,6 +492,16 @@ define([
if(weeksData.hasOwnProperty( yearWeekProp )){
let weekData = weeksData[ yearWeekProp ];
// map ----------------------------------------------------------------------------------
formattedWeeksData.mapCreate.push( weekData.mapCreate );
formattedWeeksData.mapSum += parseInt( weekData.mapCreate );
formattedWeeksData.mapUpdate.push( weekData.mapUpdate );
formattedWeeksData.mapSum += parseInt( weekData.mapUpdate );
formattedWeeksData.mapDelete.push( weekData.mapDelete );
formattedWeeksData.mapSum += parseInt( weekData.mapDelete );
// system -------------------------------------------------------------------------------
formattedWeeksData.systemCreate.push( weekData.systemCreate );
formattedWeeksData.systemSum += parseInt( weekData.systemCreate );
@@ -463,6 +532,11 @@ define([
formattedWeeksData.signatureDelete.push( weekData.signatureDelete );
formattedWeeksData.signatureSum += parseInt( weekData.signatureDelete );
}else{
// map -------------------------------------------------------------------------------
formattedWeeksData.mapCreate.push(0);
formattedWeeksData.mapUpdate.push(0);
formattedWeeksData.mapDelete.push(0);
// system -------------------------------------------------------------------------------
formattedWeeksData.systemCreate.push(0);
formattedWeeksData.systemUpdate.push(0);
@@ -487,6 +561,11 @@ define([
}
}
// map ---------------------------------------------------------------------------------------
formattedWeeksData.mapCreate = formattedWeeksData.mapCreate.join(',');
formattedWeeksData.mapUpdate = formattedWeeksData.mapUpdate.join(',');
formattedWeeksData.mapDelete = formattedWeeksData.mapDelete.join(',');
// system ---------------------------------------------------------------------------------------
formattedWeeksData.systemCreate = formattedWeeksData.systemCreate.join(',');
formattedWeeksData.systemUpdate = formattedWeeksData.systemUpdate.join(',');
@@ -515,6 +594,19 @@ define([
name: data.name,
lastLogin: data.lastLogin
},
mapCreate: {
type: 'C',
data: formattedWeeksData.mapCreate
},
mapUpdate: {
type: 'U',
data: formattedWeeksData.mapUpdate
},
mapDelete: {
type: 'D',
data: formattedWeeksData.mapDelete
},
mapSum: formattedWeeksData.mapSum,
systemCreate: {
type: 'C',
data: formattedWeeksData.systemCreate
@@ -554,7 +646,8 @@ define([
data: formattedWeeksData.signatureDelete
},
signatureSum: formattedWeeksData.signatureSum,
totalSum: formattedWeeksData.systemSum + formattedWeeksData.connectionSum + formattedWeeksData.signatureSum
totalSum: formattedWeeksData.mapSum + formattedWeeksData.systemSum +
formattedWeeksData.connectionSum + formattedWeeksData.signatureSum
};
formattedData.push(rowData);
@@ -602,18 +695,18 @@ define([
* @param type
* @returns {boolean}
*/
let isTabTypeEnabled = function(type){
let isTabTypeEnabled = (type) => {
let enabled = false;
switch(type){
case 'private':
if(Init.mapTypes.private.defaultConfig.activity_logging){
if( Boolean(Util.getObjVal(Init.mapTypes, type + '.defaultConfig.log_activity_enabled')) ){
enabled = true;
}
break;
case 'corporation':
if(
Init.mapTypes.corporation.defaultConfig.activity_logging &&
Boolean(Util.getObjVal(Init.mapTypes, type + '.defaultConfig.log_activity_enabled')) &&
Util.getCurrentUserInfo('corporationId')
){
enabled = true;
@@ -621,7 +714,7 @@ define([
break;
case 'alliance':
if(
Init.mapTypes.alliance.defaultConfig.activity_logging &&
Boolean(Util.getObjVal(Init.mapTypes, type + '.defaultConfig.log_activity_enabled')) &&
Util.getCurrentUserInfo('allianceId')
){
enabled = true;
@@ -636,15 +729,46 @@ define([
* show activity stats dialog
*/
$.fn.showStatsDialog = function(){
requirejs(['text!templates/dialog/stats.html', 'mustache', 'peityInlineChart'], function(template, Mustache) {
requirejs(['text!templates/dialog/stats.html', 'mustache', 'datatables.loader'], function(template, Mustache) {
// get current statistics map settings
let logActivityEnabled = false;
let activeMap = Util.getMapModule().getActiveMap();
if(activeMap){
let activeMapId = activeMap.data('id');
let activeMapData = Util.getCurrentMapData(activeMapId);
if(activeMapData){
logActivityEnabled = Boolean(Util.getObjVal(activeMapData, 'config.logging.activity'));
}
}
// check which dialog tab is default active
let enablePrivateTab = isTabTypeEnabled('private');
let enableCorporationTab = isTabTypeEnabled('corporation');
let enableAllianceTab = isTabTypeEnabled('alliance');
let activePrivateTab = false;
let activeCorporationTab = false;
let activeAllianceTab = false;
if(enableCorporationTab){
activeCorporationTab = true;
}else if(enableAllianceTab){
activeAllianceTab = true;
}else if(enablePrivateTab){
activePrivateTab = true;
}
let data = {
id: config.statsDialogId,
dialogNavigationClass: config.dialogNavigationClass,
dialogNavLiClass: config.dialogNavigationListItemClass,
enablePrivateTab: isTabTypeEnabled('private'),
enableCorporationTab: isTabTypeEnabled('corporation'),
enableAllianceTab: isTabTypeEnabled('alliance'),
enablePrivateTab: enablePrivateTab,
enableCorporationTab: enableCorporationTab,
enableAllianceTab: enableAllianceTab,
activePrivateTab: activePrivateTab,
activeCorporationTab: activeCorporationTab,
activeAllianceTab: activeAllianceTab,
logActivityEnabled: logActivityEnabled,
statsContainerId: config.statsContainerId,
statsTableId: config.statsTableId,
dialogNavigationOffsetClass: config.dialogNavigationOffsetClass,
@@ -670,7 +794,6 @@ define([
// model events
statsDialog.on('show.bs.modal', function(e) {
let dialogElement = $(e.target);
initStatsTable(dialogElement);
});

View File

@@ -197,15 +197,15 @@ define([
switch(options.type){
case 'character':
imagePath = Init.url.ccpImageServer + 'Character/' + data.id + '_32.jpg';
imagePath = Init.url.ccpImageServer + '/Character/' + data.id + '_32.jpg';
previewContent = '<img src="' + imagePath + '" style="max-width: 100%" />';
break;
case 'corporation':
imagePath = Init.url.ccpImageServer + 'Corporation/' + data.id + '_32.png';
imagePath = Init.url.ccpImageServer + '/Corporation/' + data.id + '_32.png';
previewContent = '<img src="' + imagePath + '" style="max-width: 100%" />';
break;
case 'alliance':
imagePath = Init.url.ccpImageServer + 'Alliance/' + data.id + '_32.png';
imagePath = Init.url.ccpImageServer + '/Alliance/' + data.id + '_32.png';
previewContent = '<img src="' + imagePath + '" style="max-width: 100%" />';
break;
}

View File

@@ -10,7 +10,7 @@ define([
], function($, Init, Util, Morris) {
'use strict';
var config = {
let config = {
// module info
moduleClass: 'pf-module', // class for each module
@@ -53,8 +53,8 @@ define([
* @param option
* @returns {string}
*/
var getInfoForGraph = function(graphKey, option){
var info = '';
let getInfoForGraph = function(graphKey, option){
let info = '';
if(config.systemGraphLabels.hasOwnProperty(graphKey)){
info = config.systemGraphLabels[graphKey][option];
@@ -69,14 +69,14 @@ define([
* @param graphKey
* @param graphData
*/
var initGraph = function(graphElement, graphKey, graphData, eventLine){
let initGraph = function(graphElement, graphKey, graphData, eventLine){
if(graphData.length > 0){
var labelYFormat = function(y){
let labelYFormat = function(y){
return Math.round(y);
};
var graphConfig = {
let graphConfig = {
element: graphElement,
data: graphData,
xkey: 'x',
@@ -121,24 +121,24 @@ define([
* @param parentElement
* @param systemData
*/
var drawModule = function(parentElement, systemData){
let drawModule = function(parentElement, systemData){
// graph data is available for k-space systems
if(systemData.type.id === 2){
var requestData = {
let requestData = {
systemIds: [systemData.systemId]
};
// calculate time offset until system created
var serverData = Util.getServerTime();
let serverData = Util.getServerTime();
var timestampNow = Math.floor(serverData.getTime() / 1000);
var timeSinceUpdate = timestampNow - systemData.updated;
let timestampNow = Math.floor(serverData.getTime() / 1000);
let timeSinceUpdate = timestampNow - systemData.updated;
var timeInHours = Math.floor(timeSinceUpdate / 3600);
var timeInMinutes = Math.floor((timeSinceUpdate % 3600) / 60);
var timeInMinutesPercent = ( timeInMinutes / 60 ).toFixed(2);
var eventLine = timeInHours + timeInMinutesPercent;
let timeInHours = Math.floor(timeSinceUpdate / 3600);
let timeInMinutes = Math.floor((timeSinceUpdate % 3600) / 60);
let timeInMinutesPercent = ( timeInMinutes / 60 ).toFixed(2);
let eventLine = timeInHours + timeInMinutesPercent;
// graph is from right to left -> convert event line
eventLine = 23 - eventLine;
@@ -152,7 +152,7 @@ define([
if( Object.keys(systemGraphsData).length > 0 ){
// create new (hidden) module container
var moduleElement = $('<div>', {
let moduleElement = $('<div>', {
class: [config.moduleClass, config.systemGraphModuleClass].join(' '),
css: {opacity: 0}
});
@@ -165,7 +165,7 @@ define([
}
// row element
var rowElement = $('<div>', {
let rowElement = $('<div>', {
class: 'row'
});
moduleElement.append(rowElement);
@@ -173,15 +173,15 @@ define([
$.each(systemGraphsData, function(systemId, graphsData){
$.each(graphsData, function(graphKey, graphData){
var colElement = $('<div>', {
let colElement = $('<div>', {
class: ['col-xs-12', 'col-sm-6', 'col-md-4'].join(' ')
});
var headlineElement = $('<h5>').text( getInfoForGraph(graphKey, 'headline') );
let headlineElement = $('<h5>').text( getInfoForGraph(graphKey, 'headline') );
colElement.append(headlineElement);
var graphElement = $('<div>', {
let graphElement = $('<div>', {
class: config.systemGraphClass
});
@@ -203,7 +203,7 @@ define([
});
}
}).fail(function( jqXHR, status, error) {
var reason = status + ' ' + error;
let reason = status + ' ' + error;
Util.showNotify({title: jqXHR.status + ': System graph data', text: reason, type: 'warning'});
$(document).setProgramStatus('problem');
});
@@ -218,10 +218,10 @@ define([
*/
$.fn.drawSystemGraphModule = function(systemData){
var parentElement = $(this);
let parentElement = $(this);
// check if module already exists
var moduleElement = parentElement.find('.' + config.systemGraphModuleClass);
let moduleElement = parentElement.find('.' + config.systemGraphModuleClass);
if(moduleElement.length > 0){
moduleElement.velocity('transition.slideDownOut', {

View File

@@ -30,9 +30,10 @@ define([
};
/**
* get label element with given content
*
* @param text
* @returns {*|XMLList}
* @param options
* @returns {jQuery}
*/
let getLabel = function(text, options){
let label = $('<span>', {
@@ -42,7 +43,6 @@ define([
return label;
};
let showKillmails = function(moduleElement, killboardData){
// show number of killMails
@@ -61,7 +61,7 @@ define([
break;
}
moduleElement.append( $('<h5>').text(i + 'h ago'));
moduleElement.append( $('<h5>').text( i ? i + 'h ago' : 'recent'));
let killMailData = killboardData.tableData[i].killmails;
@@ -77,25 +77,25 @@ define([
let killData = killMailData[j];
let linkUrl = '//zkillboard.com/kill/' + killData.killID + '/';
let victimImageUrl = Init.url.ccpImageServer + 'Type/' + killData.victim.shipTypeID + '_64.png';
let killDate = getDateObjectByTimeString(killData.killTime);
let linkUrl = '//zkillboard.com/kill/' + killData.killmail_id + '/';
let victimImageUrl = Init.url.ccpImageServer + '/Type/' + killData.victim.ship_type_id + '_64.png';
let killDate = Util.convertDateToUTC(new Date(killData.killmail_time));
let killDateString = Util.convertDateToString(killDate);
let killLossValue = Util.formatPrice( killData.zkb.totalValue );
// check for ally
let victimAllyLogoUrl = '';
let displayAlly = 'none';
if(killData.victim.allianceID > 0){
victimAllyLogoUrl = Init.url.ccpImageServer + 'Alliance/' + killData.victim.allianceID + '_32.png';
if(killData.victim.alliance_id > 0){
victimAllyLogoUrl = Init.url.ccpImageServer + '/Alliance/' + killData.victim.alliance_id + '_32.png';
displayAlly = 'block';
}
// check for corp
let victimCorpLogoUrl = '';
let displayCorp = 'none';
if(killData.victim.corporationID > 0){
victimCorpLogoUrl = Init.url.ccpImageServer + 'Corporation/' + killData.victim.corporationID + '_32.png';
if(killData.victim.corporation_id > 0){
victimCorpLogoUrl = Init.url.ccpImageServer + '/Corporation/' + killData.victim.corporation_id + '_32.png';
displayCorp = 'inline';
}
@@ -126,7 +126,7 @@ define([
text: killData.victim.characterName
}).prepend(
$('<small>', {
text: killDateString + ' - '
text: killDateString
})
).prepend(
$('<img>', {
@@ -324,7 +324,7 @@ define([
wSpaceLinkModifier = 'w-space/';
}
let url = Init.url.zKillboard;
let url = Init.url.zKillboard + '/';
url += 'no-items/' + wSpaceLinkModifier + 'no-attackers/solarSystemID/' + systemData.systemId + '/pastSeconds/' + timeFrameInSeconds + '/';
killboardGraphElement.showLoadingAnimation();
@@ -338,16 +338,14 @@ define([
// the API wont return more than 200KMs ! - remember last bar block with complete KM information
let lastCompleteDiffHourData = 0;
// loop kills and count kills by hour
for (let i = 0; i < kbData.length; i++) {
let killmailData = kbData[i];
let killDate = getDateObjectByTimeString(killmailData.killTime);
let killDate = Util.convertDateToUTC(new Date(killmailData.killmail_time));
// get time diff
let timeDiffMin = Math.round(( serverDate - killDate ) / 1000 / 60);
let timeDiffHour = Math.round(timeDiffMin / 60);
let timeDiffHour = Math.floor(timeDiffMin / 60);
// update chart data
if (chartData[timeDiffHour]) {
@@ -420,22 +418,11 @@ define([
});
};
/**
* transform timestring
* @param timeString
* @returns {Date}
*/
let getDateObjectByTimeString = function(timeString){
let match = timeString.match(/^(\d+)-(\d+)-(\d+) (\d+)\:(\d+)\:(\d+)$/);
let date = new Date(match[1], match[2] - 1, match[3], match[4], match[5], match[6]);
return date;
};
/**
* get module element
* @param parentElement
* @param systemData
* @returns {*|HTMLElement}
* @returns {*|jQuery|HTMLElement}
*/
let getModule = function(parentElement, systemData){

View File

@@ -4,8 +4,8 @@
define([
'jquery',
'app/init',
'config/system_effect',
'config/signature_type',
'conf/system_effect',
'conf/signature_type',
'bootbox',
'localForage',
'velocity',
@@ -264,7 +264,8 @@ define([
errors[i].field.length > 0
){
let formField = formElement.find('[name="' + errors[i].field + '"]');
formField.parents('.form-group').removeClass('has-success').addClass('has-error');
let formGroup = formField.parents('.form-group').removeClass('has-success').addClass('has-error');
let formHelp = formGroup.find('.help-block').text(errors[i].message);
}
}else if(errors[i].type === 'warning'){
@@ -559,6 +560,7 @@ define([
let data = {
id: config.headCharacterSwitchId,
browserTabId: getBrowserTabId(),
routes: Init.routes,
userData: userData,
otherCharacters: $.grep( userData.characters, function( character ) {
@@ -729,17 +731,24 @@ define([
let defaultOptions = {
dismissible: true,
messageId: 'pf-alert-' + Math.random().toString(36).substring(7),
messageTypeClass: messageTypeClass,
messageTextClass: messageTextClass
messageTextClass: messageTextClass,
insertElement: 'replace'
};
defaultOptions = $.extend(defaultOptions, config);
let content = Mustache.render(template, defaultOptions);
containerElement.html(content);
switch(defaultOptions.insertElement){
case 'replace': containerElement.html(content); break;
case 'prepend': containerElement.prepend(content); break;
case 'append': containerElement.append(content); break;
default: console.error('insertElement: %s is not specified!', defaultOptions.insertElement);
}
containerElement.children().first().velocity('stop').velocity('fadeIn');
//containerElement.children().first().velocity('stop').velocity('fadeIn');
$('#' + defaultOptions.messageId).velocity('stop').velocity('fadeIn');
});
};
@@ -1072,6 +1081,20 @@ define([
return Init.currentUserData;
};
/**
* get a unique ID for each tab
* -> store ID in session storage
*/
let getBrowserTabId = () => {
let key = 'tabId';
let tabId = sessionStorage.getItem(key);
if(tabId === null){
tabId = Math.random().toString(36).substr(2, 5);
sessionStorage.setItem(key, tabId);
}
return tabId;
};
/**
* set default jQuery AJAX configuration
*/
@@ -1081,6 +1104,9 @@ define([
// Add custom application headers on "same origin" requests only!
// -> Otherwise a "preflight" request is made, which will "probably" fail
if(settings.crossDomain === false){
// Add browser tab information
xhr.setRequestHeader('Pf-Tab-Id', getBrowserTabId()) ;
// add current character data to ANY XHR request (HTTP HEADER)
// -> This helps to identify multiple characters on multiple browser tabs
let userData = getCurrentUserData();
@@ -2058,34 +2084,59 @@ define([
};
/**
* Create Date as UTC
* clear session Storage
* -> otherwise a tab refresh does not clear sessionStorage!
*/
let clearSessionStorage = () => {
if(sessionStorage){
sessionStorage.clear();
}
};
/**
* Create Date() as UTC
* @param date
* @returns {Date}
*/
let createDateAsUTC = function(date) {
let createDateAsUTC = function(date){
return new Date(Date.UTC(date.getFullYear(), date.getMonth(), date.getDate(), date.getHours(), date.getMinutes(), date.getSeconds()));
};
/**
* Convert Date to UTC (!important function!)
* Convert Date() to UTC (!important function!)
* @param date
* @returns {Date}
*/
let convertDateToUTC = function(date) {
let convertDateToUTC = function(date){
return new Date(date.getUTCFullYear(), date.getUTCMonth(), date.getUTCDate(), date.getUTCHours(), date.getUTCMinutes(), date.getUTCSeconds());
};
/**
* Convert Date to Time String
* Convert Date() to Time String
* @param date
* @param showSeconds
* @returns {string}
*/
let convertDateToString = function(date){
let convertDateToString = function(date, showSeconds){
let dateString = ('0'+ (date.getMonth() + 1 )).slice(-2) + '/' + ('0'+date.getDate()).slice(-2) + '/' + date.getFullYear();
let timeString = ('0' + date.getHours()).slice(-2) + ':' + ('0'+date.getMinutes()).slice(-2);
timeString += (showSeconds) ? ':' + ('0'+date.getSeconds()).slice(-2) : '';
return dateString + ' ' + timeString;
};
/**
* get deep json object value if exists
* -> e.g. key = 'first.last.third' string
* @param obj
* @param key
* @returns {*}
*/
let getObjVal = (obj, key) => {
return key.split('.').reduce((o, x) => {
return (typeof o === 'undefined' || o === null) ? o : o[x];
}, obj);
};
/**
* get document path
* -> www.pathfinder.com/pathfinder/ -> /pathfinder
@@ -2197,11 +2248,15 @@ define([
getNearBySystemData: getNearBySystemData,
getNearByCharacterData: getNearByCharacterData,
setDestination: setDestination,
convertDateToUTC: convertDateToUTC,
convertDateToString: convertDateToString,
getOpenDialogs: getOpenDialogs,
openIngameWindow: openIngameWindow,
formatPrice: formatPrice,
getLocalStorage: getLocalStorage,
clearSessionStorage: clearSessionStorage,
getBrowserTabId: getBrowserTabId,
getObjVal: getObjVal,
getDocumentPath: getDocumentPath,
redirect: redirect,
logout: logout

File diff suppressed because one or more lines are too long

View File

@@ -93,13 +93,26 @@ jQuery.fn.dragToSelect = function (conf) {
return this;
}
var parentOffset = parent.offset();
var parentDim = {
left: parentOffset.left,
top: parentOffset.top,
width: parent.width(),
height: parent.height()
};
var parentDim = {
left: 0,
top: 0,
width: 10,
height: 10
};
// set parent dimensions
// -> should be updated in case of left/right menu is open
var setParentDimensions = (parent) => {
var parentOffset = parent.offset();
parentDim = {
left: parentOffset.left,
top: parentOffset.top,
width: parent.width(),
height: parent.height()
};
}
setParentDimensions(parent);
// Current origin of select box
var selectBoxOrigin = {
@@ -343,6 +356,7 @@ jQuery.fn.dragToSelect = function (conf) {
// Do the right stuff then return this --------------------------------------------------------
selectBox.mousemove(function(e){
setParentDimensions(parent);
lastMousePosition.x = e.pageX;
lastMousePosition.y = e.pageY;
e.preventDefault();
@@ -353,7 +367,6 @@ jQuery.fn.dragToSelect = function (conf) {
e.which === 1 && // left mouse down
e.target === realParent[0] // prevent while dragging a system :)
) {
// Make sure user isn't clicking scrollbar (or disallow clicks far to the right actually)
if ((e.pageX + 20) > jQuery(document.body).width()) {
return;
@@ -366,6 +379,7 @@ jQuery.fn.dragToSelect = function (conf) {
e.preventDefault();
}).mousemove(function(e){
setParentDimensions(parent);
lastMousePosition.x = e.pageX;
lastMousePosition.y = e.pageY;
e.preventDefault();

File diff suppressed because one or more lines are too long

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