Merge pull request #721 from exodus4d/develop

v1.4.3
This commit is contained in:
Mark Friedrich
2018-12-02 13:12:37 +01:00
committed by GitHub
184 changed files with 2620 additions and 1399 deletions

View File

@@ -1,74 +1,123 @@
; Global Framework Config
[SERVER]
SERVER_NAME = PATHFINDER
SERVER_NAME = PATHFINDER
[globals]
; Default Verbosity level of the stack trace.
; Assign values between 0 to 3 for increasing verbosity levels. Check (environment.ini) config for overwriting
; (default: 0)
DEBUG = 0
; Verbosity level of error stack trace for errors
; This affects error logging and stack traces returned to clients on error.
; DEBUG level can be overwritten in environment.ini
; Syntax: 0 | 1 | 2 | 3
; Default: 0
DEBUG = 0
; If TRUE, the framework, after having logged stack trace and errors, stops execution
; -> (die without any status) when a non-fatal error is detected. (default: FALSE)
HALT = FALSE
; How to behave on 'non-fatal' errors
; If TRUE, the framework, after having logged stack trace and errors, stops execution
; (die without any status) when a non-fatal error is detected.
; Tip: You should not change this.
; Syntax: TRUE | FALSE
; Default: FALSE
HALT = FALSE
; Timezone to use. Sync program with eve server time. (default: UTC)
TZ = UTC
; Timezone to use
; Sync Pathfinder with EVE server time.
; Tip: You should not change this.
; Default: UTC
TZ = UTC
; Cache key prefix. Same for all cache values for this installation
; CLI (cronjob) scripts use it for cache manipulation
SEED = {{ md5(@SERVER.SERVER_NAME) }}
; Default language
; Overwrites HTTP Accept-Language request header.
; Used by setlocale() and affects number formatting.
; Syntax: String
; Default: en-US
LANGUAGE = en-US
; Cache backend. Can handle Redis, Memcache module, APC, WinCache, XCache and a filesystem-based cache.
; (default: folder=tmp/cache/)
CACHE = folder=tmp/cache/
;CACHE = redis=localhost:6379
; Cache key prefix
; Same for all cache values for this installation.
; CLI (cronjob) scripts use it for cache manipulation.
; Tip: You should not change this.
; Syntax String
; Default: {{ md5(@SERVER.SERVER_NAME) }}
SEED = {{ md5(@SERVER.SERVER_NAME) }}
; 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
; Cache backend
; Can handle Redis, Memcache module, APC, WinCache, XCache and a filesystem-based cache.
; Tip: Redis is recommended and gives the best performance.
; Syntax: folder=[DIR] | redis=[SERVER]
; Default: folder=tmp/cache/
; Value: folder=[DIR]
; - Cache data is stored on disc
; redis=[SERVER]
; - Cache data is stored in Redis (e.g. redis=localhost:6379)
CACHE = folder=tmp/cache/
; Cache backend used by PHPs Session handler.
; Tip1: Best performance and recommended configuration for Pathfinder is to configured Redis as PHPs default Session handler
; in your php.ini and set 'default' value here in order to use Redis (fastest)
; Tip2: If Redis is not available for you, leave this at 'mysql' (faster than PHPs default files bases Sessions)
; Syntax: mysql | default
; Default: mysql
; Value: mysql
; - Session data get stored in 'pathfinder'.'sessions' table (environment.ini → DB_PF_NAME).
; Table `sessions` is auto created if not exist.
; default
; - Session data get stored in PHPs default Session handler (php.ini → session.save_handler and session.save_path)
; PHPs default session.save_handler is `files` and each Session is written to disc (slowest)
SESSION_CACHE = mysql
; Callback functions ==============================================================================
ONERROR = Controller\Controller->showError
UNLOAD = Controller\Controller->unload
ONERROR = Controller\Controller->showError
UNLOAD = Controller\Controller->unload
; Path configurations =============================================================================
; relative to "BASE" dir
; All path configurations are relative to BASE dir and should NOT be changed
; Temporary folder for cache, filesystem locks, compiled F3 templates, etc. (default: tmp/)
TEMP = tmp/
; Temporary folder for cache
; Used for compiled templates.
; Syntax: [DIR]
; Default: tmp/
TEMP = tmp/
; Log file folder. (default: logs/)
LOGS = logs/
; Log file folder
; Syntax: [DIR]
; Default: logs/
LOGS = logs/
; UI/template folder. (default: public/)
UI = public/
; UI folder
; Where all the public assets (templates, images, styles, scripts) are located.
; Syntax: [DIR]
; Default: public/
UI = public/
; Autoloader for user-defined PHP classes that the framework will attempt to autoload at runtime. (default: app/main/)
AUTOLOAD = app/main/
; Autoload folder
; Where PHP attempts to autoload PHP classes at runtime.
; Syntax: [DIR]
; Default: app/main/
AUTOLOAD = app/main/
; Favicons. (default: favicon/)
FAVICON = favicon/
; Favicon folder
; Syntax: [DIR]
; Default: favicon/
FAVICON = favicon/
; Export folder (e.g. static table data). (default: export/)
EXPORT = export/
; Export folder
; Where DB dump files are located/created at.
; Syntax: [DIR]
; Default: export/
EXPORT = export/
; Default language (overwrites HTTP Accept-Language request header) used for "setlocale()" affects number formatting. (default: en-US)
LANGUAGE = en-US
; Custom *.ini file folder
; Can be used to overwrite default *.ini files and settings
; See: https://github.com/exodus4d/pathfinder/wiki/Configuration#custom-confpathfinderini
; Syntax: [DIR]
CONF.CUSTOM = conf/
CONF.DEFAULT = app/
; custom *.ini file folder, can be used to overwrite default *.ini files
CONF.CUSTOM = conf/
CONF.DEFAULT = app/
; load additional config files
; DO NOT load environment.ini, it is loaded automatically
; Load additional config files
; DO NOT load environment.ini, it is loaded automatically
[configs]
{{@CONF.DEFAULT}}routes.ini = true
{{@CONF.DEFAULT}}pathfinder.ini = true
{{@CONF.CUSTOM}}pathfinder.ini = true
{{@CONF.DEFAULT}}requirements.ini = true
{{@CONF.DEFAULT}}cron.ini = true
{{@CONF.DEFAULT}}routes.ini = true
{{@CONF.DEFAULT}}pathfinder.ini = true
{{@CONF.CUSTOM}}pathfinder.ini = true
{{@CONF.DEFAULT}}requirements.ini = true
{{@CONF.DEFAULT}}cron.ini = true

View File

@@ -30,6 +30,7 @@ DB_UNIVERSE_PASS =
CCP_SSO_URL = https://sisilogin.testeveonline.com
CCP_SSO_CLIENT_ID =
CCP_SSO_SECRET_KEY =
CCP_SSO_DOWNTIME = 11:00
; CCP ESI API
CCP_ESI_URL = https://esi.tech.ccp.is
@@ -82,6 +83,7 @@ DB_CCP_PASS =
CCP_SSO_URL = https://login.eveonline.com
CCP_SSO_CLIENT_ID =
CCP_SSO_SECRET_KEY =
CCP_SSO_DOWNTIME = 11:00
; CCP ESI API
CCP_ESI_URL = https://esi.tech.ccp.is

View File

@@ -20,7 +20,6 @@ class AccessController extends Controller {
* @param $params
* @return bool
* @throws \Exception
* @throws \Exception\PathfinderException
* @throws \ZMQSocketException
*/
function beforeroute(\Base $f3, $params): bool {
@@ -29,15 +28,7 @@ class AccessController extends Controller {
// requires a valid logged in user!
if( !$this->isLoggedIn($f3) ){
// no character found or login timer expired
$this->logoutCharacter();
if($f3->get('AJAX')){
// unauthorized request
$f3->status(403);
}else{
// redirect to landing page
$f3->reroute(['login']);
}
$this->logoutCharacter($f3);
// skip route handler and afterroute()
$return = false;
}
@@ -51,7 +42,6 @@ class AccessController extends Controller {
* @param \Base $f3
* @return bool
* @throws \Exception
* @throws \Exception\PathfinderException
*/
protected function isLoggedIn(\Base $f3): bool {
$loginCheck = false;
@@ -71,7 +61,6 @@ class AccessController extends Controller {
* @param \Base $f3
* @param Model\CharacterModel $character
* @return bool
* @throws \Exception\PathfinderException
*/
private function checkLogTimer(\Base $f3, Model\CharacterModel $character){
$loginCheck = false;
@@ -104,7 +93,6 @@ class AccessController extends Controller {
* @param Model\MapModel $map
* @return int (number of active connections for this map)
* @throws \Exception
* @throws \Exception\PathfinderException
* @throws \ZMQSocketException
*/
protected function broadcastMapData(Model\MapModel $map){
@@ -117,7 +105,6 @@ class AccessController extends Controller {
* @param Model\MapModel $map
* @return array
* @throws \Exception
* @throws \Exception\PathfinderException
*/
protected function getFormattedMapData(Model\MapModel $map){
$mapData = $map->getData();

View File

@@ -37,7 +37,6 @@ class Admin extends Controller{
* @param $params
* @return bool
* @throws \Exception
* @throws \Exception\PathfinderException
*/
function beforeroute(\Base $f3, $params): bool {
$return = parent::beforeroute($f3, $params);
@@ -50,7 +49,7 @@ class Admin extends Controller{
$this->dispatch($f3, $params, $character);
}
$f3->set('tplAuthType', $f3->alias( 'sso', ['action' => 'requestAdminAuthorization']));
$f3->set('tplAuthType', $f3->get('BASE') . $f3->alias( 'sso', ['action' => 'requestAdminAuthorization']));
// page title
$f3->set('tplPageTitle', 'Admin | ' . Config::getPathfinderData('name'));
@@ -67,7 +66,6 @@ class Admin extends Controller{
/**
* event handler after routing
* @param \Base $f3
* @throws \Exception\PathfinderException
*/
public function afterroute(\Base $f3) {
// js view (file)
@@ -123,7 +121,6 @@ class Admin extends Controller{
* @param $params
* @param null $character
* @throws \Exception
* @throws \Exception\PathfinderException
*/
public function dispatch(\Base $f3, $params, $character = null){
if($character instanceof CharacterModel){
@@ -232,7 +229,6 @@ class Admin extends Controller{
* @param CharacterModel $character
* @param int $kickCharacterId
* @param int $minutes
* @throws \Exception\PathfinderException
*/
protected function kickCharacter(CharacterModel $character, $kickCharacterId, $minutes){
$kickOptions = self::KICK_OPTIONS;
@@ -262,7 +258,6 @@ class Admin extends Controller{
* @param CharacterModel $character
* @param int $banCharacterId
* @param int $value
* @throws \Exception\PathfinderException
*/
protected function banCharacter(CharacterModel $character, $banCharacterId, $value){
$banCharacters = $this->filterValidCharacters($character, $banCharacterId);
@@ -309,7 +304,6 @@ class Admin extends Controller{
* @param CharacterModel $character
* @param int $mapId
* @param int $value
* @throws \Exception\PathfinderException
*/
protected function activateMap(CharacterModel $character, int $mapId, int $value){
$maps = $this->filterValidMaps($character, $mapId);
@@ -322,7 +316,6 @@ class Admin extends Controller{
/**
* @param CharacterModel $character
* @param int $mapId
* @throws \Exception\PathfinderException
*/
protected function deleteMap(CharacterModel $character, int $mapId){
$maps = $this->filterValidMaps($character, $mapId);
@@ -336,7 +329,6 @@ class Admin extends Controller{
* @param CharacterModel $character
* @param int $mapId
* @return \DB\CortexCollection[]|MapModel[]
* @throws \Exception\PathfinderException
*/
protected function filterValidMaps(CharacterModel $character, int $mapId) {
$maps = [];
@@ -355,7 +347,6 @@ class Admin extends Controller{
* get log file for "admin" logs
* @param string $type
* @return \Log
* @throws \Exception\PathfinderException
*/
static function getLogger($type = 'ADMIN'){
return parent::getLogger('ADMIN');
@@ -406,7 +397,6 @@ class Admin extends Controller{
* init /maps page data
* @param \Base $f3
* @param CharacterModel $character
* @throws \Exception\PathfinderException
*/
protected function initMaps(\Base $f3, CharacterModel $character){
$data = (object) [];

View File

@@ -29,7 +29,6 @@ class GitHub extends Controller\Controller {
/**
* get HTTP request options for API (curl) request
* @return array
* @throws \Exception\PathfinderException
*/
protected function getRequestReleaseOptions() : array {
$options = $this->getBaseRequestOptions();
@@ -41,7 +40,6 @@ class GitHub extends Controller\Controller {
* get HTTP request options for API (curl) request
* @param string $text
* @return array
* @throws \Exception\PathfinderException
*/
protected function getRequestMarkdownOptions(string $text) : array {
$params = [
@@ -59,7 +57,6 @@ class GitHub extends Controller\Controller {
/**
* get release information from GitHub
* @param \Base $f3
* @throws \Exception\PathfinderException
*/
public function releases(\Base $f3){
$cacheKey = 'CACHE_GITHUB_RELEASES';

View File

@@ -7,6 +7,7 @@
*/
namespace Controller\Api;
use Controller;
use data\file\FileHandler;
use lib\Config;
@@ -50,7 +51,6 @@ class Map extends Controller\AccessController {
* Get all required static config data for program initialization
* @param \Base $f3
* @throws Exception
* @throws Exception\PathfinderException
*/
public function initData(\Base $f3){
// expire time in seconds
@@ -221,6 +221,7 @@ class Map extends Controller\AccessController {
// universe category data ---------------------------------------------------------------------------------
$return->universeCategories = [
6 => Model\Universe\BasicUniverseModel::getNew('CategoryModel')->getById(6)->getData(['mass']),
65 => Model\Universe\BasicUniverseModel::getNew('CategoryModel')->getById(65)->getData()
];
@@ -572,11 +573,7 @@ class Map extends Controller\AccessController {
$return->mapData = $map->getData();
}catch(Exception\ValidationException $e){
$validationError = (object) [];
$validationError->type = 'error';
$validationError->field = $e->getField();
$validationError->message = $e->getMessage();
$return->error[] = $validationError;
$return->error[] = $e->getError();
}
}else{
// map access denied
@@ -634,7 +631,6 @@ class Map extends Controller\AccessController {
* -> if characters with map access found -> broadcast mapData to them
* @param Model\MapModel $map
* @throws Exception
* @throws Exception\PathfinderException
* @throws \ZMQSocketException
*/
protected function broadcastMapAccess(Model\MapModel $map){
@@ -708,7 +704,6 @@ class Map extends Controller\AccessController {
* -> function is called continuously (trigger) by any active client
* @param \Base $f3
* @throws Exception
* @throws Exception\PathfinderException
*/
public function updateData(\Base $f3){
$postData = (array)$f3->get('POST');
@@ -848,7 +843,6 @@ class Map extends Controller\AccessController {
* @param Model\MapModel[] $mapModels
* @return array
* @throws Exception
* @throws Exception\PathfinderException
*/
protected function getFormattedMapsData($mapModels){
$mapData = [];
@@ -864,7 +858,6 @@ class Map extends Controller\AccessController {
* -> function is called continuously by any active client
* @param \Base $f3
* @throws Exception
* @throws Exception\PathfinderException
*/
public function updateUserData(\Base $f3){
$postData = (array)$f3->get('POST');
@@ -1196,7 +1189,6 @@ class Map extends Controller\AccessController {
* get map log data
* @param \Base $f3
* @throws Exception
* @throws Exception\PathfinderException
*/
public function getLogData(\Base $f3){
$postData = (array)$f3->get('POST');

View File

@@ -0,0 +1,45 @@
<?php
/**
* Created by PhpStorm.
* User: exodu
* Date: 13.10.2018
* Time: 16:14
*/
namespace Controller\Api\Rest;
use Controller;
abstract class AbstractRestController extends Controller\AccessController {
/**
* get send data from request
* API requests require "Content-Type: application/json"
* -> $_POST does not include request data -> request BODY might contain JSON
* @param \Base $f3
* @return array
*/
protected function getRequestData(\Base $f3) : array {
$data = [];
if( !empty($body = $f3->get('BODY')) ){
$bodyDecode = json_decode($body, true);
if(($jsonError = json_last_error()) === JSON_ERROR_NONE){
$data = $bodyDecode;
}else{
$f3->set('HALT', true);
$f3->error(400, 'Request data: ' . json_last_error_msg());
}
}
return $data;
}
/**
* render API response to client
* @param $output
*/
protected function out($output){
echo json_encode($output);
}
}

View File

@@ -1,50 +1,40 @@
<?php
/**
* Created by PhpStorm.
* User: exodus4d
* Date: 01.03.15
* Time: 18:37
* User: Exodus 4D
* Date: 10.11.2018
* Time: 12:10
*/
namespace Controller\Api;
use Controller;
namespace Controller\Api\Rest;
use Model;
class Connection extends Controller\AccessController {
class Connection extends AbstractRestController {
/**
* save a new connection or updates an existing (drag/drop) between two systems
* if a connection is changed (drag&drop) to another system. -> this function is called for update
* @param \Base $f3
* @throws \Exception
* @throws \ZMQSocketException
*/
public function save(\Base $f3){
$postData = (array)$f3->get('POST');
$return = (object) [];
$return->error = [];
$return->connectionData = (object) [];
if(
isset($postData['connectionData']) &&
isset($postData['mapData'])
){
$mapData = (array)$postData['mapData'];
$connectionData = (array)$postData['connectionData'];
public function put(\Base $f3){
$requestData = $this->getRequestData($f3);
$connectionData = [];
if($mapId = (int)$requestData['mapId']){
$activeCharacter = $this->getCharacter();
// get map model and check map access
/**
* @var Model\MapModel $map
*/
$map = Model\BasicModel::getNew('MapModel');
$map->getById( (int)$mapData['id'] );
$map->getById($mapId);
if( $map->hasAccess($activeCharacter) ){
$source = $map->getSystemById( $connectionData['source'] );
$target = $map->getSystemById( $connectionData['target'] );
if($map->hasAccess($activeCharacter)){
$source = $map->getSystemById((int)$requestData['source']);
$target = $map->getSystemById((int)$requestData['target']);
if(
!is_null($source) &&
@@ -54,7 +44,7 @@ class Connection extends Controller\AccessController {
* @var $connection Model\ConnectionModel
*/
$connection = Model\BasicModel::getNew('ConnectionModel');
$connection->getById( (int)$connectionData['id'] );
$connection->getById((int)$requestData['id']);
$connection->mapId = $map;
$connection->source = $source;
@@ -65,30 +55,30 @@ class Connection extends Controller\AccessController {
$connection->setDefaultTypeData();
if($connection->save($activeCharacter)){
$return->connectionData = $connection->getData();
$connectionData = $connection->getData();
// broadcast map changes
$this->broadcastMapData($connection->mapId);
}else{
$return->error = $connection->getErrors();
}
}
}
}
echo json_encode($return);
$this->out($connectionData);
}
/**
* delete connection
* @param \Base $f3
* @param $params
* @throws \Exception
* @throws \ZMQSocketException
*/
public function delete(\Base $f3){
$mapId = (int)$f3->get('POST.mapId');
$connectionIds = (array)$f3->get('POST.connectionIds');
public function delete(\Base $f3, $params){
$requestData = $this->getRequestData($f3);
$connectionIds = array_map('intval', explode(',', (string)$params['id']));
$deletedConnectionIds = [];
if($mapId){
if($mapId = (int)$requestData['mapId']){
$activeCharacter = $this->getCharacter();
/**
@@ -97,22 +87,23 @@ class Connection extends Controller\AccessController {
$map = Model\BasicModel::getNew('MapModel');
$map->getById($mapId);
if( $map->hasAccess($activeCharacter) ){
if($map->hasAccess($activeCharacter)){
foreach($connectionIds as $connectionId){
if( $connection = $map->getConnectionById($connectionId) ){
if($connection = $map->getConnectionById($connectionId)){
$connection->delete( $activeCharacter );
$connection->reset();
$deletedConnectionIds[] = $connectionId;
}
}
// broadcast map changes
$this->broadcastMapData($map);
if(count($deletedConnectionIds)){
$this->broadcastMapData($map);
}
}
}
echo json_encode([]);
$this->out($deletedConnectionIds);
}
}
}

View File

@@ -0,0 +1,111 @@
<?php
/**
* Created by PhpStorm.
* User: exodu
* Date: 13.10.2018
* Time: 15:28
*/
namespace Controller\Api\Rest;
use Model;
class Log extends AbstractRestController {
/**
* put (insert) log data
* @param \Base $f3
* @throws \Exception
*/
public function put(\Base $f3){
$requestData = $this->getRequestData($f3);
$connectionData = [];
if($connectionId = (int)$requestData['connectionId']){
$activeCharacter = $this->getCharacter();
/**
* @var Model\ConnectionModel $connection
*/
$connection = Model\BasicModel::getNew('ConnectionModel');
$connection->getById($connectionId);
if($connection->hasAccess($activeCharacter)){
$log = $connection->getNewLog();
$log->setData($requestData);
$log->record = false; // log not recorded by ESI
$log->save();
$connectionData[] = $log->getConnection()->getData(true, true);
}
}
$this->out($connectionData);
}
/**
* delete (deactivate) log data
* @param \Base $f3
* @param $params
* @throws \Exception
*/
public function delete(\Base $f3, $params){
$logId = (int)$params['id'];
$connectionData = [];
if($log = $this->update($logId, ['active' => false])){
$connectionData[] = $log->getConnection()->getData(true, true);
}
$this->out($connectionData);
}
/**
* update log data
* @param \Base $f3
* @param $params
* @throws \Exception
*/
public function patch(\Base $f3, $params){
$logId = (int)$params['id'];
$requestData = $this->getRequestData($f3);
$connectionData = [];
if($log = $this->update($logId, $requestData)){
$connectionData[] = $log->getConnection()->getData(true, true);
}
$this->out($connectionData);
}
// ----------------------------------------------------------------------------------------------------------------
/**
* update existing connectionLog with new data
* @param int $logId
* @param array $logData
* @return bool|Model\ConnectionLogModel
* @throws \Exception
*/
private function update(int $logId, array $logData){
$log = false;
if($logId){
$activeCharacter = $this->getCharacter();
/**
* @var Model\ConnectionLogModel $log
*/
$log = Model\BasicModel::getNew('ConnectionLogModel');
$log->getById($logId, 0, false);
if($log->hasAccess($activeCharacter)){
$log->setData($logData);
if(isset($logData['active'])){
$log->setActive((bool)$logData['active']);
}
$log->save();
}
}
return $log;
}
}

View File

@@ -0,0 +1,190 @@
<?php
/**
* Created by PhpStorm.
* User: Exodus 4D
* Date: 09.11.2018
* Time: 12:34
*/
namespace Controller\Api\Rest;
use Model;
class System extends AbstractRestController {
/**
* put (insert) system
* @param \Base $f3
* @throws \Exception
*/
public function put(\Base $f3){
$requestData = $this->getRequestData($f3);
$systemData = [];
if($mapId = (int)$requestData['mapId']){
$activeCharacter = $this->getCharacter();
/**
* @var $map Model\MapModel
*/
$map = Model\BasicModel::getNew('MapModel');
$map->getById($mapId);
if($map->hasAccess($activeCharacter)){
$system = $map->getNewSystem($requestData['systemId']);
$systemData = $this->update($system, $requestData)->getData();
}
}
$this->out($systemData);
}
/**
* update existing system
* @param \Base $f3
* @param $params
* @throws \Exception
*/
public function patch(\Base $f3, $params){
$requestData = $this->getRequestData($f3);
$systemData = [];
if($systemId = (int)$params['id']){
$activeCharacter = $this->getCharacter();
/**
* @var $system Model\SystemModel
*/
$system = Model\BasicModel::getNew('SystemModel');
$system->getById($systemId);
if($system->hasAccess($activeCharacter)){
$systemData = $this->update($system, $requestData)->getData();
}
}
$this->out($systemData);
}
/**
* @param \Base $f3
* @param $params
* @throws \ZMQSocketException
* @throws \Exception
*/
public function delete(\Base $f3, $params){
$requestData = $this->getRequestData($f3);
$systemIds = array_map('intval', explode(',', (string)$params['id']));
$deletedSystemIds = [];
if($mapId = (int)$requestData['mapId']){
$activeCharacter = $this->getCharacter();
/**
* @var Model\MapModel $map
*/
$map = Model\BasicModel::getNew('MapModel');
$map->getById($mapId);
if($map->hasAccess($activeCharacter)){
$newSystemModel = Model\BasicModel::getNew('SystemModel');
foreach($systemIds as $systemId){
if($system = $map->getSystemById($systemId)){
// check whether system should be deleted OR set "inactive"
if($this->checkDeleteMode($map, $system)){
// delete log
// -> first set updatedCharacterId -> required for activity log
$system->updatedCharacterId = $activeCharacter;
$system->update();
// ... now get fresh object and delete..
$newSystemModel->getById($system->_id, 0);
$newSystemModel->erase();
$newSystemModel->reset();
}else{
// keep data -> set "inactive"
$system->setActive(false);
$system->save($activeCharacter);
}
$system->reset();
$deletedSystemIds[] = $systemId;
}
}
// broadcast map changes
if(count($deletedSystemIds)){
$this->broadcastMapData($map);
}
}
}
$this->out($deletedSystemIds);
}
// ----------------------------------------------------------------------------------------------------------------
/**
* update system with new data
* @param Model\SystemModel $system
* @param array $systemData
* @return Model\SystemModel
* @throws \ZMQSocketException
* @throws \Exception
*/
private function update(Model\SystemModel $system, array $systemData) : Model\SystemModel {
$activeCharacter = $this->getCharacter();
// statusId === 0 is 'auto' status -> keep current status
// -> relevant systems that already have a status (inactive systems)
if( (int)$systemData['statusId'] <= 0 ){
unset($systemData['statusId']);
}
if( !$system->dry() ){
// activate system (e.g. was inactive))
$system->setActive(true);
}
$system->setData($systemData);
$system->save($activeCharacter);
// get data from "fresh" model (e.g. some relational data has changed: "statusId")
/**
* @var $newSystem Model\SystemModel
*/
$newSystem = Model\BasicModel::getNew('SystemModel');
$newSystem->getById($system->_id, 0);
$newSystem->clearCacheData();
// broadcast map changes
$this->broadcastMapData($newSystem->mapId);
return $newSystem;
}
/**
* checks whether a system should be "deleted" or set "inactive" (keep some data)
* @param Model\MapModel $map
* @param Model\SystemModel $system
* @return bool
*/
private function checkDeleteMode(Model\MapModel $map, Model\SystemModel $system) : bool {
$delete = true;
if( !empty($system->description) ){
// never delete systems with custom description set!
$delete = false;
}elseif(
$map->persistentAliases &&
!empty($system->alias) &&
($system->alias != $system->name)
){
// map setting "persistentAliases" is active (default) AND
// alias is set and != name
$delete = false;
}
return $delete;
}
}

View File

@@ -414,7 +414,7 @@ class Route extends Controller\AccessController {
* @param array $mapIds
* @param array $filterData
* @return array
* @throws \Exception\PathfinderException
* @throws \Exception
*/
public function searchRoute(int $systemFromId, int $systemToId, $searchDepth = 0, array $mapIds = [], array $filterData = []) : array {
// search root by ESI API
@@ -439,7 +439,6 @@ class Route extends Controller\AccessController {
* @param array $filterData
* @return array
* @throws \Exception
* @throws \Exception\PathfinderException
*/
private function searchRouteCustom(int $systemFromId, int $systemToId, $searchDepth = 0, array $mapIds = [], array $filterData = []) : array {
// reset all previous set jump data
@@ -519,7 +518,6 @@ class Route extends Controller\AccessController {
* @param array $filterData
* @return array
* @throws \Exception
* @throws \Exception\PathfinderException
*/
private function searchRouteESI(int $systemFromId, int $systemToId, int $searchDepth = 0, array $mapIds = [], array $filterData = []) : array {
// reset all previous set jump data
@@ -645,7 +643,6 @@ class Route extends Controller\AccessController {
* search multiple route between two systems
* @param \Base $f3
* @throws \Exception
* @throws \Exception\PathfinderException
*/
public function search($f3){
$requestData = (array)$f3->get('POST');

View File

@@ -135,8 +135,13 @@ class Signature extends Controller\AccessController {
$system->saveSignature($signature, $activeCharacter);
$updatedSignatureIds[] = $signature->_id;
$return->signatures[] = $signature->getData();
// TODO figure out why $system->connectionId is NULL after change/save
//-> workaround: get data from "new" $signature model
$signatureNew = Model\BasicModel::getNew('SystemSignatureModel');
$signatureNew->getById($signature->_id);
$updatedSignatureIds[] = $signatureNew->_id;
$return->signatures[] = $signatureNew->getData();
$signature->reset();
}

View File

@@ -123,7 +123,6 @@ class Statistic extends Controller\AccessController {
* @param int $yearEnd
* @param int $weekEnd
* @return array
* @throws \Exception\PathfinderException
*/
protected function queryStatistic( CharacterModel $character, $typeId, $yearStart, $weekStart, $yearEnd, $weekEnd){
$data = [];

View File

@@ -10,7 +10,6 @@ namespace Controller\Api;
use Controller;
use Model;
use Exception;
class System extends Controller\AccessController {
@@ -26,94 +25,6 @@ class System extends Controller\AccessController {
return sprintf(self::CACHE_KEY_GRAPH, 'SYSTEM_' . $systemId);
}
/**
* save a new system to a a map
* @param \Base $f3
* @throws \Exception
*/
public function save(\Base $f3){
$postData = (array)$f3->get('POST');
$return = (object) [];
$return->error = [];
$return->systemData = (object) [];
if(
isset($postData['systemData']) &&
isset($postData['mapData'])
){
$activeCharacter = $this->getCharacter();
$systemData = (array)$postData['systemData'];
$mapData = (array)$postData['mapData'];
$systemModel = null;
if( (int)$systemData['statusId'] <= 0 ){
unset($systemData['statusId']);
}
if( isset($systemData['id']) ){
// update existing system (e.g. set description) ------------------------------------------------------
/**
* @var $system Model\SystemModel
*/
$system = Model\BasicModel::getNew('SystemModel');
$system->getById($systemData['id']);
if(
!$system->dry() &&
$system->hasAccess($activeCharacter)
){
// system model found
// activate system (e.g. was inactive))
$system->setActive(true);
$systemModel = $system;
}
}elseif( isset($mapData['id']) ){
// save NEW system ------------------------------------------------------------------------------------
/**
* @var $map Model\MapModel
*/
$map = Model\BasicModel::getNew('MapModel');
$map->getById($mapData['id']);
if($map->hasAccess($activeCharacter)){
$systemModel = $map->getNewSystem($systemData['systemId']);
}
}
if( !is_null($systemModel) ){
try{
// set/update system custom data
$systemModel->copyfrom($systemData, ['statusId', 'locked', 'rallyUpdated', 'position', 'description']);
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();
}
}catch(Exception\ValidationException $e){
$validationError = (object) [];
$validationError->type = 'error';
$validationError->field = $e->getField();
$validationError->message = $e->getMessage();
$return->error[] = $validationError;
}
}
}
echo json_encode($return);
}
/**
* get system log data from CCP API import
* system Kills, Jumps,....
@@ -246,7 +157,6 @@ class System extends Controller\AccessController {
* send Rally Point poke
* @param \Base $f3
* @throws \Exception
* @throws \Exception\PathfinderException
*/
public function pokeRally(\Base $f3){
$rallyData = (array)$f3->get('POST');
@@ -301,87 +211,5 @@ class System extends Controller\AccessController {
echo json_encode($return);
}
/**
* delete systems and all its connections from map
* -> set "active" flag
* @param \Base $f3
* @throws \Exception
*/
public function delete(\Base $f3){
$mapId = (int)$f3->get('POST.mapId');
$systemIds = array_map('intval', (array)$f3->get('POST.systemIds'));
$return = (object) [];
$return->deletedSystemIds = [];
if($mapId){
$activeCharacter = $this->getCharacter();
/**
* @var Model\MapModel $map
*/
$map = Model\BasicModel::getNew('MapModel');
$map->getById($mapId);
if($map->hasAccess($activeCharacter)){
$newSystemModel = Model\BasicModel::getNew('SystemModel');
foreach($systemIds as $systemId){
if( $system = $map->getSystemById($systemId) ){
// check whether system should be deleted OR set "inactive"
if( $this->checkDeleteMode($map, $system) ){
// delete log
// -> first set updatedCharacterId -> required for activity log
$system->updatedCharacterId = $activeCharacter;
$system->update();
// ... now get fresh object and delete..
$newSystemModel->getById( $system->id, 0);
$newSystemModel->erase();
$newSystemModel->reset();
}else{
// keep data -> set "inactive"
$system->setActive(false);
$system->save($activeCharacter);
}
$system->reset();
$return->deletedSystemIds[] = $systemId;
}
}
// broadcast map changes
if(count($return->deletedSystemIds)){
$this->broadcastMapData($map);
}
}
}
echo json_encode($return);
}
/**
* checks whether a system should be "deleted" or set "inactive" (keep some data)
* @param Model\MapModel $map
* @param Model\SystemModel $system
* @return bool
*/
protected function checkDeleteMode(Model\MapModel $map, Model\SystemModel $system){
$delete = true;
if( !empty($system->description) ){
// never delete systems with custom description set!
$delete = false;
}elseif(
$map->persistentAliases &&
!empty($system->alias) &&
($system->alias != $system->name)
){
// map setting "persistentAliases" is active (default) AND
// alias is set and != name
$delete = false;
}
return $delete;
}
}

View File

@@ -109,7 +109,6 @@ class User extends Controller\Controller{
* -> return character data (if valid)
* @param \Base $f3
* @throws Exception
* @throws Exception\PathfinderException
*/
public function getCookieCharacter(\Base $f3){
$data = $f3->get('POST');
@@ -201,15 +200,10 @@ class User extends Controller\Controller{
/**
* log the current user out + clear character system log data
* @param \Base $f3
* @throws Exception
* @throws \ZMQSocketException
*/
public function logout(\Base $f3){
$this->logoutCharacter(false, true, true, true);
$return = (object) [];
$return->reroute = rtrim(self::getEnvironmentData('URL'), '/') . $f3->alias('login');
echo json_encode($return);
$this->logoutCharacter($f3, false, true, true, true);
}
/**
@@ -345,17 +339,9 @@ class User extends Controller\Controller{
}
}catch(Exception\ValidationException $e){
$validationError = (object) [];
$validationError->type = 'error';
$validationError->field = $e->getField();
$validationError->message = $e->getMessage();
$return->error[] = $validationError;
$return->error[] = $e->getError();
}catch(Exception\RegistrationException $e){
$registrationError = (object) [];
$registrationError->type = 'error';
$registrationError->field = $e->getField();
$registrationError->message = $e->getMessage();
$return->error[] = $registrationError;
$return->error[] = $e->getError();
}
// return new/updated user data
@@ -394,10 +380,8 @@ class User extends Controller\Controller{
sprintf(self::LOG_DELETE_ACCOUNT, $user->id, $user->name)
);
$this->logoutCharacter(true, true, true, true);
$this->logoutCharacter($f3, true, true, true, true);
$user->erase();
$return->reroute = rtrim(self::getEnvironmentData('URL'), '/') . $f3->alias('login');
}
}else{
// captcha not valid -> return error

View File

@@ -29,7 +29,7 @@ class AppController extends Controller {
if($return = parent::beforeroute($f3, $params)){
// href for SSO Auth
$f3->set('tplAuthType', $f3->alias( 'sso', ['action' => 'requestAuthorization'] ));
$f3->set('tplAuthType', $f3->get('BASE') . $f3->alias( 'sso', ['action' => 'requestAuthorization'] ));
// characters from cookies
$f3->set('cookieCharacters', $this->getCookieByName(self::COOKIE_PREFIX_CHARACTER, true));

View File

@@ -52,7 +52,6 @@ class Sso extends Api\User{
* redirect user to CCP SSO page and request authorization
* -> cf. Controller->getCookieCharacters() ( equivalent cookie based login)
* @param \Base $f3
* @throws \Exception\PathfinderException
*/
public function requestAdminAuthorization($f3){
// store browser tabId to be "targeted" after login
@@ -67,7 +66,6 @@ class Sso extends Api\User{
* -> cf. Controller->getCookieCharacters() ( equivalent cookie based login)
* @param \Base $f3
* @throws \Exception
* @throws \Exception\PathfinderException
*/
public function requestAuthorization($f3){
$params = $f3->get('GET');
@@ -133,7 +131,6 @@ class Sso extends Api\User{
* @param \Base $f3
* @param array $scopes
* @param string $rootAlias
* @throws \Exception\PathfinderException
*/
private function rerouteAuthorization(\Base $f3, $scopes = [], $rootAlias = 'login'){
if( !empty( Controller\Controller::getEnvironmentData('CCP_SSO_CLIENT_ID') ) ){
@@ -166,7 +163,6 @@ class Sso extends Api\User{
* -> see requestAuthorization()
* @param \Base $f3
* @throws \Exception
* @throws \Exception\PathfinderException
*/
public function callbackAuthorization($f3){
$getParams = (array)$f3->get('GET');
@@ -307,7 +303,6 @@ class Sso extends Api\User{
* login by cookie name
* @param \Base $f3
* @throws \Exception
* @throws \Exception\PathfinderException
*/
public function login(\Base $f3){
$data = (array)$f3->get('GET');
@@ -345,7 +340,6 @@ class Sso extends Api\User{
* -> else try to refresh auth and get fresh "access_token"
* @param bool $authCode
* @return null|\stdClass
* @throws \Exception\PathfinderException
*/
public function getSsoAccessData($authCode){
$accessData = null;
@@ -365,7 +359,6 @@ class Sso extends Api\User{
* verify authorization code, and get an "access_token" data
* @param $authCode
* @return \stdClass
* @throws \Exception\PathfinderException
*/
protected function verifyAuthorizationCode($authCode){
$requestParams = [
@@ -381,7 +374,6 @@ class Sso extends Api\User{
* -> if "access_token" is expired, this function gets a fresh one
* @param $refreshToken
* @return \stdClass
* @throws \Exception\PathfinderException
*/
public function refreshAccessToken($refreshToken){
$requestParams = [
@@ -398,7 +390,6 @@ class Sso extends Api\User{
* OR by providing a valid "refresh_token"
* @param $requestParams
* @return \stdClass
* @throws \Exception\PathfinderException
*/
protected function requestAccessData($requestParams){
$verifyAuthCodeUrl = self::getVerifyAuthorizationCodeEndpoint();
@@ -463,7 +454,6 @@ class Sso extends Api\User{
* -> if more character information is required, use ESI "characters" endpoints request instead
* @param $accessToken
* @return mixed|null
* @throws \Exception\PathfinderException
*/
public function verifyCharacterData($accessToken){
$verifyUserUrl = self::getVerifyUserEndpoint();
@@ -586,7 +576,6 @@ class Sso extends Api\User{
* get CCP SSO url from configuration file
* -> throw error if url is broken/missing
* @return string
* @throws \Exception\PathfinderException
*/
static function getSsoUrlRoot(){
$url = '';
@@ -616,7 +605,6 @@ class Sso extends Api\User{
/**
* get logger for SSO logging
* @return \Log
* @throws \Exception\PathfinderException
*/
static function getSSOLogger(){
return parent::getLogger('SSO');

View File

@@ -9,6 +9,7 @@
namespace Controller;
use Controller\Api as Api;
use Exception\PathfinderException;
use lib\Config;
use lib\Resource;
use lib\Monolog;
@@ -66,7 +67,6 @@ class Controller {
* @param \Base $f3
* @param $params
* @return bool
* @throws \Exception\PathfinderException
*/
function beforeroute(\Base $f3, $params): bool {
// initiate DB connection
@@ -103,10 +103,10 @@ class Controller {
header($resource->buildHeader(), false);
}
if($this->getTemplate()){
if($file = $this->getTemplate()){
// Ajax calls don´t need a page render..
// this happens on client side
echo \Template::instance()->render( $this->getTemplate() );
echo \Template::instance()->render($file);
}
}
@@ -131,7 +131,6 @@ class Controller {
* @param $session
* @param $sid
* @return bool
* @throws \Exception\PathfinderException
*/
$onSuspect = function($session, $sid){
self::getLogger('SESSION_SUSPECT')->write( sprintf(
@@ -160,7 +159,6 @@ class Controller {
/**
* init new Resource handler
* @param \Base $f3
* @throws \Exception\PathfinderException
*/
protected function initResource(\Base $f3){
$resource = Resource::instance();
@@ -228,7 +226,6 @@ class Controller {
* -> store validation data in DB
* @param Model\CharacterModel $character
* @throws \Exception
* @throws \Exception\PathfinderException
*/
protected function setLoginCookie(Model\CharacterModel $character){
if( $this->getCookieState() ){
@@ -286,7 +283,6 @@ class Controller {
* @param bool $checkAuthorization
* @return Model\CharacterModel[]
* @throws \Exception
* @throws \Exception\PathfinderException
*/
protected function getCookieCharacters($cookieData = [], $checkAuthorization = true){
$characters = [];
@@ -489,15 +485,16 @@ class Controller {
/**
* log out current character or all active characters (multiple browser tabs)
* -> send response data to client
* @param \Base $f3
* @param bool $all
* @param bool $deleteSession
* @param bool $deleteLog
* @param bool $deleteCookie
* @throws \Exception
* @throws \ZMQSocketException
*/
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);
protected function logoutCharacter(\Base $f3, bool $all = false, bool $deleteSession = true, bool $deleteLog = true, bool $deleteCookie = false){
$sessionCharacterData = (array)$f3->get(Api\User::SESSION_KEY_CHARACTERS);
if($sessionCharacterData){
$activeCharacterId = ($activeCharacter = $this->getCharacter()) ? $activeCharacter->_id : 0;
@@ -523,6 +520,20 @@ class Controller {
(new Socket( Config::getSocketUri() ))->sendData('characterLogout', $characterIds);
}
}
if($f3->get('AJAX')){
$status = 403;
$f3->status($status);
$return = (object) [];
$return->reroute = rtrim(self::getEnvironmentData('URL'), '/') . $f3->alias('login');
$return->error[] = $this->getErrorObject($status, Config::getMessageFromHTTPStatus($status));
echo json_encode($return);
}else{
// redirect to landing page
$f3->reroute(['login']);
}
}
/**
@@ -627,7 +638,6 @@ class Controller {
/**
* get a custom userAgent string for API calls
* @return string
* @throws \Exception\PathfinderException
*/
protected function getUserAgent(){
$userAgent = '';
@@ -663,18 +673,26 @@ class Controller {
* -> on HTTP request -> render error page
* @param \Base $f3
* @return bool
* @throws \Exception\PathfinderException
*/
public function showError(\Base $f3){
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
);
$errorData = $f3->get('ERROR');
$exception = $f3->get('EXCEPTION');
if($exception instanceof PathfinderException){
// ... handle Pathfinder exceptions (e.g. validation Exceptions,..)
$error = $exception->getError();
}else{
// ... handle error $f3->error() calls
$error = $this->getErrorObject(
$errorData['code'],
$errorData['status'],
$errorData['text'],
$f3->get('DEBUG') >= 1 ? $errorData['trace'] : null
);
}
// check if error is a PDO Exception ----------------------------------------------------------------------
if(strpos(strtolower( $f3->get('ERROR.text') ), 'duplicate') !== false){
@@ -725,24 +743,6 @@ class Controller {
* @return bool
*/
public function unload(\Base $f3){
// track some 4xx Client side errors
// 5xx errors are handled in "ONERROR" callback
$status = http_response_code();
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));
}
echo json_encode($return);
}
}
// store all user activities that are buffered for logging in this request
// this should work even on non HTTP200 responses
$this->logActivities();
@@ -887,7 +887,6 @@ class Controller {
* get the current registration status
* 0=registration stop |1=new registration allowed
* @return int
* @throws \Exception\PathfinderException
*/
static function getRegistrationStatus(){
return (int)Config::getPathfinderData('registration.status');
@@ -898,7 +897,6 @@ class Controller {
* -> set in pathfinder.ini
* @param string $type
* @return \Log|null
* @throws \Exception\PathfinderException
*/
static function getLogger($type){
return LogController::getLogger($type);

View File

@@ -163,7 +163,6 @@ class LogController extends \Prefab {
* get Logger instance
* @param string $type
* @return \Log|null
* @throws \Exception\PathfinderException
*/
public static function getLogger($type){
$logFiles = Config::getPathfinderData('logfiles');

View File

@@ -16,7 +16,6 @@ class MapController extends AccessController {
/**
* @param \Base $f3
* @throws \Exception
* @throws \Exception\PathfinderException
*/
public function init(\Base $f3) {
$character = $this->getCharacter();

View File

@@ -39,6 +39,7 @@ class Setup extends Controller {
'CCP_SSO_URL',
'CCP_SSO_CLIENT_ID',
'CCP_SSO_SECRET_KEY',
'CCP_SSO_DOWNTIME',
'CCP_ESI_URL',
'CCP_ESI_DATASOURCE',
'SMTP_HOST',
@@ -137,7 +138,6 @@ class Setup extends Controller {
* @param \Base $f3
* @param array $params
* @return bool
* @throws \Exception\PathfinderException
*/
function beforeroute(\Base $f3, $params): bool {
$this->initResource($f3);
@@ -162,7 +162,6 @@ class Setup extends Controller {
/**
* @param \Base $f3
* @throws \Exception\PathfinderException
*/
public function afterroute(\Base $f3) {
// js view (file)
@@ -789,7 +788,6 @@ class Setup extends Controller {
* get default map config
* @param \Base $f3
* @return array
* @throws \Exception\PathfinderException
*/
protected function getMapsDefaultConfig(\Base $f3): array {
$matrix = \Matrix::instance();
@@ -1517,8 +1515,8 @@ class Setup extends Controller {
*/
protected function invalidateCookies(\Base $f3){
$this->getDB('PF');
$authentidationModel = Model\BasicModel::getNew('CharacterAuthenticationModel');
$results = $authentidationModel->find();
$authenticationModel = Model\BasicModel::getNew('CharacterAuthenticationModel');
$results = $authenticationModel->find();
if($results){
foreach($results as $result){
$result->erase();
@@ -1537,7 +1535,7 @@ class Setup extends Controller {
if($bytes){
$base = log($bytes, 1024);
$suffixes = array('', 'KB', 'M', 'GB', 'TB');
$result = round(pow(1024, $base - floor($base)), $precision) .''. $suffixes[floor($base)];
$result = round(pow(1024, $base - floor($base)), $precision) .''. $suffixes[(int)floor($base)];
}
return $result;
}

View File

@@ -22,7 +22,6 @@ class MapUpdate extends AbstractCron {
* deactivate all "private" maps whose lifetime is over
* >> php index.php "/cron/deactivateMapData"
* @param \Base $f3
* @throws \Exception\PathfinderException
*/
function deactivateMapData(\Base $f3){
$this->setMaxExecutionTime();

View File

@@ -116,7 +116,6 @@ class Database extends \Prefab {
* @param string $password
* @param string $alias
* @return SQL|null
* @throws \Exception\PathfinderException
*/
protected function connect($dns, $name, $user, $password, $alias){
$db = null;
@@ -128,7 +127,7 @@ class Database extends \Prefab {
];
// set ERRMODE depending on pathfinders global DEBUG level
if($f3->get('DEBUG') >= 3){
if($f3->get('DEBUG') >= 1){
$options[\PDO::ATTR_ERRMODE] = \PDO::ERRMODE_WARNING;
}else{
$options[\PDO::ATTR_ERRMODE] = \PDO::ERRMODE_EXCEPTION;
@@ -286,7 +285,6 @@ class Database extends \Prefab {
/**
* get logger for DB logging
* @return \Log
* @throws \Exception\PathfinderException
*/
static function getLogger(){
return LogController::getLogger('ERROR');

View File

@@ -1,23 +0,0 @@
<?php
/**
* Created by PhpStorm.
* User: exodus4d
* Date: 21.02.15
* Time: 00:41
*/
namespace Exception;
class BaseException extends \Exception {
const VALIDATION_EXCEPTION = 403;
const REGISTRATION_EXCEPTION = 403;
const CONFIG_VALUE_EXCEPTION = 500;
const DB_EXCEPTION = 500;
public function __construct(string $message, int $code = 0){
parent::__construct($message, $code);
}
}

View File

@@ -0,0 +1,18 @@
<?php
/**
* Created by PhpStorm.
* User: exodu
* Date: 20.10.2018
* Time: 18:53
*/
namespace Exception;
class ConfigException extends PathfinderException {
protected $codes = [
1000 => 500
];
}

View File

@@ -8,9 +8,13 @@
namespace Exception;
class DatabaseException extends BaseException {
class DatabaseException extends PathfinderException {
protected $codes = [
1500 => 500
];
public function __construct(string $message){
parent::__construct($message, self::DB_EXCEPTION);
parent::__construct($message, 1500);
}
}

View File

@@ -0,0 +1,16 @@
<?php
/**
* Created by PhpStorm.
* User: Exodus 4D
* Date: 29.11.2018
* Time: 18:12
*/
namespace Exception;
class DateException extends PathfinderException {
protected $codes = [
3000 => 500 // invalid DateRange
];
}

View File

@@ -1,17 +1,61 @@
<?php
/**
* Created by PhpStorm.
* User: exodu
* Date: 05.06.2017
* Time: 19:19
* User: exodus4d
* Date: 21.02.15
* Time: 00:41
*/
namespace Exception;
use lib\Config;
class PathfinderException extends BaseException{
class PathfinderException extends \Exception {
public function __construct($message){
parent::__construct($message, self::CONFIG_VALUE_EXCEPTION);
/**
* default HTTP response code for PathfinderExceptions
* -> can be specified by using custom Exception codes
*/
const DEFAULT_RESPONSECODE = 500;
/**
* lists all exception codes
* @var array
*/
protected $codes = [
0 => self::DEFAULT_RESPONSECODE
];
public function __construct(string $message, int $code = 0){
if( !array_key_exists($code, $this->codes) ){
// exception code not specified by child class
$code = 0;
}
parent::__construct($message, $code);
}
}
/**
* get error object
* @return \stdClass
*/
public function getError() : \stdClass {
$error = (object) [];
$error->type = 'error';
$error->code = $this->getResponseCode();
$error->status = Config::getHttpStatusByCode($this->getResponseCode());
$error->message = $this->getMessage();
if(\Base::instance()->get('DEBUG') >= 1){
$error->trace = preg_split('/\R/', $this->getTraceAsString()); // no $this->>getTrace() here -> to much data
}
return $error;
}
/**
* returns the HTTP response code for the client from exception
* -> if Exception is not handled/catched 'somewhere' this code is used by the final onError handler
* @return int
*/
public function getResponseCode() : int {
return $this->codes[$this->getCode()];
}
}

View File

@@ -9,7 +9,11 @@
namespace Exception;
class RegistrationException extends BaseException{
class RegistrationException extends PathfinderException{
protected $codes = [
2000 => 403
];
/**
* form field name that causes this exception
@@ -17,22 +21,18 @@ class RegistrationException extends BaseException{
*/
private $field;
/**
* @return mixed
*/
public function getField(){
return $this->field;
}
/**
* @param mixed $field
*/
public function setField($field){
public function __construct(string $message, string $field = ''){
parent::__construct($message, 2000);
$this->field = $field;
}
public function __construct($message, $field = ''){
parent::__construct($message, self::REGISTRATION_EXCEPTION);
$this->setField($field);
/**
* get error object
* @return \stdClass
*/
public function getError() : \stdClass {
$error = parent::getError();
$error->field = $this->field;
return $error;
}
}

View File

@@ -9,7 +9,11 @@
namespace Exception;
class ValidationException extends BaseException {
class ValidationException extends PathfinderException {
protected $codes = [
2000 => 422
];
/**
* table column that triggers the exception
@@ -17,35 +21,18 @@ class ValidationException extends BaseException {
*/
private $field;
/**
* @return string
*/
public function getField(): string {
return $this->field;
}
/**
* @param string $field
*/
public function setField(string $field){
$this->field = $field;
}
public function __construct(string $message, string $field = ''){
parent::__construct($message, self::VALIDATION_EXCEPTION);
$this->setField($field);
parent::__construct($message, 2000);
$this->field = $field;
}
/**
* get error object
* @return \stdClass
*/
public function getError(){
$error = (object) [];
$error->type = 'error';
$error->field = $this->getField();
$error->message = $this->getMessage();
public function getError() : \stdClass {
$error = parent::getError();
$error->field = $this->field;
return $error;
}
}

View File

@@ -0,0 +1,55 @@
<?php
/**
* Created by PhpStorm.
* User: Exodus 4D
* Date: 29.11.2018
* Time: 18:06
*/
namespace lib;
use Exception\DateException;
class DateRange {
/**
* from range start
* @var \DateTime
*/
protected $from;
/**
* to range end
* @var \DateTime
*/
protected $to;
/**
* DateRange constructor.
* @param \DateTime $from
* @param \DateTime $to
* @throws DateException
*/
public function __construct(\DateTime $from, \DateTime $to){
if($from == $to){
throw new DateException('A period cannot be the same time', 3000);
}else{
if($from < $to){
$this->from = $from;
$this->to = $to;
} else {
$this->from = $to;
$this->to = $from;
}
}
}
/**
* check if DateTime $dateCheck is within this range
* @param \DateTime $dateCheck
* @return bool
*/
public function inRange(\DateTime $dateCheck) : bool {
return $dateCheck >= $this->from && $dateCheck <= $this->to;
}
}

View File

@@ -36,8 +36,8 @@ class Monolog extends \Prefab {
'mail' => 'Monolog\Handler\SwiftMailerHandler',
'slackMap' => 'lib\logging\handler\SlackMapWebhookHandler',
'slackRally' => 'lib\logging\handler\SlackRallyWebhookHandler',
'discordMap' => 'lib\logging\handler\SlackMapWebhookHandler', // use Slack handler for Discord
'discordRally' => 'lib\logging\handler\SlackRallyWebhookHandler', // use Slack handler for Discord
'discordMap' => 'lib\logging\handler\DiscordMapWebhookHandler',
'discordRally' => 'lib\logging\handler\DiscordRallyWebhookHandler',
'zmq' => 'lib\logging\handler\ZMQHandler'
];

View File

@@ -24,7 +24,6 @@ class CcpClient extends \Prefab {
* get ApiClient instance
* @param \Base $f3
* @return ApiClient|null
* @throws \Exception\PathfinderException
*/
protected function getClient(\Base $f3){
$client = null;
@@ -45,7 +44,6 @@ class CcpClient extends \Prefab {
/**
* @return string
* @throws \Exception\PathfinderException
*/
protected function getUserAgent(){
$userAgent = '';
@@ -71,7 +69,6 @@ class CcpClient extends \Prefab {
* @param $name
* @param $arguments
* @return array|mixed
* @throws \Exception\PathfinderException
*/
public function __call($name, $arguments){
$return = [];

View File

@@ -30,6 +30,9 @@ class Config extends \Prefab {
*/
const ARRAY_KEYS = ['CCP_ESI_SCOPES', 'CCP_ESI_SCOPES_ADMIN'];
const
HTTP_422='Unprocessable Entity';
/**
* all environment data
* @var array
@@ -208,7 +211,6 @@ class Config extends \Prefab {
/**
* get SMTP config values
* @return \stdClass
* @throws Exception\PathfinderException
*/
static function getSMTPConfig(): \stdClass{
$config = new \stdClass();
@@ -253,7 +255,6 @@ class Config extends \Prefab {
* get email for notifications by hive key
* @param $key
* @return mixed
* @throws Exception\PathfinderException
*/
static function getNotificationMail($key){
return self::getPathfinderData('notification' . ($key ? '.' . $key : ''));
@@ -264,7 +265,6 @@ class Config extends \Prefab {
* -> read from pathfinder.ini
* @param string $mapType
* @return mixed
* @throws Exception\PathfinderException
*/
static function getMapsDefaultConfig($mapType = ''){
if( $mapConfig = self::getPathfinderData('map' . ($mapType ? '.' . $mapType : '')) ){
@@ -373,27 +373,71 @@ class Config extends \Prefab {
){
$uri = 'tcp://' . $ip . ':' . $port;
}
return $uri;
}
/**
* @param string $key
* @return null|mixed
* @throws Exception\PathfinderException
*/
static function getPathfinderData($key = ''){
$hiveKey = self::HIVE_KEY_PATHFINDER . ($key ? '.' . strtoupper($key) : '');
$data = null; // make sure it is always defined
try{
if( !\Base::instance()->exists($hiveKey, $data) ){
throw new Exception\PathfinderException(sprintf(self::ERROR_CONF_PATHFINDER, $hiveKey));
throw new Exception\ConfigException(sprintf(self::ERROR_CONF_PATHFINDER, $hiveKey));
}
}catch (Exception\PathfinderException $e){
}catch (Exception\ConfigException $e){
LogController::getLogger('ERROR')->write($e->getMessage());
}
return $data;
}
/**
* get HTTP status message by HTTP return code
* -> either from F3 or from self::Config constants
* @param int $code
* @return string
*/
static function getHttpStatusByCode(int $code) : string {
if(empty($status = @constant('Base::HTTP_' . $code))){
$status = @constant('self::HTTP_' . $code);
}
return $status;
}
/**
* check if a given DateTime() is within downTime range: downtime + 10m
* -> can be used for prevent logging errors during downTime
* @param \DateTime|null $dateCheck
* @return bool
* @throws Exception\DateException
* @throws \Exception
*/
static function inDownTimeRange(\DateTime $dateCheck = null) : bool {
// default daily downtime 00:00am
$downTimeParts = [0, 0];
if( !empty($downTime = (string)self::getEnvironmentData('CCP_SSO_DOWNTIME')) ){
$parts = array_map('intval', explode(':', $downTime));
if(count($parts) === 2){
// well formatted DOWNTIME found in config files
$downTimeParts = $parts;
}
}
// downTime Range is 10m
$downtimeInterval = new \DateInterval('PT10M');
$timezone = \Base::instance()->get('getTimeZone')();
// if set -> use current time
$dateCheck = is_null($dateCheck) ? new \DateTime('now', $timezone) : $dateCheck;
$dateDowntimeStart = new \DateTime('now', $timezone);
$dateDowntimeStart->setTime($downTimeParts[0],$downTimeParts[1]);
$dateDowntimeEnd = clone $dateDowntimeStart;
$dateDowntimeEnd->add($downtimeInterval);
$dateRange = new DateRange($dateDowntimeStart, $dateDowntimeEnd);
return $dateRange->inRange($dateCheck);
}
}

View File

@@ -68,7 +68,6 @@ abstract class AbstractCharacterLog extends AbstractChannelLog{
/**
* get character thumbnailUrl
* @return string
* @throws \Exception\PathfinderException
*/
protected function getThumbUrl(): string {
$url = '';

View File

@@ -37,7 +37,6 @@ class RallyLog extends AbstractCharacterLog{
/**
* @return string
* @throws \Exception\PathfinderException
*/
protected function getThumbUrl() : string{
$url = '';

View File

@@ -0,0 +1,99 @@
<?php
/**
* Created by PhpStorm.
* User: Exodus 4D
* Date: 17.11.2018
* Time: 10:18
*/
namespace lib\logging\handler;
use lib\Util;
abstract class AbstractMapWebhookHandler extends AbstractWebhookHandler {
/**
* @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,156 @@
<?php
/**
* Created by PhpStorm.
* User: Exodus 4D
* Date: 17.11.2018
* Time: 10:09
*/
namespace lib\logging\handler;
use League\HTMLToMarkdown\HtmlConverter;
use lib\Util;
abstract class AbstractRallyWebhookHandler extends AbstractWebhookHandler {
/**
* @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['objCountPlanets'])){
// System planet count
$attachment['fields'][] = $this->generateAttachmentField('Planets', $objectData['objCountPlanets']);
}
if(!empty($objectData['objDescription'])){
// System description
$attachment['fields'][] = $this->generateAttachmentField('System description', '```' . $this->htmlToMarkdown($objectData['objDescription']) . '```', false, false);
}
if(!empty($objectData['objUrl'])){
// System deeeplink
$attachment['fields'][] = $this->generateAttachmentField('', $objectData['objUrl'] , 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;
}
/**
* convert $html into Markdown
* @param $html
* @return string
*/
protected function htmlToMarkdown($html){
$converter = new HtmlConverter();
$converter->getConfig()->setOption('strip_tags', true);
$markdown = $converter->convert($html);
return $markdown;
}
}

View File

@@ -12,7 +12,7 @@ use lib\Config;
use Monolog\Handler;
use Monolog\Logger;
abstract class AbstractSlackWebhookHandler extends Handler\AbstractProcessingHandler {
abstract class AbstractWebhookHandler extends Handler\AbstractProcessingHandler {
/**
* @var string
@@ -179,7 +179,6 @@ abstract class AbstractSlackWebhookHandler extends Handler\AbstractProcessingHan
* @param array $attachment
* @param array $characterData
* @return array
* @throws \Exception\PathfinderException
*/
protected function setAuthor(array $attachment, array $characterData): array {
if( !empty($characterData['id']) && !empty($characterData['name'])){

View File

@@ -0,0 +1,14 @@
<?php
/**
* Created by PhpStorm.
* User: Exodus 4D
* Date: 17.11.2018
* Time: 10:23
*/
namespace lib\logging\handler;
class DiscordMapWebhookHandler extends AbstractMapWebhookHandler {
}

View File

@@ -0,0 +1,20 @@
<?php
/**
* Created by PhpStorm.
* User: Exodus 4D
* Date: 17.11.2018
* Time: 10:13
*/
namespace lib\logging\handler;
class DiscordRallyWebhookHandler extends AbstractRallyWebhookHandler {
protected function htmlToMarkdown($html){
$markdown = parent::htmlToMarkdown($html);
// Discord supports syntax highlighting for MarkDown
$markdown = 'Markdown' . "\n" . $markdown;
return $markdown;
}
}

View File

@@ -8,95 +8,7 @@
namespace lib\logging\handler;
use lib\Util;
class SlackMapWebhookHandler extends AbstractSlackWebhookHandler {
/**
* @param array $record
* @return array
* @throws \Exception\PathfinderException
*/
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;
}
class SlackMapWebhookHandler extends AbstractMapWebhookHandler {
}

View File

@@ -8,139 +8,10 @@
namespace lib\logging\handler;
use lib\Util;
class SlackRallyWebhookHandler extends AbstractSlackWebhookHandler {
class SlackRallyWebhookHandler extends AbstractRallyWebhookHandler {
/**
* @param array $record
* @return array
* @throws \Exception\PathfinderException
*/
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['objCountPlanets'])){
// System planet count
$attachment['fields'][] = $this->generateAttachmentField('Planets', $objectData['objCountPlanets']);
}
if(!empty($objectData['objDescription'])){
// System trueSec
$attachment['fields'][] = $this->generateAttachmentField('System description', '```' . $objectData['objDescription'] . '```', false, false);
}
if(!empty($objectData['objUrl'])){
// System deeeplink
$attachment['fields'][] = $this->generateAttachmentField('', $objectData['objUrl'] , 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

@@ -100,9 +100,9 @@ class Web extends \Web {
* @param array $additionalOptions
* @param int $retryCount request counter for failed call
* @return array|FALSE|mixed
* @throws \Exception\PathfinderException
* @throws \Exception\DateException
*/
public function request($url,array $options = null, $additionalOptions = [], $retryCount = 0 ) {
public function request($url,array $options = null, $additionalOptions = [], $retryCount = 0){
$f3 = \Base::instance();
if( !$f3->exists( $hash = $this->getCacheKey($url, $options) ) ){
@@ -135,7 +135,11 @@ class Web extends \Web {
$url,
json_decode($result['body'])
);
LogController::getLogger('ERROR')->write($errorMsg);
// if request not within downTime time range -> log error
if( !Config::inDownTimeRange() ){
LogController::getLogger('ERROR')->write($errorMsg);
}
break;
case 500:
case 501:
@@ -151,7 +155,11 @@ class Web extends \Web {
$url,
json_decode($result['body'])
);
LogController::getLogger('ERROR')->write($errorMsg);
// if request not within downTime time range -> log error
if( !Config::inDownTimeRange() ){
LogController::getLogger('ERROR')->write($errorMsg);
}
// trigger error
if($additionalOptions['suppressHTTPErrors'] !== true){
@@ -174,8 +182,10 @@ class Web extends \Web {
json_decode($result['body'])
);
// log error
LogController::getLogger('ERROR')->write($errorMsg);
// if request not within downTime time range -> log error
if( !Config::inDownTimeRange() ){
LogController::getLogger('ERROR')->write($errorMsg);
}
if($additionalOptions['suppressHTTPErrors'] !== true){
$f3->error(504, $errorMsg);
@@ -190,7 +200,9 @@ class Web extends \Web {
$url
);
LogController::getLogger('ERROR')->write($errorMsg);
if( !Config::inDownTimeRange() ){
LogController::getLogger('ERROR')->write($errorMsg);
}
break;
}

View File

@@ -23,7 +23,7 @@ abstract class AbstractMapTrackingModel extends BasicModel implements LogModelIn
'on-delete' => 'CASCADE'
]
],
'validate' => 'validate_notDry'
'validate' => 'notDry'
],
'updatedCharacterId' => [
'type' => Schema::DT_INT,
@@ -35,7 +35,7 @@ abstract class AbstractMapTrackingModel extends BasicModel implements LogModelIn
'on-delete' => 'CASCADE'
]
],
'validate' => 'validate_notDry'
'validate' => 'notDry'
]
];

View File

@@ -178,6 +178,7 @@ class ActivityLogModel extends BasicModel {
* @param null $table
* @param null $fields
* @return bool
* @throws \Exception
*/
public static function setup($db=null, $table=null, $fields=null){
$status = parent::setup($db,$table,$fields);

View File

@@ -77,7 +77,6 @@ class AllianceModel extends BasicModel {
/**
* get all maps for this alliance
* @return array|mixed
* @throws \Exception\PathfinderException
*/
public function getMaps(){
$maps = [];

View File

@@ -118,7 +118,7 @@ abstract class BasicModel extends \DB\Cortex {
parent::__construct($db, $table, $fluid, $ttl);
// insert events ------------------------------------------------------------------------------------
$this->beforeinsert( function($self, $pkeys){
$this->beforeinsert(function($self, $pkeys){
return $self->beforeInsertEvent($self, $pkeys);
});
@@ -127,21 +127,21 @@ abstract class BasicModel extends \DB\Cortex {
});
// update events ------------------------------------------------------------------------------------
$this->beforeupdate( function($self, $pkeys){
$this->beforeupdate(function($self, $pkeys){
return $self->beforeUpdateEvent($self, $pkeys);
});
$this->afterupdate( function($self, $pkeys){
$this->afterupdate(function($self, $pkeys){
$self->afterUpdateEvent($self, $pkeys);
});
// erase events -------------------------------------------------------------------------------------
$this->beforeerase( function($self, $pkeys){
$this->beforeerase(function($self, $pkeys){
return $self->beforeEraseEvent($self, $pkeys);
});
$this->aftererase( function($self, $pkeys){
$this->aftererase(function($self, $pkeys){
$self->afterEraseEvent($self, $pkeys);
});
}
@@ -264,7 +264,7 @@ abstract class BasicModel extends \DB\Cortex {
* get static fields for this model instance
* @return array
*/
protected function getStaticFieldConf(): array {
protected function getStaticFieldConf() : array {
$staticFieldConfig = [];
// static tables (fixed data) do not require them...
@@ -299,13 +299,14 @@ abstract class BasicModel extends \DB\Cortex {
* @param $val
* @return bool
*/
protected function validateField(string $key, $val): bool {
protected function validateField(string $key, $val) : bool {
$valid = true;
if($fieldConf = $this->fieldConf[$key]){
if($method = $this->fieldConf[$key]['validate']){
if( !is_string($method)){
$method = 'validate_' . $key;
$method = $key;
}
$method = 'validate_' . $method;
if(method_exists($this, $method)){
// validate $key (column) with this method...
$valid = $this->$method($key, $val);
@@ -325,7 +326,7 @@ abstract class BasicModel extends \DB\Cortex {
* @return bool
* @throws \Exception\ValidationException
*/
protected function validate_notDry($key, $val): bool {
protected function validate_notDry($key, $val) : bool {
$valid = true;
if($colConf = $this->fieldConf[$key]){
if(isset($colConf['belongs-to-one'])){
@@ -344,6 +345,30 @@ abstract class BasicModel extends \DB\Cortex {
return $valid;
}
/**
* validates a model field to be not empty
* @param $key
* @param $val
* @return bool
*/
protected function validate_notEmpty($key, $val) : bool {
$valid = false;
if($colConf = $this->fieldConf[$key]){
switch($colConf['type']){
case Schema::DT_INT:
case Schema::DT_FLOAT:
if( (is_int($val) || ctype_digit($val)) && (int)$val > 0){
$valid = true;
}
break;
default:
}
}
return $valid;
}
/**
* get key for for all objects in this table
* @return string
@@ -628,7 +653,7 @@ abstract class BasicModel extends \DB\Cortex {
* function should be overwritten in parent classes
* @return bool
*/
public function isValid(): bool {
public function isValid() : bool {
return true;
}
@@ -776,7 +801,7 @@ abstract class BasicModel extends \DB\Cortex {
* @param string $action
* @return Logging\LogInterface
*/
protected function newLog($action = ''): Logging\LogInterface{
protected function newLog($action = '') : Logging\LogInterface{
return new Logging\DefaultLog($action);
}
@@ -800,7 +825,7 @@ abstract class BasicModel extends \DB\Cortex {
* get all validation errors
* @return array
*/
public function getErrors(): array {
public function getErrors() : array {
return $this->validationError;
}
@@ -808,7 +833,7 @@ abstract class BasicModel extends \DB\Cortex {
* checks whether data is outdated and should be refreshed
* @return bool
*/
protected function isOutdated(): bool {
protected function isOutdated() : bool {
$outdated = true;
if(!$this->dry()){
$timezone = $this->getF3()->get('getTimeZone')();
@@ -832,7 +857,7 @@ abstract class BasicModel extends \DB\Cortex {
}catch(ValidationException $e){
$this->setValidationError($e);
}catch(DatabaseException $e){
self::getF3()->error($e->getCode(), $e->getMessage(), $e->getTrace());
self::getF3()->error($e->getResponseCode(), $e->getMessage(), $e->getTrace());
}
}
@@ -913,7 +938,6 @@ abstract class BasicModel extends \DB\Cortex {
* debug log function
* @param string $text
* @param string $type
* @throws \Exception\PathfinderException
*/
public static function log($text, $type = 'DEBUG'){
Controller\LogController::getLogger($type)->write($text);

View File

@@ -300,8 +300,9 @@ class CharacterModel extends BasicModel {
/**
* setter for "banned" status
* @param bool|int $status
* @return mixed
* @param $status
* @return mixed|string|null
* @throws \Exception
*/
public function set_banned($status){
if($this->allowBanChange){
@@ -405,6 +406,7 @@ class CharacterModel extends BasicModel {
*/
private function resetAdminColumns(){
$this->kick();
$this->ban();
}
/**
@@ -473,7 +475,6 @@ class CharacterModel extends BasicModel {
* get ESI API "access_token" from OAuth
* @return bool|mixed
* @throws \Exception
* @throws \Exception\PathfinderException
*/
public function getAccessToken(){
$accessToken = false;
@@ -544,7 +545,6 @@ class CharacterModel extends BasicModel {
* checks whether this character is authorized to log in
* -> check corp/ally whitelist config (pathfinder.ini)
* @return bool
* @throws \Exception\PathfinderException
*/
public function isAuthorized(){
$authStatus = 'UNKNOWN';
@@ -614,7 +614,6 @@ class CharacterModel extends BasicModel {
* get Pathfinder role for character
* @return RoleModel
* @throws \Exception
* @throws \Exception\PathfinderException
*/
public function requestRole() : RoleModel{
$role = null;
@@ -660,7 +659,6 @@ class CharacterModel extends BasicModel {
* request all corporation roles granted to this character
* @return array
* @throws \Exception
* @throws \Exception\PathfinderException
*/
protected function requestRoles(){
$rolesData = [];
@@ -1021,7 +1019,6 @@ class CharacterModel extends BasicModel {
/**
* get all accessible map models for this character
* @return MapModel[]
* @throws \Exception\PathfinderException
*/
public function getMaps(){
$this->filter(

View File

@@ -30,11 +30,19 @@ class ConnectionLogModel extends BasicModel {
'table' => 'connection',
'on-delete' => 'CASCADE'
]
]
],
'validate' => 'notDry'
],
'record' => [
'type' => Schema::DT_BOOL,
'nullable' => false,
'default' => 1,
'index' => true
],
'shipTypeId' => [
'type' => Schema::DT_INT,
'index' => true
'index' => true,
'validate' => 'notEmpty'
],
'shipTypeName' => [
'type' => Schema::DT_VARCHAR128,
@@ -44,11 +52,13 @@ class ConnectionLogModel extends BasicModel {
'shipMass' => [
'type' => Schema::DT_FLOAT,
'nullable' => false,
'default' => 0
'default' => 0,
'validate' => 'notEmpty'
],
'characterId' => [
'type' => Schema::DT_INT,
'index' => true
'index' => true,
'validate' => 'notEmpty'
],
'characterName' => [
'type' => Schema::DT_VARCHAR128,
@@ -57,6 +67,14 @@ class ConnectionLogModel extends BasicModel {
]
];
/**
* set map data by an associative array
* @param array $data
*/
public function setData(array $data){
$this->copyfrom($data, ['shipTypeId', 'shipTypeName', 'shipMass', 'characterId', 'characterName']);
}
/**
* get connection log data
* @return \stdClass
@@ -64,6 +82,8 @@ class ConnectionLogModel extends BasicModel {
public function getData() : \stdClass {
$logData = (object) [];
$logData->id = $this->id;
$logData->active = $this->active;
$logData->record = $this->record;
$logData->connection = (object) [];
$logData->connection->id = $this->get('connectionId', true);
@@ -73,12 +93,46 @@ class ConnectionLogModel extends BasicModel {
$logData->ship->typeName = $this->shipTypeName;
$logData->ship->mass = $this->shipMass;
$logData->character = (object) [];
$logData->character->id = $this->characterId;
$logData->character->name = $this->characterName;
$logData->created = (object) [];
$logData->created->created = strtotime($this->created);
$logData->created->character = (object) [];
$logData->created->character->id = $this->characterId;
$logData->created->character->name = $this->characterName;
$logData->updated = (object) [];
$logData->updated->updated = strtotime($this->updated);
return $logData;
}
/**
* validate shipTypeId
* @param string $key
* @param string $val
* @return bool
*/
protected function validate_shipTypeId(string $key, string $val): bool {
return !empty((int)$val);
}
/**
* @return ConnectionModel
*/
public function getConnection() : ConnectionModel {
return $this->get('connectionId');
}
/**
* check object for model access
* @param CharacterModel $characterModel
* @return bool
*/
public function hasAccess(CharacterModel $characterModel) : bool {
$access = false;
if( !$this->dry() ){
$access = $this->getConnection()->hasAccess($characterModel);
}
return $access;
}
}

View File

@@ -137,9 +137,9 @@ class ConnectionModel extends AbstractMapTrackingModel {
/**
* check object for model access
* @param CharacterModel $characterModel
* @return mixed
* @return bool
*/
public function hasAccess(CharacterModel $characterModel){
public function hasAccess(CharacterModel $characterModel) : bool {
$access = false;
if( !$this->dry() ){
$access = $this->mapId->hasAccess($characterModel);
@@ -149,7 +149,7 @@ class ConnectionModel extends AbstractMapTrackingModel {
/**
* set default connection type by search route between endpoints
* @throws \Exception\PathfinderException
* @throws \Exception
*/
public function setDefaultTypeData(){
if(
@@ -215,7 +215,6 @@ class ConnectionModel extends AbstractMapTrackingModel {
* @param $pkeys
* @return bool
* @throws \Exception\DatabaseException
* @throws \Exception\PathfinderException
*/
public function beforeInsertEvent($self, $pkeys){
// check for "default" connection type and add them if missing
@@ -266,8 +265,8 @@ class ConnectionModel extends AbstractMapTrackingModel {
/**
* @param string $action
* @return Logging\LogInterface
* @throws \Exception\PathfinderException
* @return logging\LogInterface
* @throws \Exception\ConfigException
*/
public function newLog($action = ''): Logging\LogInterface{
return $this->getMap()->newLog($action)->setTempData($this->getLogObjectData());
@@ -335,10 +334,6 @@ class ConnectionModel extends AbstractMapTrackingModel {
*/
public function getLogs(){
$logs = [];
$this->filter('connectionLog', [
'active = :active',
':active' => 1
]);
if($this->connectionLog){
$logs = $this->connectionLog;
@@ -377,20 +372,34 @@ class ConnectionModel extends AbstractMapTrackingModel {
return $logsData;
}
/**
* get blank connectionLog model
* @return ConnectionLogModel
* @throws \Exception
*/
public function getNewLog() : ConnectionLogModel {
/**
* @var $log ConnectionLogModel
*/
$log = self::getNew('ConnectionLogModel');
$log->connectionId = $this;
return $log;
}
/**
* log new mass for this connection
* @param CharacterLogModel $characterLog
* @return $this
* @return ConnectionModel
* @throws \Exception
*/
public function logMass(CharacterLogModel $characterLog){
public function logMass(CharacterLogModel $characterLog) : self {
if( !$characterLog->dry() ){
$log = $this->rel('connectionLog');
$log = $this->getNewLog();
$log->shipTypeId = $characterLog->shipTypeId;
$log->shipTypeName = $characterLog->shipTypeName;
$log->shipMass = $characterLog->shipMass;
$log->characterId = $characterLog->characterId->_id;
$log->characterName = $characterLog->characterId->name;
$log->connectionId = $this;
$log->save();
}

View File

@@ -181,7 +181,6 @@ class CorporationModel extends BasicModel {
* @param array $mapIds
* @param array $options
* @return array
* @throws \Exception\PathfinderException
*/
public function getMaps($mapIds = [], $options = []){
$maps = [];

View File

@@ -10,9 +10,9 @@ namespace Model;
use DB\SQL\Schema;
use data\file\FileHandler;
use Exception\ConfigException;
use lib\Config;
use lib\logging;
use Exception\PathfinderException;
class MapModel extends AbstractMapTrackingModel {
@@ -44,7 +44,7 @@ class MapModel extends AbstractMapTrackingModel {
'on-delete' => 'CASCADE'
]
],
'validate' => 'validate_notDry',
'validate' => 'notDry',
'activity-log' => true
],
'typeId' => [
@@ -57,7 +57,7 @@ class MapModel extends AbstractMapTrackingModel {
'on-delete' => 'CASCADE'
]
],
'validate' => 'validate_notDry',
'validate' => 'notDry',
'activity-log' => true
],
'name' => [
@@ -199,7 +199,6 @@ class MapModel extends AbstractMapTrackingModel {
* get data
* -> this includes system and connection data as well
* @return \stdClass
* @throws PathfinderException
* @throws \Exception
*/
public function getData(){
@@ -791,7 +790,6 @@ class MapModel extends AbstractMapTrackingModel {
* checks whether a character has access to this map or not
* @param CharacterModel $characterModel
* @return bool
* @throws PathfinderException
*/
public function hasAccess(CharacterModel $characterModel) : bool {
$hasAccess = false;
@@ -972,8 +970,8 @@ class MapModel extends AbstractMapTrackingModel {
/**
* @param string $action
* @return Logging\LogInterface
* @throws PathfinderException
* @return logging\LogInterface
* @throws ConfigException
*/
public function newLog($action = ''): Logging\LogInterface{
$logChannelData = $this->getLogChannelData();
@@ -1048,7 +1046,6 @@ class MapModel extends AbstractMapTrackingModel {
/**
* check if "activity logging" is enabled for this map type
* @return bool
* @throws PathfinderException
*/
public function isActivityLogEnabled(): bool {
return $this->logActivity && (bool) Config::getMapsDefaultConfig($this->typeId->name)['log_activity_enabled'];
@@ -1057,7 +1054,6 @@ class MapModel extends AbstractMapTrackingModel {
/**
* check if "history logging" is enabled for this map type
* @return bool
* @throws PathfinderException
*/
public function isHistoryLogEnabled(): bool {
return $this->logHistory && (bool) Config::getMapsDefaultConfig($this->typeId->name)['log_history_enabled'];
@@ -1067,7 +1063,7 @@ class MapModel extends AbstractMapTrackingModel {
* check if "Slack WebHook" is enabled for this map type
* @param string $channel
* @return bool
* @throws PathfinderException
* @throws ConfigException
*/
public function isSlackChannelEnabled(string $channel): bool {
$enabled = false;
@@ -1077,7 +1073,7 @@ class MapModel extends AbstractMapTrackingModel {
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));
default: throw new ConfigException(sprintf(self::ERROR_SLACK_CHANNEL, $channel));
}
if((bool) Config::getMapsDefaultConfig($this->typeId->name)[$defaultMapConfigKey]){
@@ -1095,7 +1091,7 @@ class MapModel extends AbstractMapTrackingModel {
* check if "Discord WebHook" is enabled for this map type
* @param string $channel
* @return bool
* @throws PathfinderException
* @throws ConfigException
*/
public function isDiscordChannelEnabled(string $channel): bool {
$enabled = false;
@@ -1105,7 +1101,7 @@ class MapModel extends AbstractMapTrackingModel {
switch($channel){
case 'discordWebHookURLHistory': $defaultMapConfigKey = 'send_history_discord_enabled'; break;
case 'discordWebHookURLRally': $defaultMapConfigKey = 'send_rally_discord_enabled'; break;
default: throw new PathfinderException(sprintf(self::ERROR_DISCORD_CHANNEL, $channel));
default: throw new ConfigException(sprintf(self::ERROR_DISCORD_CHANNEL, $channel));
}
if((bool) Config::getMapsDefaultConfig($this->typeId->name)[$defaultMapConfigKey]){
@@ -1123,7 +1119,6 @@ class MapModel extends AbstractMapTrackingModel {
* check if "E-Mail" Log is enabled for this map
* @param string $type
* @return bool
* @throws PathfinderException
*/
public function isMailSendEnabled(string $type): bool{
$enabled = false;
@@ -1197,7 +1192,6 @@ class MapModel extends AbstractMapTrackingModel {
* @param string $type
* @param bool $addJson
* @return \stdClass
* @throws PathfinderException
*/
public function getSMTPConfig(string $type, bool $addJson = true): \stdClass{
$config = Config::getSMTPConfig();
@@ -1350,7 +1344,6 @@ class MapModel extends AbstractMapTrackingModel {
* get all active characters (with active log)
* grouped by systems
* @return \stdClass
* @throws PathfinderException
* @throws \Exception
*/
public function getUserData(){

View File

@@ -95,8 +95,6 @@ class SystemModel extends AbstractMapTrackingModel {
],
'description' => [
'type' => Schema::DT_TEXT,
'nullable' => false,
'default' => '',
'activity-log' => true,
'validate' => true
],
@@ -121,6 +119,14 @@ class SystemModel extends AbstractMapTrackingModel {
]
];
/**
* set map data by an associative array
* @param array $data
*/
public function setData(array $data){
$this->copyfrom($data, ['statusId', 'locked', 'rallyUpdated', 'position', 'description']);
}
/**
* get map data as object
* @return \stdClass
@@ -151,7 +157,7 @@ class SystemModel extends AbstractMapTrackingModel {
$systemData->locked = $this->locked;
$systemData->rallyUpdated = strtotime($this->rallyUpdated);
$systemData->rallyPoke = $this->rallyPoke;
$systemData->description = $this->description;
$systemData->description = $this->description ? : '';
$systemData->position = (object) [];
$systemData->position->x = $this->posX;
@@ -273,7 +279,7 @@ class SystemModel extends AbstractMapTrackingModel {
$valid = true;
if(mb_strlen($val) > 9000){
$valid = false;
$this->throwValidationException($key);
$this->throwValidationException($key, 'Validation failed: "' . $key . '" too long');
}
return $valid;
}
@@ -490,8 +496,8 @@ class SystemModel extends AbstractMapTrackingModel {
/**
* @param string $action
* @return Logging\LogInterface
* @throws \Exception\PathfinderException
* @return logging\LogInterface
* @throws \Exception\ConfigException
*/
public function newLog($action = ''): Logging\LogInterface{
return $this->getMap()->newLog($action)->setTempData($this->getLogObjectData());
@@ -654,7 +660,7 @@ class SystemModel extends AbstractMapTrackingModel {
* -> send to an Email
* @param array $rallyData
* @param CharacterModel $characterModel
* @throws \Exception\PathfinderException
* @throws \Exception\ConfigException
*/
public function sendRallyPoke(array $rallyData, CharacterModel $characterModel){
// rally log needs at least one handler to be valid

View File

@@ -162,8 +162,8 @@ class SystemSignatureModel extends AbstractMapTrackingModel {
/**
* @param string $action
* @return Logging\LogInterface
* @throws \Exception\PathfinderException
* @return logging\LogInterface
* @throws \Exception\ConfigException
*/
public function newLog($action = ''): Logging\LogInterface{
return $this->getMap()->newLog($action)->setTempData($this->getLogObjectData());
@@ -194,11 +194,14 @@ class SystemSignatureModel extends AbstractMapTrackingModel {
$hasChanged = false;
foreach((array)$signatureData as $key => $value){
if(
$this->exists($key) &&
$this->$key != $value
){
$hasChanged = true;
if($this->exists($key)){
if($this->$key instanceof ConnectionModel){
$currentValue = $this->get($key, true);
}else{
$currentValue = $this->$key;
}
$hasChanged = $currentValue !== $value;
break;
}
}

View File

@@ -33,14 +33,15 @@ class CategoryModel extends BasicUniverseModel {
/**
* get category data
* @return object
* @param array $additionalData
* @return null|object
*/
public function getData(){
public function getData(array $additionalData = []){
$categoryData = (object) [];
$categoryData->id = $this->id;
$categoryData->name = $this->name;
if($groupsData = $this->getGroupsData()){
if($groupsData = $this->getGroupsData($additionalData)){
$categoryData->groups = $groupsData;
}
@@ -69,14 +70,15 @@ class CategoryModel extends BasicUniverseModel {
}
/**
* @param array $additionalData
* @return array
*/
protected function getGroupsData() : array {
protected function getGroupsData(array $additionalData = []) : array {
$groupsData = [];
$groups = $this->getGroups();
foreach($groups as $group){
$groupsData[] = $group->getData();
$groupsData[] = $group->getData($additionalData);
}
return $groupsData;

View File

@@ -30,7 +30,7 @@ class ConstellationModel extends BasicUniverseModel {
'on-delete' => 'CASCADE'
]
],
'validate' => 'validate_notDry'
'validate' => 'notDry'
],
'x' => [
'type' => Schema::DT_BIGINT,

View File

@@ -36,7 +36,7 @@ class GroupModel extends BasicUniverseModel {
'on-delete' => 'CASCADE'
]
],
'validate' => 'validate_notDry'
'validate' => 'notDry'
],
'types' => [
'has-many' => ['Model\Universe\TypeModel', 'groupId']
@@ -45,14 +45,15 @@ class GroupModel extends BasicUniverseModel {
/**
* get group data
* @return object
* @param array $additionalData
* @return null|object
*/
public function getData(){
public function getData(array $additionalData = []){
$groupData = (object) [];
$groupData->id = $this->id;
$groupData->name = $this->name;
if($typesData = $this->getTypesData()){
if($typesData = $this->getTypesData($additionalData)){
$groupData->types = $typesData;
}
@@ -81,14 +82,15 @@ class GroupModel extends BasicUniverseModel {
}
/**
* @param array $additionalData
* @return array
*/
protected function getTypesData() : array {
protected function getTypesData(array $additionalData = []) : array {
$typesData = [];
$types = $this->getTypes();
foreach($types as $type){
$typesData[] = $type->getData();
$typesData[] = $type->getData($additionalData);
}
return $typesData;

View File

@@ -30,7 +30,7 @@ class PlanetModel extends BasicUniverseModel {
'on-delete' => 'CASCADE'
]
],
'validate' => 'validate_notDry'
'validate' => 'notDry'
],
'typeId' => [
'type' => Schema::DT_INT,
@@ -42,7 +42,7 @@ class PlanetModel extends BasicUniverseModel {
'on-delete' => 'SET NULL'
]
],
'validate' => 'validate_notDry'
'validate' => 'notDry'
],
'x' => [
'type' => Schema::DT_BIGINT,

View File

@@ -30,7 +30,7 @@ class StargateModel extends BasicUniverseModel {
'on-delete' => 'CASCADE'
]
],
'validate' => 'validate_notDry'
'validate' => 'notDry'
],
'typeId' => [
'type' => Schema::DT_INT,
@@ -42,7 +42,7 @@ class StargateModel extends BasicUniverseModel {
'on-delete' => 'SET NULL'
]
],
'validate' => 'validate_notDry'
'validate' => 'notDry'
],
'destinationSystemId' => [
'type' => Schema::DT_INT,
@@ -54,7 +54,7 @@ class StargateModel extends BasicUniverseModel {
'on-delete' => 'CASCADE'
]
],
'validate' => 'validate_notDry'
'validate' => 'notDry'
],
'x' => [
'type' => Schema::DT_BIGINT,

View File

@@ -30,7 +30,7 @@ class StarModel extends BasicUniverseModel {
'on-delete' => 'SET NULL'
]
],
'validate' => 'validate_notDry'
'validate' => 'notDry'
],
'age' => [
'type' => Schema::DT_BIGINT,

View File

@@ -37,7 +37,7 @@ class StructureModel extends BasicUniverseModel {
'on-delete' => 'CASCADE'
]
],
'validate' => 'validate_notDry'
'validate' => 'notDry'
],
'x' => [
'type' => Schema::DT_FLOAT,

View File

@@ -32,7 +32,7 @@ class SystemModel extends BasicUniverseModel {
'on-delete' => 'CASCADE'
]
],
'validate' => 'validate_notDry'
'validate' => 'notDry'
],
'starId' => [
'type' => Schema::DT_INT,
@@ -44,7 +44,7 @@ class SystemModel extends BasicUniverseModel {
'on-delete' => 'CASCADE'
]
],
'validate' => 'validate_notDry'
'validate' => 'notDry'
],
'security' => [
'type' => Schema::DT_VARCHAR128

View File

@@ -25,7 +25,7 @@ class SystemStaticModel extends BasicUniverseModel {
'on-delete' => 'CASCADE'
]
],
'validate' => 'validate_notDry'
'validate' => 'notDry'
],
'wormholeId' => [
'type' => Schema::DT_INT,
@@ -37,7 +37,7 @@ class SystemStaticModel extends BasicUniverseModel {
'on-delete' => 'CASCADE'
]
],
'validate' => 'validate_notDry'
'validate' => 'notDry'
]
];

View File

@@ -59,7 +59,7 @@ class TypeModel extends BasicUniverseModel {
'on-delete' => 'CASCADE'
]
],
'validate' => 'validate_notDry',
'validate' => 'notDry',
],
'marketGroupId' => [
'type' => Schema::DT_INT,
@@ -99,13 +99,18 @@ class TypeModel extends BasicUniverseModel {
/**
* get type data
* @return object
* @param array $additionalData
* @return null|object
*/
public function getData(){
public function getData(array $additionalData = []){
$typeData = (object) [];
$typeData->id = $this->id;
$typeData->name = $this->name;
foreach($additionalData as $key){
$typeData->$key = $this->$key;
}
return $typeData;
}

View File

@@ -35,7 +35,7 @@ class WormholeModel extends BasicUniverseModel {
'on-delete' => 'SET NULL'
]
],
'validate' => 'validate_notDry'
'validate' => 'notDry'
],
'static' => [
'type' => Schema::DT_BOOL,

View File

@@ -94,7 +94,6 @@ class UserModel extends BasicModel {
* @param UserModel $self
* @param $pkeys
* @return bool
* @throws Exception\PathfinderException
* @throws Exception\RegistrationException
*/
public function beforeInsertEvent($self, $pkeys){
@@ -137,7 +136,6 @@ class UserModel extends BasicModel {
/**
* checks whether user has a valid email address and pathfinder has a valid SMTP config
* @return bool
* @throws Exception\PathfinderException
*/
protected function isMailSendEnabled() : bool{
return Config::isValidSMTPConfig($this->getSMTPConfig());
@@ -146,7 +144,6 @@ class UserModel extends BasicModel {
/**
* get SMTP config for this user
* @return \stdClass
* @throws Exception\PathfinderException
*/
protected function getSMTPConfig() : \stdClass{
$config = Config::getSMTPConfig();

View File

@@ -3,7 +3,7 @@
[PATHFINDER]
NAME = Pathfinder
; installed version (used for CSS/JS cache busting)
VERSION = v1.4.2
VERSION = v1.4.3
; contact information [optional]
CONTACT = https://github.com/exodus4d
; public contact email [optional]

View File

@@ -30,7 +30,7 @@ ZMQ = 1.1.3
EVENT = 2.3.0
; exec() function required for run Shell scripts from PHP
EXEC = 1
EXEC = 1
; max execution time for requests (seconds)
MAX_EXECUTION_TIME = 10
@@ -84,5 +84,5 @@ NPM = 3.10.0
[REQUIREMENTS.DATA]
STRUCTURES = 33
SHIPS = 490
SHIPS = 491
NEIGHBOURS = 5201

View File

@@ -13,8 +13,12 @@ GET @map: /map* [sync] = Controller\MapContro
; admin panel
GET @admin: /admin* [sync] = Controller\Admin->dispatch
; ajax wildcard APIs (throttled)
; AJAX API wildcard endpoints (not cached, throttled)
GET|POST /api/@controller/@action [ajax] = Controller\Api\@controller->@action, 0, 512
GET|POST /api/@controller/@action/@arg1 [ajax] = Controller\Api\@controller->@action, 0, 512
GET|POST /api/@controller/@action/@arg1/@arg2 [ajax] = Controller\Api\@controller->@action, 0, 512
[maps]
; REST API wildcard endpoints (not cached, throttled)
/api/rest/@controller* [ajax] = Controller\Api\Rest\@controller, 0, 512
/api/rest/@controller/@id [ajax] = Controller\Api\Rest\@controller, 0, 512

View File

@@ -21,6 +21,8 @@
}],
"require": {
"php-64bit": ">=7.0",
"ext-pdo": "*",
"ext-openssl": "*",
"ext-curl": "*",
"ext-json": "*",
"ext-mbstring": "*",
@@ -30,6 +32,7 @@
"monolog/monolog": "1.*",
"websoftwares/monolog-zmq-handler": "0.2.*",
"swiftmailer/swiftmailer": "^6.0",
"league/html-to-markdown": "4.8.*",
"exodus4d/pathfinder_esi": "dev-develop as 0.0.x-dev"
}
}

View File

@@ -21,6 +21,8 @@
}],
"require": {
"php-64bit": ">=7.0",
"ext-pdo": "*",
"ext-openssl": "*",
"ext-curl": "*",
"ext-json": "*",
"ext-mbstring": "*",
@@ -30,6 +32,7 @@
"monolog/monolog": "1.*",
"websoftwares/monolog-zmq-handler": "0.2.*",
"swiftmailer/swiftmailer": "^6.0",
"league/html-to-markdown": "4.8.*",
"exodus4d/pathfinder_esi": "dev-master#v1.2.5"
}
}

View File

@@ -109,14 +109,16 @@ let uglifyJsOptions = {
let printError = (title, example) => {
let cliLineLength = (cliBoxLength - 8);
log('').log(colors.red( '= ERROR ' + '=' . repeat(cliLineLength)));
log('');
log(colors.red( '= ERROR ' + '=' . repeat(cliLineLength)));
log(colors.red(title));
if(example){
log(`
${colors.gray(example)}
`);
}
log(colors.red('='.repeat(cliBoxLength))).log('');
log(colors.red('='.repeat(cliBoxLength)));
log('');
};
// == Settings ========================================================================================================
@@ -270,34 +272,34 @@ let mergeConf = (confUser, confDefault) => {
*/
let printHelp = () => {
let cliLineLength = (cliBoxLength - 7);
log('')
.log(colors.cyan( '= HELP ' + '='.repeat(cliLineLength)))
.log(`
${colors.cyan('documentation:')} ${colors.gray('https://github.com/exodus4d/pathfinder/wiki/GulpJs')}
log('');
log(colors.cyan( '= HELP ' + '='.repeat(cliLineLength)));
log(`
${colors.cyan('documentation:')} ${colors.gray('https://github.com/exodus4d/pathfinder/wiki/GulpJs')}
${colors.cyan('usage:')} ${colors.gray('$ npm run gulp [task] -- [--options] ...')}
${colors.cyan('tasks:')}
${colors.gray('help')} This view
${colors.gray('default')} Development environment. Working with row src files and file watcher, default:
${colors.gray('')} ${colors.gray('--jsUglify=false --jsSourcemaps=false --cssSourcemaps=false --jsGzip=false --cssGzip=false --jsBrotli=false --cssBrotli=false')}
${colors.gray('production')} Production build. Concat and uglify static resources, default:
${colors.gray('')} ${colors.gray('--jsUglify=true --jsSourcemaps=true --cssSourcemaps=true --jsGzip=true --cssGzip=true --jsBrotli=true --cssBrotli=true')}
${colors.cyan('options:')}
${colors.gray('--tag')} Set build version. ${colors.gray('default: --tag="v1.2.4" -> dest path: public/js/v1.2.4')}
${colors.gray('--jsUglify')} Set js uglification. ${colors.gray('(true || false)')}
${colors.gray('--jsSourcemaps')} Set js sourcemaps generation. ${colors.gray('(true || false)')}
${colors.gray('--jsGzip')} Set js "gzip" compression mode. ${colors.gray('(true || false)')}
${colors.gray('--jsBrotli')} Set js "brotli" compression mode. ${colors.gray('(true || false)')}
${colors.cyan('usage:')} ${colors.gray('$ npm run gulp [task] -- [--options] ...')}
${colors.cyan('tasks:')}
${colors.gray('help')} This view
${colors.gray('default')} Development environment. Working with row src files and file watcher, default:
${colors.gray('')} ${colors.gray('--jsUglify=false --jsSourcemaps=false --cssSourcemaps=false --jsGzip=false --cssGzip=false --jsBrotli=false --cssBrotli=false')}
${colors.gray('production')} Production build. Concat and uglify static resources, default:
${colors.gray('')} ${colors.gray('--jsUglify=true --jsSourcemaps=true --cssSourcemaps=true --jsGzip=true --cssGzip=true --jsBrotli=true --cssBrotli=true')}
${colors.cyan('options:')}
${colors.gray('--tag')} Set build version. ${colors.gray('default: --tag="v1.2.4" -> dest path: public/js/v1.2.4')}
${colors.gray('--jsUglify')} Set js uglification. ${colors.gray('(true || false)')}
${colors.gray('--jsSourcemaps')} Set js sourcemaps generation. ${colors.gray('(true || false)')}
${colors.gray('--jsGzip')} Set js "gzip" compression mode. ${colors.gray('(true || false)')}
${colors.gray('--jsBrotli')} Set js "brotli" compression mode. ${colors.gray('(true || false)')}
${colors.gray('--cssSourcemaps')} Set CSS sourcemaps generation. ${colors.gray('(true || false)')}
${colors.gray('--cssGzip')} Set CSS "gzip" compression mode. ${colors.gray('(true || false)')}
${colors.gray('--cssBrotli')} Set CSS "brotli" compression mode. ${colors.gray('(true || false)')}
${colors.gray('--debug')} Set debug mode (more output). ${colors.gray('(true || false)')}
`)
.log(colors.cyan('='.repeat(cliBoxLength)))
.log('');
${colors.gray('--cssSourcemaps')} Set CSS sourcemaps generation. ${colors.gray('(true || false)')}
${colors.gray('--cssGzip')} Set CSS "gzip" compression mode. ${colors.gray('(true || false)')}
${colors.gray('--cssBrotli')} Set CSS "brotli" compression mode. ${colors.gray('(true || false)')}
${colors.gray('--debug')} Set debug mode (more output). ${colors.gray('(true || false)')}
`);
log(colors.cyan('='.repeat(cliBoxLength)));
log('');
};
/**

View File

@@ -54,7 +54,7 @@ requirejs.config({
blueImpGalleryBootstrap: 'lib/bootstrap-image-gallery', // v3.4.2 Bootstrap extension for Blue Imp Gallery - https://blueimp.github.io/Bootstrap-Image-Gallery
bootstrapConfirmation: 'lib/bootstrap-confirmation', // v1.0.5 Bootstrap extension for inline confirm dialog - https://github.com/tavicu/bs-confirmation
bootstrapToggle: 'lib/bootstrap-toggle.min', // v2.2.0 Bootstrap Toggle (Checkbox) - http://www.bootstraptoggle.com
lazyload: 'lib/jquery.lazyload.min', // v1.9.5 LazyLoader images - http://www.appelsiini.net/projects/lazyload
lazyload: 'lib/jquery.lazyload.min', // v1.9.7 LazyLoader images - http://www.appelsiini.net/projects/lazyload
sortable: 'lib/sortable.min', // v1.6.0 Sortable - drag&drop reorder - https://github.com/rubaxa/Sortable
'summernote.loader': './app/summernote.loader', // v0.8.10 Summernote WYSIWYG editor -https://summernote.org

View File

@@ -18,6 +18,9 @@ define([
lengthMenu: [[5, 10, 25, 50, -1], [5, 10, 25, 50, 'All']],
order: [], // no default order because columnDefs is empty
autoWidth: false,
language: {
info: '_START_ - _END_ of _TOTAL_ entries'
},
responsive: {
breakpoints: Init.breakpoints,
details: false

View File

@@ -9,6 +9,7 @@ define(['jquery'], ($) => {
let Config = {
path: {
img: '/public/img/', // path for images
api: '/api/rest', //ajax URL - REST API
// user API
getCaptcha: '/api/user/getCaptcha', // ajax URL - get captcha image
getServerStatus: '/api/user/getEveServerStatus', // ajax URL - get EVE-Online server status
@@ -34,14 +35,9 @@ define(['jquery'], ($) => {
getMapLogData: '/api/map/getLogData', // ajax URL - get logs data
// system API
getSystemData: '/api/system/getData', // ajax URL - get system data
saveSystem: '/api/system/save', // ajax URL - saves system to map
deleteSystem: '/api/system/delete', // ajax URL - delete system from map
getSystemGraphData: '/api/system/graphData', // ajax URL - get all system graph 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
// signature API
saveSignatureData: '/api/signature/save', // ajax URL - save signature data for system
deleteSignatureData: '/api/signature/delete', // ajax URL - delete signature data for system

View File

@@ -20,7 +20,6 @@ define([
let config = {
taskDialogId: 'pf-task-dialog', // id for map "task manager" dialog
dialogDynamicAreaClass: 'pf-dynamic-area', // class for dynamic areas
timestampCounterClass: 'pf-timestamp-counter', // class for "timestamp" counter
taskDialogStatusAreaClass: 'pf-task-dialog-status', // class for "status" dynamic area
taskDialogLogTableAreaClass: 'pf-task-dialog-table', // class for "log table" dynamic area
@@ -83,7 +82,7 @@ define([
requirejs(['text!templates/dialog/task_manager.html', 'mustache', 'datatables.loader'], function(templateTaskManagerDialog, Mustache){
let data = {
id: config.taskDialogId,
dialogDynamicAreaClass: config.dialogDynamicAreaClass,
dialogDynamicAreaClass: Util.config.dynamicAreaClass,
taskDialogStatusAreaClass: config.taskDialogStatusAreaClass,
taskDialogLogTableAreaClass: config.taskDialogLogTableAreaClass
};
@@ -218,7 +217,7 @@ define([
});
let graphArea = $('<div>', {
class: config.dialogDynamicAreaClass
class: Util.config.dynamicAreaClass
}).append( graphElement );
let headline = $('<h4>', {

View File

@@ -343,7 +343,7 @@ define([
let initGallery = (newElements) => {
if( newElements.length > 0){
// We have to add ALL thumbnail elements to the gallery!
// -> even those wthat are invisible (not lazyLoaded) now!
// -> even those which are invisible (not lazyLoaded) now!
// -> This is required for "swipe" through all images
let allThumbLinks = getThumbnailElements();
@@ -582,7 +582,7 @@ define([
* update all character panels -> set CSS class (e.g. after some panels were added/removed,..)
*/
let updateCharacterPanels = function(){
let characterRows = $('.' + config.characterSelectionClass + ' .pf-dynamic-area').parent();
let characterRows = $('.' + config.characterSelectionClass + ' .' + Util.config.dynamicAreaClass).parent();
let rowClassIdentifier = ((12 / characterRows.length ) <= 3) ? 3 : (12 / characterRows.length);
$(characterRows).removeClass().addClass('col-sm-' + rowClassIdentifier);
};
@@ -635,7 +635,7 @@ define([
// request character data for each character panel
requirejs(['text!templates/ui/character_panel.html', 'mustache'], function(template, Mustache){
$('.' + config.characterSelectionClass + ' .pf-dynamic-area').each(function(){
$('.' + config.characterSelectionClass + ' .' + Util.config.dynamicAreaClass).each(function(){
let characterElement = $(this);
characterElement.showLoadingAnimation();
@@ -814,7 +814,7 @@ define([
// init carousel
initCarousel();
// init scrollspy
// init scrollSpy
// -> after "Carousel"! required for correct "viewport" calculation (Gallery)!
initScrollSpy();

View File

@@ -559,7 +559,7 @@ define([
// confirm dialog
bootbox.confirm('Is this connection really gone?', function(result){
if(result){
$().deleteConnections([activeConnection]);
MapUtil.deleteConnections([activeConnection]);
}
});
break;
@@ -1385,158 +1385,73 @@ define([
* @param connection
*/
let saveConnection = function(connection){
if( connection instanceof jsPlumb.Connection ){
if(connection instanceof jsPlumb.Connection){
let map = connection._jsPlumb.instance;
let mapContainer = $( map.getContainer() );
let mapContainer = $(map.getContainer());
let mapId = mapContainer.data('id');
let connectionData = MapUtil.getDataByConnection(connection);
connectionData.mapId = mapId;
let requestData = {
mapData: {
id: mapId
},
connectionData: connectionData
};
Util.request('PUT', 'connection', [], connectionData, {
connection: connection,
map: map,
mapId: mapId,
oldConnectionData: connectionData
}).then(
payload => {
let newConnectionData = payload.data;
$.ajax({
type: 'POST',
url: Init.path.saveConnection,
data: requestData,
dataType: 'json',
context: {
connection: connection,
map: map,
mapId: mapId,
oldConnectionData: connectionData
}
}).done(function(responseData){
let newConnectionData = responseData.connectionData;
if( !$.isEmptyObject(newConnectionData) ){
let updateCon = false;
if( !$.isEmptyObject(newConnectionData) ){
let updateCon = false;
if(this.oldConnectionData.id > 0){
// connection exists (e.g. drag&drop new target system... (ids should never changed)
let connection = $().getConnectionById(this.mapId, this.oldConnectionData.id);
updateCon = true;
}else{
// new connection, check if connectionId was already updated (webSocket push is faster than ajax callback)
let connection = $().getConnectionById(this.mapId, newConnectionData.id);
if(connection){
// connection already updated
this.map.detach(this.connection, {fireEvent: false});
}else{
// .. else update this connection
connection = this.connection;
if(payload.context.oldConnectionData.id > 0){
// connection exists (e.g. drag&drop new target system... (ids should never changed)
let connection = $().getConnectionById(payload.context.mapId, payload.context.oldConnectionData.id);
updateCon = true;
}else{
// new connection, check if connectionId was already updated (webSocket push is faster than ajax callback)
let connection = $().getConnectionById(payload.context.mapId, newConnectionData.id);
if(connection){
// connection already updated
payload.context.map.detach(payload.context.connection, {fireEvent: false});
}else{
// .. else update this connection
connection = payload.context.connection;
updateCon = true;
}
}
if(updateCon){
// update connection data e.g. "scope" has auto detected
connection = updateConnection(connection, payload.context.oldConnectionData, newConnectionData);
// new/updated connection should be cached immediately!
updateConnectionCache(payload.context.mapId, connection);
}
// connection scope
let scope = MapUtil.getScopeInfoForConnection(newConnectionData.scope, 'label');
let title = 'New connection established';
if(payload.context.oldConnectionData.id > 0){
title = 'Connection switched';
}
Util.showNotify({title: title, text: 'Scope: ' + scope, type: 'success'});
}else{
// some save errors
payload.context.map.detach(payload.context.connection, {fireEvent: false});
}
if(updateCon){
// update connection data e.g. "scope" has auto detected
connection = updateConnection(connection, this.oldConnectionData, newConnectionData);
// new/updated connection should be cached immediately!
updateConnectionCache(this.mapId, connection);
}
// connection scope
let scope = MapUtil.getScopeInfoForConnection(newConnectionData.scope, 'label');
let title = 'New connection established';
if(this.oldConnectionData.id > 0){
title = 'Connection switched';
}
Util.showNotify({title: title, text: 'Scope: ' + scope, type: 'success'});
}else{
// some save errors
this.map.detach(this.connection, {fireEvent: false});
},
payload => {
// remove this connection from map
payload.context.map.detach(payload.context.connection, {fireEvent: false});
Util.handleAjaxErrorResponse(payload);
}
// 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});
let reason = status + ' ' + error;
Util.showNotify({title: jqXHR.status + ': saveConnection', text: reason, type: 'warning'});
$(document).setProgramStatus('problem');
});
}
};
/**
* delete a connection and all related data
* @param connections
* @param callback
*/
$.fn.deleteConnections = function(connections, callback){
if(connections.length > 0){
// remove connections from map
let removeConnections = function(tempConnections){
for(let i = 0; i < tempConnections.length; i++){
// if a connection is manually (drag&drop) detached, the jsPlumb instance does not exist any more
// connection is already deleted!
if(tempConnections[i]._jsPlumb){
tempConnections[i]._jsPlumb.instance.detach(tempConnections[i], {fireEvent: false});
}
}
};
// prepare delete request
let map = connections[0]._jsPlumb.instance;
let mapContainer = $( map.getContainer() );
let connectionIds = [];
// connectionIds for delete request
for(let i = 0; i < connections.length; i++){
let connectionId = connections[i].getParameter('connectionId');
// drag&drop a new connection does not have an id yet, if connection is not established correct
if(connectionId !== undefined){
connectionIds[i] = connections[i].getParameter('connectionId');
}
}
if(connectionIds.length > 0){
let requestData = {
mapId: mapContainer.data('id'),
connectionIds: connectionIds
};
$.ajax({
type: 'POST',
url: Init.path.deleteConnection,
data: requestData,
dataType: 'json',
context: connections
}).done(function(data){
// remove connections from map
removeConnections(this);
// optional callback
if(callback){
callback();
}
}).fail(function(jqXHR, status, error){
let reason = status + ' ' + error;
Util.showNotify({title: jqXHR.status + ': deleteSystem', text: reason, type: 'warning'});
$(document).setProgramStatus('problem');
});
}
);
}
};
@@ -2181,7 +2096,7 @@ define([
newJsPlumbInstance.bind('connectionDetached', function(info, e){
// a connection is manually (drag&drop) detached! otherwise this event should not be send!
let connection = info.connection;
$().deleteConnections([connection]);
MapUtil.deleteConnections([connection]);
});
newJsPlumbInstance.bind('checkDropAllowed', function(params){

View File

@@ -4,7 +4,9 @@
define([
'jquery',
'app/init',
'app/util'
'app/util',
'mousewheel',
'customScrollbar'
], ($, Init, Util) => {
'use strict';

View File

@@ -56,47 +56,6 @@ define([
'- DPS and Logistic ships needed'
};
/**
* save a new system and add it to the map
* @param requestData
* @param context
* @param callback
*/
let saveSystem = (requestData, context, callback) => {
$.ajax({
type: 'POST',
url: Init.path.saveSystem,
data: requestData,
dataType: 'json',
context: context
}).done(function(responseData){
let newSystemData = responseData.systemData;
if( !$.isEmptyObject(newSystemData) ){
Util.showNotify({title: 'New system', text: newSystemData.name, type: 'success'});
callback(newSystemData);
}
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(){
this.systemDialog.find('.modal-content').hideLoadingAnimation();
});
};
/**
* open "new system" dialog and add the system to map
* optional the new system is connected to a "sourceSystem" (if available)
@@ -256,7 +215,7 @@ define([
// get form Values
let form = this.find('form');
let systemDialogData = $(form).getFormValues();
let formData = $(form).getFormValues();
// validate form
form.validator('validate');
@@ -288,27 +247,31 @@ define([
};
}
systemDialogData.position = newPosition;
formData.position = newPosition;
formData.mapId = mapId;
// ----------------------------------------------------------------------------------------
let requestData = {
systemData: systemDialogData,
mapData: {
id: mapId
}
};
this.find('.modal-content').showLoadingAnimation();
saveSystem(requestData, {
systemDialog: this
}, (newSystemData) => {
// success callback
callback(map, newSystemData, sourceSystem);
Util.request('PUT', 'system', [], formData, {
systemDialog: systemDialog,
formElement: form,
map: map,
sourceSystem: sourceSystem
}, context => {
// always do
context.systemDialog.find('.modal-content').hideLoadingAnimation();
}).then(
payload => {
Util.showNotify({title: 'New system', text: payload.data.name, type: 'success'});
callback(payload.context.map, payload.data, payload.context.sourceSystem);
bootbox.hideAll();
},
Util.handleAjaxErrorResponse
);
bootbox.hideAll();
});
return false;
}
}
@@ -695,36 +658,29 @@ define([
*/
let deleteSystems = (map, systems = [], callback = (systems) => {}) => {
let mapContainer = $( map.getContainer() );
let systemIds = systems.map(system => $(system).data('id'));
$.ajax({
type: 'POST',
url: Init.path.deleteSystem,
data: {
mapId: mapContainer.data('id'),
systemIds: systems.map( system => $(system).data('id') )
Util.request('DELETE', 'system', systemIds, {
mapId: mapContainer.data('id')
}, {
map: map,
systems: systems
}).then(
payload => {
// check if all systems were deleted that should get deleted
let deletedSystems = payload.context.systems.filter(
function(system){
return this.indexOf( $(system).data('id') ) !== -1;
}, payload.data
);
// remove systems from map
removeSystems(payload.context.map, deletedSystems);
callback(deletedSystems);
},
dataType: 'json',
context: {
map: map,
systems: systems
}
}).done(function(data){
// check if all systems were deleted that should get deleted
let deletedSystems = this.systems.filter(
function(system){
return this.indexOf( $(system).data('id') ) !== -1;
}, data.deletedSystemIds
);
// remove systems from map
removeSystems(this.map, deletedSystems);
callback(deletedSystems);
}).fail(function(jqXHR, status, error){
let reason = status + ' ' + error;
Util.showNotify({title: jqXHR.status + ': deleteSystem', text: reason, type: 'warning'});
$(document).setProgramStatus('problem');
});
Util.handleAjaxErrorResponse
);
};
/**
@@ -733,7 +689,7 @@ define([
* @param systems
*/
let removeSystems = (map, systems) => {
let removeSystemCallbak = deleteSystem => {
let removeSystemCallback = deleteSystem => {
map.remove(deleteSystem);
};
@@ -741,10 +697,10 @@ define([
system = $(system);
// check if system is "active"
if( system.hasClass(config.systemActiveClass) ){
if(system.hasClass(config.systemActiveClass)){
delete Init.currentSystemData;
// get parent Tab Content and fire clear modules event
let tabContentElement = MapUtil.getTabContentElementByMapElement( system );
let tabContentElement = MapUtil.getTabContentElementByMapElement(system);
$(tabContentElement).trigger('pf:removeSystemModules');
}
@@ -759,7 +715,7 @@ define([
// remove system
system.velocity('transition.whirlOut', {
duration: Init.animationSpeed.mapDeleteSystem,
complete: removeSystemCallbak
complete: removeSystemCallback
});
}
};

View File

@@ -350,6 +350,67 @@ define([
return data;
};
/**
* delete a connection and all related data
* @param connections
* @param callback
*/
let deleteConnections = (connections, callback) => {
if(connections.length > 0){
// remove connections from map
let removeConnections = connections => {
for(let connection of connections){
connection._jsPlumb.instance.detach(connection, {fireEvent: false});
}
};
// prepare delete request
let map = connections[0]._jsPlumb.instance;
let mapContainer = $(map.getContainer());
// connectionIds for delete request
let connectionIds = [];
for(let connection of connections){
let connectionId = connection.getParameter('connectionId');
// drag&drop a new connection does not have an id yet, if connection is not established correct
if(connectionId !== undefined){
connectionIds.push(connectionId);
}
}
if(connectionIds.length > 0){
Util.request('DELETE', 'connection', connectionIds, {
mapId: mapContainer.data('id')
}, {
connections: connections
}).then(
payload => {
// check if all connections were deleted that should get deleted
let deletedConnections = payload.context.connections.filter(
function(connection){
// if a connection is manually (drag&drop) detached, the jsPlumb instance does not exist any more
// connection is already deleted!
return (
connection._jsPlumb &&
this.indexOf( connection.getParameter('connectionId') ) !== -1
);
}, payload.data
);
// remove connections from map
removeConnections(deletedConnections);
if(callback){
callback();
}
},
Util.handleAjaxErrorResponse
);
}
}
};
/**
* get connection related data from a connection
* -> data requires a signature bind to that connection
@@ -1682,6 +1743,7 @@ define([
setConnectionWHStatus: setConnectionWHStatus,
getScopeInfoForConnection: getScopeInfoForConnection,
getDataByConnections: getDataByConnections,
deleteConnections: deleteConnections,
getConnectionDataFromSignatures: getConnectionDataFromSignatures,
getEndpointOverlayContent: getEndpointOverlayContent,
getTabContentElementByMapElement: getTabContentElementByMapElement,

View File

@@ -79,15 +79,21 @@ define([
let reason = status + ' ' + jqXHR.status + ': ' + error;
let errorData = [];
let redirect = false; // redirect user to other page e.g. login
let reload = true; // reload current page (default: true)
if(jqXHR.responseJSON){
// handle JSON
let errorObj = jqXHR.responseJSON;
let responseObj = jqXHR.responseJSON;
if(
errorObj.error &&
errorObj.error.length > 0
responseObj.error &&
responseObj.error.length > 0
){
errorData = errorObj.error;
errorData = responseObj.error;
}
if(responseObj.reroute){
redirect = responseObj.reroute;
}
}else{
// handle HTML
@@ -98,7 +104,13 @@ define([
}
console.error(' ↪ %s Error response: %o', jqXHR.url, errorData);
$(document).trigger('pf:shutdown', {status: jqXHR.status, reason: reason, error: errorData});
$(document).trigger('pf:shutdown', {
status: jqXHR.status,
reason: reason,
error: errorData,
redirect: redirect,
reload: reload
});
};
// map init functions =========================================================================================

View File

@@ -787,12 +787,14 @@ define([
label: '<i class="fas fa-fw fa-sync"></i> restart',
className: ['btn-primary'].join(' '),
callback: function(){
// check if error was 5xx -> reload page
// -> else try to logout -> ajax request
if(data.status >= 500 && data.status < 600){
// redirect to login
window.location = '../';
if(data.redirect) {
// ... redirect user to e.g. login form page ...
Util.redirect(data.redirect, ['logout']);
}else if(data.reload){
// ... or reload current page ...
location.reload();
}else{
// ... fallback try to logout user
documentElement.trigger('pf:menuLogout');
}
}
@@ -811,12 +813,9 @@ define([
};
// add error information (if available)
if(
data.error &&
data.error.length
){
for(let i = 0; i < data.error.length; i++){
options.content.textSmaller.push(data.error[i].message);
if(data.error && data.error.length){
for(let error of data.error){
options.content.textSmaller.push(error.message);
}
}

View File

@@ -76,7 +76,7 @@ define([
dialogElement.find('.modal-content').hideLoadingAnimation();
if(responseData.reroute !== undefined){
Util.redirect(responseData.reroute, []);
Util.redirect(responseData.reroute);
}else if(
responseData.error &&
responseData.error.length > 0

View File

@@ -22,7 +22,7 @@ define([
* show jump info dialog
*/
$.fn.showJumpInfoDialog = function(){
requirejs(['text!templates/dialog/jump_info.html', 'mustache'], (template, Mustache) => {
requirejs(['text!templates/dialog/jump_info.html', 'mustache', 'datatables.loader'], (template, Mustache) => {
let data = {
config: config,
wormholes: Object.keys(Init.wormholes).map(function(k){ return Init.wormholes[k]; }), // convert Json to array

View File

@@ -654,7 +654,7 @@ define([
// deleteSignatures(row);
let connection = $().getConnectionById(mapData.config.id, rowData.id);
$().deleteConnections([connection], function(){
MapUtil.deleteConnections([connection], () => {
// callback function after ajax "delete" success
// remove table row
tempTableElement.DataTable().rows(deleteRowElement).remove().draw();

View File

@@ -404,7 +404,7 @@ define([
*/
let getStatsData = function(requestData, context){
context.dynamicArea = $('#' + config.statsContainerId + ' .pf-dynamic-area');
context.dynamicArea = $('#' + config.statsContainerId + ' .' + Util.config.dynamicAreaClass);
context.dynamicArea.showLoadingAnimation();
$.ajax({
@@ -753,7 +753,7 @@ define([
* show activity stats dialog
*/
$.fn.showStatsDialog = function(){
requirejs(['text!templates/dialog/stats.html', 'mustache', 'datatables.loader'], function(template, Mustache){
requirejs(['text!templates/dialog/stats.html', 'mustache', 'datatables.loader'], (template, Mustache) => {
// get current statistics map settings
let logActivityEnabled = false;
let activeMap = Util.getMapModule().getActiveMap();
@@ -806,13 +806,7 @@ define([
title: 'Statistics',
message: content,
size: 'large',
show: false,
buttons: {
close: {
label: 'close',
className: 'btn-default'
}
}
show: false
});
// model events

View File

@@ -2,7 +2,6 @@
* system effects dialog
*/
define([
'jquery',
'app/init',
@@ -10,12 +9,14 @@ define([
'app/render',
'bootbox',
'app/map/util'
], function($, Init, Util, Render, bootbox, MapUtil){
], ($, Init, Util, Render, bootbox, MapUtil) => {
'use strict';
let config = {
// system effect dialog
systemEffectDialogWrapperClass: 'pf-system-effect-dialog-wrapper' // class for system effect dialog
systemEffectDialogClass: 'pf-system-effect-dialog', // class for system effect dialog
systemEffectTableClass: 'pf-system-effect-table' // Table class for effect tables
};
let cache = {
@@ -26,20 +27,23 @@ define([
* show system effect dialog
*/
$.fn.showSystemEffectInfoDialog = function(){
requirejs(['datatables.loader'], () => {
// cache table structure
if(!cache.systemEffectDialog){
let dialogWrapperElement = $('<div>', {
class: config.systemEffectDialogWrapperClass
let rowElement = $('<div>', {
class: 'row'
});
let systemEffectData = Util.getSystemEffectData();
$.each( systemEffectData.wh, function(effectName, effectData ){
// last active (hover) table columnIndex
let lastActiveColIndex = null;
let colCount = 0;
for(let [effectName, effectData] of Object.entries(systemEffectData.wh)){
colCount++;
let table = $('<table>', {
class: ['table', 'table-condensed'].join(' ')
class: ['compact', 'stripe', 'order-column', 'row-border', config.systemEffectTableClass].join(' ')
});
let tbody = $('<tbody>');
@@ -51,8 +55,7 @@ define([
let systemEffectName = MapUtil.getEffectInfoForSystem(effectName, 'name');
let systemEffectClass = MapUtil.getEffectInfoForSystem(effectName, 'class');
$.each( effectData, function(areaId, areaData ){
for(let [areaId, areaData] of Object.entries(effectData)){
let systemType = 'C' + areaId;
let securityClass = Util.getSecurityClassForSystem( systemType );
@@ -61,20 +64,20 @@ define([
thead.append( rows[0] );
rows[0].append(
$('<td>').html('&nbsp;&nbsp;' + systemEffectName).prepend(
$('<th>').html('&nbsp;&nbsp;' + systemEffectName).prepend(
$('<i>', {
class: ['fas', 'fa-square', 'fa-fw', systemEffectClass].join(' ')
class: ['fas', 'fa-square', systemEffectClass].join(' ')
})
)
);
}
rows[0].append( $('<td>', {
rows[0].append( $('<th>', {
class: ['text-right', 'col-xs-1', securityClass].join(' ')
}).text( systemType ));
$.each( areaData, function(i, data ){
for(let [i, data] of Object.entries(areaData)){
i = parseInt(i);
if(areaId === '1'){
rows.push( $('<tr>') );
tbody.append(rows[i + 1]);
@@ -87,21 +90,100 @@ define([
rows[i + 1].append( $('<td>', {
class: 'text-right'
}).text( data.value ));
});
}
}
let colElement = $('<div>', {
class: ['col-md-6'].join(' ')
}).append(
$('<div>', {
class: [Util.config.dynamicAreaClass].join(' ')
}).append(
table.append(thead).append(tbody)
)
);
rowElement.append(colElement);
// add clearfix after even col count
if(colCount % 2 === 0){
rowElement.append(
$('<div>', {
class: ['clearfix', 'visible-md', 'visible-lg'].join(' ')
})
);
}
cache.systemEffectDialog = rowElement;
}
let effectsDialog = bootbox.dialog({
className: config.systemEffectDialogClass,
title: 'System effect information',
message: cache.systemEffectDialog,
size: 'large',
show: false
});
effectsDialog.on('show.bs.modal', function(e){
let headerAll = $();
let columnsAll = $();
let removeColumnHighlight = () => {
headerAll.removeClass('colHighlight');
columnsAll.removeClass('colHighlight');
};
let tableApis = $(this).find('table').DataTable({
pageLength: -1,
paging: false,
lengthChange: false,
ordering: false,
searching: false,
info: false,
columnDefs: [],
data: null, // use DOM data overwrites [] default -> data.loader.js
initComplete: function(settings, json){
let tableApi = this.api();
tableApi.tables().nodes().to$().on('mouseover', 'td', function(){
// inside table cell -> get current hover colIndex
let colIndex = tableApi.cell(this).index().column;
if(colIndex !== lastActiveColIndex){
removeColumnHighlight();
lastActiveColIndex = colIndex;
if(colIndex > 0){
// active column changed -> highlight same colIndex on other tables
let tableApis = $.fn.dataTable.tables({ visible: false, api: true })
.tables('.' + config.systemEffectTableClass);
let columns = tableApis.columns(colIndex);
columns.header().flatten().to$().addClass('colHighlight');
columns.nodes().flatten().to$().addClass('colHighlight');
}
}
}).on('mouseleave', function(){
// no longer inside table
lastActiveColIndex = null;
removeColumnHighlight();
});
}
});
dialogWrapperElement.append(table.append(thead).append(tbody));
cache.systemEffectDialog = dialogWrapperElement;
// table cells will not change so we should cache them once
headerAll = tableApis.columns().header().to$();
columnsAll = tableApis.cells().nodes().to$();
});
}
bootbox.dialog({
title: 'System effect information',
message: cache.systemEffectDialog
effectsDialog.on('hide.bs.modal', function(e){
// destroy logTable
$(this).find('table').DataTable().destroy(true);
});
effectsDialog.modal('show');
});
};
});

View File

@@ -12,7 +12,8 @@ define([
let config = {
// Select2
resultOptionImageClass: 'pf-result-image' // class for Select2 result option entry with image
resultOptionImageClass: 'pf-result-image', // class for Select2 result option entry with image
select2ImageLazyLoadClass: 'pf-select2-image-lazyLoad' // class for Select2 result images that should be lazy loaded
};
/**
@@ -60,7 +61,7 @@ define([
}
if(imagePath){
thumb = '<img src="' + imagePath + '" style="max-width: 100%" />';
thumb = '<img class="' + config.select2ImageLazyLoadClass + '" data-original="' + imagePath + '" style="max-width: 100%"/>';
}else if(iconName){
thumb = '<i class="fas fa-fw ' + iconName + '" ></i>';
}
@@ -150,8 +151,29 @@ define([
let markup = '';
if(parts.length === 2){
// wormhole data -> 2 columns
let styleClass = ['pf-fake-connection-text'];
if(state.metaData){
let metaData = state.metaData;
if(metaData.type){
let type = metaData.type;
if(type.includes('wh_eol')){
styleClass.push('pf-wh-eol');
}
if(type.includes('wh_reduced')){
styleClass.push('pf-wh-reduced');
}
if(type.includes('wh_critical')){
styleClass.push('pf-wh-critical');
}
if(type.includes('frigate')){
styleClass.push('pf-wh-frig');
}
}
}
let securityClass = Util.getSecurityClassForSystem(parts[1]);
markup += '<span>' + parts[0] + '</span>&nbsp;&nbsp;';
markup += '<span class="' + styleClass.join(' ') + '">' + parts[0] + '</span>&nbsp;&nbsp;';
markup += '<span class="' + securityClass + '">' + parts[1] + '</span>';
}else{
markup += '<span>' + state.text + '</span>';
@@ -564,6 +586,7 @@ define([
return group;
});
},*/
disabled: options.hasOwnProperty('disabled') ? options.disabled : false,
allowClear: options.maxSelectionLength <= 1,
maximumSelectionLength: options.maxSelectionLength,
templateResult: formatCategoryTypeResultData
@@ -605,6 +628,7 @@ define([
return {
id: type.id,
text: type.name,
mass: type.hasOwnProperty('mass') ? type.mass : null,
groupId: this.groupId,
categoryId: this.categoryId,
categoryType: this.categoryType
@@ -783,6 +807,11 @@ define([
return this.each(function(){
let selectElement = $(this);
// remove existing <options> from DOM in case "data" is explicit set
if(options.data){
selectElement.empty();
}
selectElement.select2(options);
// initial open dropDown

View File

@@ -6,8 +6,9 @@ define([
'jquery',
'app/init',
'app/util',
'bootbox',
'app/map/util'
], ($, Init, Util, MapUtil) => {
], ($, Init, Util, bootbox, MapUtil) => {
'use strict';
let config = {
@@ -30,7 +31,6 @@ define([
connectionInfoPanelClass: 'pf-connection-info-panel', // class for connection info panels
connectionInfoPanelId: 'pf-connection-info-panel-', // id prefix for connection info panels
dynamicAreaClass: 'pf-dynamic-area', // class for "dynamic" areas
controlAreaClass: 'pf-module-control-area', // class for "control" areas
// info table
@@ -47,9 +47,17 @@ define([
connectionInfoTableCellMassLeftClass: 'pf-connection-info-mass-left', // class for "mass left" table cell
// dataTable
tableToolbarCondensedClass: 'pf-dataTable-condensed-toolbar', // class for condensed table toolbar
connectionInfoTableClass: 'pf-connection-info-table', // class for connection tables
tableCellImageClass: 'pf-table-image-cell', // class for table "image" cells
tableCellCounterClass: 'pf-table-counter-cell', // class for table "counter" cells
tableCellActionClass: 'pf-table-action-cell', // class for "action" cells
// connection dialog
connectionDialogId: 'pf-connection-info-dialog', // id for "connection" dialog
typeSelectId: 'pf-connection-info-dialog-type-select', // id for "ship type" select
shipMassId: 'pf-connection-info-dialog-mass', // id for "ship mass" input
characterSelectId: 'pf-connection-info-dialog-character-select', // id for "character" select
// config
showShip: true // default for "show current ship mass" toggle
@@ -110,7 +118,7 @@ define([
*/
let getInfoPanelControl = (mapId) => {
let connectionElement = getConnectionElement(mapId, 0).append($('<div>', {
class: [config.dynamicAreaClass, config.controlAreaClass].join(' '),
class: [Util.config.dynamicAreaClass, config.controlAreaClass].join(' '),
html: '<i class="fas fa-fw fa-plus"></i>&nbsp;add connection&nbsp;&nbsp;<kbd>ctrl</kbd>&nbsp;+&nbsp;<kbd>click</kbd>'
}));
@@ -128,7 +136,7 @@ define([
let scopeLabel = MapUtil.getScopeInfoForConnection(connectionData.scope, 'label');
let element = $('<div>', {
class: [config.dynamicAreaClass, config.controlAreaClass].join(' ')
class: [Util.config.dynamicAreaClass, config.controlAreaClass].join(' ')
}).append(
$('<table>', {
class: ['table', 'table-condensed', 'pf-table-fixed', config.moduleTableClass].join(' ')
@@ -512,6 +520,35 @@ define([
return moduleElement.find('.' + config.connectionInfoPanelClass).not('#' + getConnectionElementId(0));
};
/**
* enrich connectionData with "logs" data (if available) and other "missing" data
* @param connectionsData
* @param newConnectionsData
* @returns {*}
*/
let enrichConnectionsData = (connectionsData, newConnectionsData) => {
for(let i = 0; i < connectionsData.length; i++){
for(let newConnectionData of newConnectionsData){
if(connectionsData[i].id === newConnectionData.id){
// copy some missing data
connectionsData[i].character = newConnectionData.character;
connectionsData[i].created = newConnectionData.created;
connectionsData[i].type = newConnectionData.type;
// check for mass logs and copy data
if(newConnectionData.logs && newConnectionData.logs.length){
connectionsData[i].logs = newConnectionData.logs;
}
// check for signatures and copy data
if(newConnectionData.signatures && newConnectionData.signatures.length){
connectionsData[i].signatures = newConnectionData.signatures;
}
break;
}
}
}
return connectionsData;
};
/**
* request connection log data
* @param requestData
@@ -531,24 +568,7 @@ define([
dataType: 'json',
context: context
}).done(function(connectionsData){
// enrich connectionData with "logs" data (if available) and other "missing" data
for(let i = 0; i < this.connectionsData.length; i++){
for(let connectionData of connectionsData){
if(this.connectionsData[i].id === connectionData.id){
// copy some missing data
this.connectionsData[i].created = connectionData.created;
// check for mass logs and copy data
if(connectionData.logs && connectionData.logs.length){
this.connectionsData[i].logs = connectionData.logs;
}
// check for signatures and copy data
if(connectionData.signatures && connectionData.signatures.length){
this.connectionsData[i].signatures = connectionData.signatures;
}
break;
}
}
}
this.connectionsData = enrichConnectionsData(this.connectionsData, connectionsData);
callback(this.moduleElement, this.connectionsData);
}).always(function(){
@@ -593,9 +613,9 @@ define([
*/
let addConnectionsData = (moduleElement, connectionsData) => {
let getRowIndexesByData = (dataTable, colName, value) => {
return dataTable.rows().eq(0).filter((rowIdx) => {
return (dataTable.cell(rowIdx, colName + ':name').data() === value);
let getRowIndexesByData = (tableApi, colName, value) => {
return tableApi.rows().eq(0).filter((rowIdx) => {
return (tableApi.cell(rowIdx, colName + ':name').data() === value);
});
};
@@ -608,42 +628,47 @@ define([
connectionInfoElement.data('connectionData', connectionData);
// update dataTable ---------------------------------------------------------------
let dataTable = connectionElement.find('.dataTable').dataTable().api();
let tableApi = connectionElement.find('.dataTable').dataTable().api();
if(connectionData.logs && connectionData.logs.length > 0){
for(let i = 0; i < connectionData.logs.length; i++){
let rowData = connectionData.logs[i];
let row = null;
let rowNew = null;
let animationStatus = null;
let indexes = getRowIndexesByData(dataTable, 'index', rowData.id);
let indexes = getRowIndexesByData(tableApi, 'index', rowData.id);
if(indexes.length === 0){
// row not found -> add new row
row = dataTable.row.add( rowData );
rowNew = tableApi.row.add(rowData);
animationStatus = 'added';
}
/* else{
// we DON´t expect changes -> no row update)
}else{
// update row with FIRST index
//row = dataTable.row( parseInt(indexes[0]) );
// update row data
//row.data(connectionData.logs[i]);
//animationStatus = 'changed';
} */
let row = tableApi.row( parseInt(indexes[0]));
let rowDataCurrent = row.data();
// check if row data changed
if(rowDataCurrent.updated.updated !== rowData.updated.updated){
// ... row changed -> delete old and re-add
// -> cell actions might have changed
row.remove();
rowNew = tableApi.row.add(rowData);
animationStatus = 'changed';
}
}
if(
animationStatus !== null &&
row.length > 0
rowNew.length > 0
){
row.nodes().to$().data('animationStatus', animationStatus);
rowNew.nodes().to$().data('animationStatus', animationStatus);
}
}
}else{
// clear table or leave empty
dataTable.clear();
tableApi.clear();
}
// redraw dataTable
dataTable.draw(false);
tableApi.draw(false);
}
}
};
@@ -664,11 +689,46 @@ define([
let table = $('<table>', {
class: ['compact', 'stripe', 'order-column', 'row-border', 'nowrap', config.connectionInfoTableClass].join(' ')
}).append('<tfoot><tr><th></th><th></th><th></th><th></th><th></th></tr></tfoot>');
}).append('<tfoot><tr><th></th><th></th><th></th><th></th><th></th><th></th><th></th></tr></tfoot>');
connectionElement.append(table);
// init empty table
let logTable = table.DataTable({
dom: '<"container-fluid"' +
'<"row ' + config.tableToolbarCondensedClass + '"' +
'<"col-xs-5"i><"col-xs-5"p><"col-xs-2 text-right"B>>' +
'<"row"tr>>',
buttons: {
name: 'tableTools',
buttons: [
{
name: 'addLog',
className: config.moduleHeadlineIconClass,
text: '<i class="fa fa-plus"></i>',
action: function(e, tableApi, node, conf){
let logData = {};
// pre-fill form with current character data (if available)
let currentUserData = Util.getCurrentUserData();
if(currentUserData && currentUserData.character){
logData.character = {
id: currentUserData.character.id,
name: currentUserData.character.name
};
if(currentUserData.character.log){
logData.ship = {
id: currentUserData.character.log.ship.typeId,
name: currentUserData.character.log.ship.typeName,
mass: currentUserData.character.log.ship.mass
};
}
}
showLogDialog(moduleElement, connectionElement, connectionData, logData);
}
}
]
},
pageLength: 8,
paging: true,
pagingType: 'simple',
@@ -679,11 +739,14 @@ define([
searching: false,
hover: false,
autoWidth: false,
// rowId: 'systemTo',
language: {
emptyTable: 'No jumps recorded',
info: '_START_ to _END_ of _MAX_',
infoEmpty: ''
info: '_START_ - _END_ of _MAX_',
infoEmpty: '',
paginate: {
previous: '',
next: ''
}
},
columnDefs: [
{
@@ -693,10 +756,25 @@ define([
orderable: false,
searchable: false,
width: 20,
class: 'text-center',
data: 'id'
className: ['text-center', 'txt-color'].join(' '),
data: 'id',
createdCell: function(cell, cellData, rowData, rowIndex, colIndex){
if(
!rowData.record ||
(rowData.updated.updated !== rowData.created.created)
){
// log was manually modified or added
$(cell)
.addClass(Util.config.helpClass)
.addClass( 'txt-color-orange').tooltip({
container: 'body',
title: 'added/updated manually'
});
}
}
},{
targets: 1,
name: 'ship',
title: '',
width: 26,
orderable: false,
@@ -716,16 +794,17 @@ define([
}
},{
targets: 2,
name: 'character',
title: '',
width: 26,
orderable: false,
className: [Util.config.helpDefaultClass, 'text-center', config.tableCellImageClass].join(' '),
data: 'created.character',
data: 'character',
render: {
_: function(data, type, row){
let value = data.name;
_: (cellData, type, rowData, meta) => {
let value = cellData.name;
if(type === 'display'){
value = '<img src="' + Init.url.ccpImageServer + '/Character/' + data.id + '_32.jpg" title="' + value + '" data-toggle="tooltip" />';
value = '<img src="' + Init.url.ccpImageServer + '/Character/' + cellData.id + '_32.jpg" title="' + value + '" data-toggle="tooltip" />';
}
return value;
}
@@ -735,20 +814,26 @@ define([
}
},{
targets: 3,
name: 'mass',
title: 'mass',
className: ['text-right'].join(' ') ,
data: 'ship.mass',
render: {
_: function(data, type, row){
let value = data;
_: (cellData, type, rowData, meta) => {
let value = cellData;
if(type === 'display'){
value = Util.formatMassValue(value);
if(!rowData.active){
// log is "deleted"
value = '<span class="pf-font-line-through txt-color txt-color-red">' + value + '</span>';
}
}
return value;
}
}
},{
targets: 4,
name: 'created',
title: 'log',
width: 55,
className: ['text-right', config.tableCellCounterClass].join(' '),
@@ -756,6 +841,108 @@ define([
createdCell: function(cell, cellData, rowData, rowIndex, colIndex){
$(cell).initTimestampCounter('d');
}
},{
targets: 5,
name: 'edit',
title: '',
orderable: false,
searchable: false,
width: 10,
className: ['text-center', config.tableCellActionClass, config.moduleHeadlineIconClass].join(' '),
data: null,
render: {
display: data => {
let icon = '';
if(data.active){
icon = '<i class="fas fa-pen"></i>';
}
return icon;
}
},
createdCell: function(cell, cellData, rowData, rowIndex, colIndex){
let tableApi = this.api();
if($(cell).is(':empty')){
$(cell).removeClass(config.tableCellActionClass + ' ' + config.moduleHeadlineIconClass);
}else{
$(cell).on('click', function(e){
showLogDialog(moduleElement, connectionElement, connectionData, rowData);
});
}
}
},{
targets: 6,
name: 'delete',
title: '',
orderable: false,
searchable: false,
width: 10,
className: ['text-center', config.tableCellActionClass].join(' '),
data: 'active',
render: {
display: data => {
let val = '<i class="fas fa-plus"></i>';
if(data){
val = '<i class="fas fa-times txt-color txt-color-redDarker"></i>';
}
return val;
}
},
createdCell: function(cell, cellData, rowData, rowIndex, colIndex){
let tableApi = this.api();
if(rowData.active){
let confirmationSettings = {
container: 'body',
placement: 'left',
btnCancelClass: 'btn btn-sm btn-default',
btnCancelLabel: 'cancel',
btnCancelIcon: 'fas fa-fw fa-ban',
title: 'delete jump log',
btnOkClass: 'btn btn-sm btn-danger',
btnOkLabel: 'delete',
btnOkIcon: 'fas fa-fw fa-times',
onConfirm : function(e, target){
// get current row data (important!)
// -> "rowData" param is not current state, values are "on createCell()" state
rowData = tableApi.row($(cell).parents('tr')).data();
connectionElement.find('table').showLoadingAnimation();
Util.request('DELETE', 'log', rowData.id, {}, {
connectionElement: connectionElement
}, requestAlways)
.then(
payload => {
addConnectionsData(moduleElement, enrichConnectionsData([connectionData], payload.data));
},
Util.handleAjaxErrorResponse
);
}
};
// init confirmation dialog
$(cell).confirmation(confirmationSettings);
}else {
$(cell).on('click', function(e){
connectionElement.find('table').showLoadingAnimation();
let requestData = {
active: 1
};
Util.request('PATCH', 'log', rowData.id, requestData, {
connectionElement: connectionElement
}, requestAlways)
.then(
payload => {
addConnectionsData(moduleElement, enrichConnectionsData([connectionData], payload.data));
},
Util.handleAjaxErrorResponse
);
});
}
}
}
],
drawCallback: function(settings){
@@ -773,27 +960,34 @@ define([
},
footerCallback: function(row, data, start, end, display ){
let api = this.api();
let sumColumnIndexes = [3];
let tableApi = this.api();
let sumColumnIndexes = ['mass:name', 'delete:name'];
// column data for "sum" columns over this page
let pageTotalColumns = api
let pageTotalColumns = tableApi
.columns( sumColumnIndexes, { page: 'all'} )
.data();
// sum columns for "total" sum
pageTotalColumns.each((colData, index) => {
pageTotalColumns[index] = colData.reduce((a, b) => {
return parseInt(a) + parseInt(b);
pageTotalColumns.each((colData, colIndex) => {
pageTotalColumns[colIndex] = colData.reduce((sum, val, rowIndex) => {
// sum "mass" (colIndex 0) only if not "deleted" (colIndex 1)
if(colIndex === 0 && pageTotalColumns[1][rowIndex]){
return sum + parseInt(val);
}else{
return sum;
}
}, 0);
});
$(sumColumnIndexes).each((index, value) => {
$( api.column( value ).footer() ).text( Util.formatMassValue(pageTotalColumns[index]) );
sumColumnIndexes.forEach((colSelector, index) => {
// only "mass" column footer needs updates
if(colSelector === 'mass:name'){
$(tableApi.column(colSelector).footer()).text( Util.formatMassValue(pageTotalColumns[index]) );
// save mass for further reCalculation of "info" table
connectionElement.find('.' + config.connectionInfoTableCellMassLogClass).data('mass', pageTotalColumns[index]);
// save mass for further reCalculation of "info" table
connectionElement.find('.' + config.connectionInfoTableCellMassLogClass).data('mass', pageTotalColumns[index]);
}
});
// calculate "info" table -----------------------------------------------------
@@ -806,14 +1000,135 @@ define([
logTable.on('order.dt search.dt', function(){
let pageInfo = logTable.page.info();
logTable.column(0, {search:'applied', order:'applied'}).nodes().each((cell, i) => {
let content = (pageInfo.recordsTotal - i) + '.&nbsp;&nbsp;';
logTable.column('index:name', {search:'applied', order:'applied'}).nodes().each((cell, i) => {
let content = (pageInfo.recordsTotal - i) + '.';
$(cell).html(content);
});
});
}
};
/**
*
* @param context
*/
let requestAlways = (context) => {
context.connectionElement.find('table').hideLoadingAnimation();
};
/**
* show jump log dialog
* @param moduleElement
* @param connectionElement
* @param connectionData
* @param logData
*/
let showLogDialog = (moduleElement, connectionElement, connectionData, logData = {}) => {
let data = {
id: config.connectionDialogId,
typeSelectId: config.typeSelectId,
shipMassId: config.shipMassId,
characterSelectId: config.characterSelectId,
logData: logData,
massFormat: () => {
return (val, render) => {
return (parseInt(render(val) || 0) / 1000) || '';
};
}
};
requirejs(['text!templates/dialog/connection_log.html', 'mustache'], (template, Mustache) => {
let content = Mustache.render(template, data);
let connectionDialog = bootbox.dialog({
title: 'Jump log',
message: content,
show: false,
buttons: {
close: {
label: 'cancel',
className: 'btn-default'
},
success: {
label: '<i class="fas fa-fw fa-check"></i>&nbsp;save',
className: 'btn-success',
callback: function(){
let form = this.find('form');
// validate form
form.validator('validate');
// check whether the form is valid
let formValid = form.isValidForm();
if(formValid){
// get form data
let formData = form.getFormValues();
formData.id = Util.getObjVal(logData, 'id') || 0;
formData.connectionId = Util.getObjVal(connectionData, 'id') || 0;
formData.shipTypeId = Util.getObjVal(formData, 'shipTypeId') || 0;
formData.shipMass = parseInt((Util.getObjVal(formData, 'shipMass') || 0) * 1000);
formData.characterId = Util.getObjVal(formData, 'characterId') || 0;
// we need some "additional" form data from the Select2 dropdown
// -> data is required on the backend side
let formDataShip = form.find('#' + config.typeSelectId).select2('data');
let formDataCharacter = form.find('#' + config.characterSelectId).select2('data');
formData.shipTypeName = formDataShip.length ? formDataShip[0].text : '';
formData.characterName = formDataCharacter.length ? formDataCharacter[0].text : '';
let method = formData.id ? 'PATCH' : 'PUT';
Util.request(method, 'log', formData.id, formData, {
connectionElement: connectionElement,
formElement: form
}, requestAlways)
.then(
payload => {
addConnectionsData(moduleElement, enrichConnectionsData([connectionData], payload.data));
this.modal('hide');
},
Util.handleAjaxErrorResponse
);
}
return false;
}
}
}
});
connectionDialog.on('show.bs.modal', function(e){
let modalContent = $('#' + config.connectionDialogId);
// init type select live search
let selectElementType = modalContent.find('#' + config.typeSelectId);
selectElementType.initUniverseTypeSelect({
categoryIds: [6],
maxSelectionLength: 1,
selected: [Util.getObjVal(logData, 'ship.id')]
}).on('select2:select select2:unselecting', function(e){
// get ship mass from selected ship type and update mass input field
let shipMass = e.params.data ? e.params.data.mass / 1000 : '';
modalContent.find('#' + config.shipMassId).val(shipMass);
});
// init character select live search
let selectElementCharacter = modalContent.find('#' + config.characterSelectId);
selectElementCharacter.initUniverseSearch({
categoryNames: ['character'],
maxSelectionLength: 1
});
});
// show dialog
connectionDialog.modal('show');
});
};
/**
* remove connection Panel from moduleElement
* @param connectionElement

View File

@@ -51,44 +51,6 @@ define([
// max character length for system description
let maxDescriptionLength = 9000;
/**
* save system (description)
* @param requestData
* @param context
* @param callback
*/
let saveSystem = (requestData, context, callback) => {
context.descriptionArea.showLoadingAnimation();
$.ajax({
type: 'POST',
url: Init.path.saveSystem,
data: requestData,
dataType: 'json',
context: context
}).done(function(responseData){
let newSystemData = responseData.systemData;
if( !$.isEmptyObject(newSystemData) ){
callback(newSystemData);
}
if(
responseData.error &&
responseData.error.length > 0
){
for(let error of responseData.error){
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'});
}).always(function(){
this.descriptionArea.hideLoadingAnimation();
});
};
/**
* update trigger function for this module
* compare data and update module
@@ -307,21 +269,22 @@ define([
if(validDescription){
// ... valid -> save()
saveSystem({
mapData: {
id: mapId
},
systemData: {
id: systemData.id,
description: description
}
descriptionArea.showLoadingAnimation();
Util.request('PATCH', 'system', systemData.id, {
description: description
}, {
descriptionArea: descriptionArea
}, (systemData) => {
// .. save callback
context.$note.summernote('destroy');
updateModule(moduleElement, systemData);
});
}, context => {
// always do
context.descriptionArea.hideLoadingAnimation();
}).then(
payload => {
context.$note.summernote('destroy');
updateModule(moduleElement, payload.data);
},
Util.handleAjaxErrorResponse
);
}
}else{
// ... no changes -> no save()

View File

@@ -337,7 +337,7 @@ define([
data: statusData
});
// init character counter
// init char counter
let textarea = modalContent.find('#' + config.descriptionTextareaId);
let charCounter = modalContent.find('.' + config.descriptionTextareaCharCounter);
Util.updateCounter(textarea, charCounter, maxDescriptionLength);

View File

@@ -32,7 +32,6 @@ define([
systemKillboardListImgCorp: 'pf-system-killboard-img-corp', // class for all corp logos
labelRecentKillsClass: 'pf-system-killboard-label-recent', // class for "recent kills" label
dynamicAreaClass: 'pf-dynamic-area', // class for "dynamic" areas
controlAreaClass: 'pf-module-control-area', // class for "control" areas
minCountKills: 5,
@@ -292,7 +291,7 @@ define([
*/
let getControlElement = () => {
let controlElement = $('<div>', {
class: [config.dynamicAreaClass, config.controlAreaClass, config.moduleHeadlineIconClass].join(' '),
class: [Util.config.dynamicAreaClass, config.controlAreaClass, config.moduleHeadlineIconClass].join(' '),
html: '<i class="fas fa-sync"></i>&nbsp;&nbsp;load more'
});
return controlElement;

View File

@@ -405,6 +405,32 @@ define([
let newSelectOptions = [];
let connectionOptions = [];
/**
* get option data for a single connection
* @param type
* @param connectionData
* @param systemData
* @returns {{value: *, text: string, metaData: {type: *}}}
*/
let getOption = (type, connectionData, systemData) => {
let text = 'UNKNOWN';
if(type === 'source'){
text = connectionData.sourceAlias + ' - ' + systemData.security;
}else if(type === 'target'){
text = connectionData.targetAlias + ' - ' + systemData.security;
}
let option = {
value: connectionData.id,
text: text,
metaData: {
type: connectionData.type
}
};
return option;
};
for(let systemConnection of systemConnections){
let connectionData = MapUtil.getDataByConnection(systemConnection);
@@ -416,19 +442,13 @@ define([
let targetSystemData = MapUtil.getSystemData(mapId, connectionData.target);
if(targetSystemData){
// take target...
connectionOptions.push({
value: connectionData.id,
text: connectionData.targetAlias + ' - ' + targetSystemData.security
});
connectionOptions.push(getOption('target', connectionData, targetSystemData));
}
}else if(systemData.id !== connectionData.source){
let sourceSystemData = MapUtil.getSystemData(mapId, connectionData.source);
if(sourceSystemData){
// take source...
connectionOptions.push({
value: connectionData.id,
text: connectionData.sourceAlias + ' - ' + sourceSystemData.security
});
connectionOptions.push(getOption('source', connectionData, sourceSystemData));
}
}
}
@@ -1119,7 +1139,14 @@ define([
let editableConnectionOnShown = cell => {
$(cell).on('shown', function(e, editable){
let inputField = editable.input.$input;
inputField.addClass('pf-select2').initSignatureConnectionSelect();
// Select2 init would work without passing select options as "data", Select2 would grap data from DOM
// -> We want to pass "meta" data for each option into Select2 for formatting
let options = {
data: Util.convertXEditableOptionsToSelect2(editable)
};
inputField.addClass('pf-select2').initSignatureConnectionSelect(options);
});
};
@@ -1534,7 +1561,10 @@ define([
let selected = $.fn.editableutils.itemsByValue(value, sourceData);
if(selected.length && selected[0].value > 0){
let errorIcon = '<i class="fas fa-exclamation-triangle txt-color txt-color-danger hide"></i>&nbsp;';
$(this).html(FormElement.formatSignatureConnectionSelectionData({text: selected[0].text})).prepend(errorIcon);
$(this).html(FormElement.formatSignatureConnectionSelectionData({
text: selected[0].text,
metaData: selected[0].metaData
})).prepend(errorIcon);
}else{
$(this).empty();
}
@@ -2150,7 +2180,7 @@ define([
// xEditable field should not open -> on 'click'
// -> therefore disable "pointer-events" on "td" for some ms -> 'click' event is not triggered
$(this).css('pointer-events', 'none');
$(e.target.parentNode).toggleClass('selected');
$(e.target).closest('tr').toggleClass('selected');
// check delete button
checkDeleteSignaturesButton(e.data.tableApi);

View File

@@ -8,6 +8,7 @@ define([
'conf/signature_type',
'bootbox',
'localForage',
'lazyload',
'velocity',
'velocityUI',
'customScrollbar',
@@ -65,10 +66,12 @@ define([
mapClass: 'pf-map' , // class for all maps
// util
userStatusClass: 'pf-user-status', // class for player status
userStatusClass: 'pf-user-status', // class for player status
dynamicAreaClass: 'pf-dynamic-area', // class for "dynamic" areas
// select2
select2Class: 'pf-select2', // class for all "Select2" <select> elements
select2ImageLazyLoadClass: 'pf-select2-image-lazyLoad',
// animation
animationPulseSuccessClass: 'pf-animation-pulse-success', // animation class
@@ -1008,6 +1011,61 @@ define([
});
};
/**
* convert XEditable Select <option> data into Select2 data format
* -> "prepend" (empty) options get added, too
* -> "metaData" can be used to pass custom data per <option>
* @param editable
* @returns {Array}
*/
let convertXEditableOptionsToSelect2 = editable => {
let data = [];
if(editable.options){
// collect all options + "prepend" option from xEditable...
let optionsPrepend = editable.options.prepend ? editable.options.prepend : [];
let options = editable.options.source();
let optionsAll = [];
optionsAll.push(...optionsPrepend, ...options);
/**
* convert a single option into Select2 format
* @param option
* @returns {{id: *, text: *}}
*/
let convertOption = (option) => {
let data = {
id: option.value,
text: option.text
};
if(editable.value === option.value){
data.selected = true;
}
// optional "metaData" that belongs to this option
if(option.hasOwnProperty('metaData')){
data.metaData = option.metaData;
}
return data;
};
// ... transform data into Select2 data format
data = optionsAll.map(group => {
if(group.children){
group.children = group.children.map(convertOption);
}else{
group = convertOption(group);
}
return group;
});
}
return data;
};
/**
* flatten XEditable array for select fields
* @param dataArray
@@ -1058,6 +1116,9 @@ define([
// in order to make mCustomScrollbar mouseWheel enable works correctly
$(resultsWrapper).find('ul.select2-results__options').off('mousewheel');
// preload images that are not visible yet
let lazyLoadImagesOffset = 240;
resultsWrapper.mCustomScrollbar({
mouseWheel: {
enable: true,
@@ -1084,10 +1145,35 @@ define([
// -> this is because the initPassiveEvents() delegates the mouseWheel events
togglePageScroll(false);
},
onUpdate: function(a){
// whenever the scroll content updates -> init lazyLoad for potential images
$('.' + config.select2ImageLazyLoadClass).lazyload({
container: this,
threshold: lazyLoadImagesOffset,
event: 'pf:lazyLoad'
});
},
onTotalScroll: function(){
// we want to "trigger" Select2´s 'scroll' event
// in order to make its "infinite scrolling" function working
this.mcs.content.find(':first-child').trigger('scroll');
},
whileScrolling: function(){
// lazy load for images -> reduce number of calculations by % 10
if(0 === this.mcs.top % 10){
let scroller = $(this).find('.mCSB_container');
let scrollerBox = scroller.closest('.mCustomScrollBox');
scrollerBox.find('.' + config.select2ImageLazyLoadClass).filter(function(){
let $this = $(this);
if($this.attr('src') === $this.attr('data-original')) return false;
let scrollerTop = scroller.position().top;
let scrollerHeight = scrollerBox.height();
let offset = $this.closest('div').position();
return (offset.top - lazyLoadImagesOffset < scrollerHeight - scrollerTop);
}).trigger('pf:lazyLoad');
}
}
}
});
@@ -1107,7 +1193,7 @@ define([
return wrapper;
};
// global open event
// global opened event
$(document).on('select2:open', '.' + config.select2Class, function(e){
let resultsWrapper = getResultsWrapper(this);
if(resultsWrapper){
@@ -1496,6 +1582,100 @@ define([
});
};
/**
* Request data from Server
* -> This function should be used (in future) for all Ajax and REST API calls
* -> works as a "wrapper" for jQueries ajax() method
* @param action
* @param entity
* @param ids
* @param data
* @param context
* @param always
* @returns {Promise<any>}
*/
let request = (action, entity, ids = [], data = {}, context = {}, always = null) => {
let requestExecutor = (resolve, reject) => {
let payload = {
action: 'request',
name: action.toLowerCase() + entity.charAt(0).toUpperCase() + entity.slice(1)
};
// build request url --------------------------------------------------------------------------------------
let url = Init.path.api + '/' + entity;
let path = '';
if(isNaN(ids)){
if(Array.isArray(ids)){
path += '/' + ids.join(',');
}
}else{
let id = parseInt(ids, 10);
path += id ? '/' + id : '';
}
url += path;
$.ajax({
type: action,
url: url,
data: JSON.stringify(data),
contentType: 'application/json; charset=utf-8',
dataType: 'json',
context: context
}).done(function(response){
payload.data = response;
payload.context = this;
resolve(payload);
}).fail(function(jqXHR, status, error){
payload.data = {
jqXHR: jqXHR,
status: status,
error: error
};
payload.context = this;
reject(payload);
}).always(function(){
if(always){
always(this);
}
});
};
return new Promise(requestExecutor);
};
/**
* global ajax error handler -> handles .fail() requests
* @param payload
*/
let handleAjaxErrorResponse = (payload) => {
// handle only request errors
if(payload.action === 'request'){
let jqXHR = payload.data.jqXHR;
let reason = '';
if(jqXHR.responseJSON){
// ... valid JSON response
let response = jqXHR.responseJSON;
if(response.error && response.error.length > 0){
// build error notification reason from errors
reason = response.error.map(error => error.message ? error.message : error.status).join('\n');
// check if errors might belong to a HTML form -> check "context"
if(payload.context.formElement){
// show form messages e.g. validation errors
payload.context.formElement.showFormMessage(response.error);
}
}
}else{
reason = 'Invalid JSON response';
}
showNotify({title: jqXHR.status + ': ' + payload.name, text: reason, type: 'error'});
}
};
/**
* get WebSocket readyState description from ID
* https://developer.mozilla.org/de/docs/Web/API/WebSocket
@@ -2863,7 +3043,7 @@ define([
* @param url
* @param params
*/
let redirect = (url, params) => {
let redirect = (url, params = []) => {
let currentUrl = document.URL;
if(url !== currentUrl){
@@ -2975,6 +3155,8 @@ define([
stopTabBlink: stopTabBlink,
getLogInfo: getLogInfo,
ajaxSetup: ajaxSetup,
request: request,
handleAjaxErrorResponse: handleAjaxErrorResponse,
setSyncStatus: setSyncStatus,
getSyncType: getSyncType,
isXHRAborted: isXHRAborted,
@@ -3011,6 +3193,7 @@ define([
getCurrentUserInfo: getCurrentUserInfo,
getCurrentCharacterLog: getCurrentCharacterLog,
initPageScroll: initPageScroll,
convertXEditableOptionsToSelect2: convertXEditableOptionsToSelect2,
flattenXEditableSelectArray: flattenXEditableSelectArray,
getCharacterDataBySystemId: getCharacterDataBySystemId,
getNearBySystemData: getNearBySystemData,

View File

@@ -1,2 +1,2 @@
/*! Lazy Load 1.9.5 - MIT license - Copyright 2010-2015 Mika Tuupola */
/*! Lazy Load 1.9.7 - MIT license - Copyright 2010-2015 Mika Tuupola */
!function(a,b,c,d){var e=a(b);a.fn.lazyload=function(f){function g(){var b=0;i.each(function(){var c=a(this);if(!j.skip_invisible||c.is(":visible"))if(a.abovethetop(this,j)||a.leftofbegin(this,j));else if(a.belowthefold(this,j)||a.rightoffold(this,j)){if(++b>j.failure_limit)return!1}else c.trigger("appear"),b=0})}var h,i=this,j={threshold:0,failure_limit:0,event:"scroll",effect:"show",container:b,data_attribute:"original",skip_invisible:!1,appear:null,load:null,placeholder:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsQAAA7EAZUrDhsAAAANSURBVBhXYzh8+PB/AAffA0nNPuCLAAAAAElFTkSuQmCC"};return f&&(d!==f.failurelimit&&(f.failure_limit=f.failurelimit,delete f.failurelimit),d!==f.effectspeed&&(f.effect_speed=f.effectspeed,delete f.effectspeed),a.extend(j,f)),h=j.container===d||j.container===b?e:a(j.container),0===j.event.indexOf("scroll")&&h.bind(j.event,function(){return g()}),this.each(function(){var b=this,c=a(b);b.loaded=!1,(c.attr("src")===d||c.attr("src")===!1)&&c.is("img")&&c.attr("src",j.placeholder),c.one("appear",function(){if(!this.loaded){if(j.appear){var d=i.length;j.appear.call(b,d,j)}a("<img />").bind("load",function(){var d=c.attr("data-"+j.data_attribute);c.hide(),c.is("img")?c.attr("src",d):c.css("background-image","url('"+d+"')"),c[j.effect](j.effect_speed),b.loaded=!0;var e=a.grep(i,function(a){return!a.loaded});if(i=a(e),j.load){var f=i.length;j.load.call(b,f,j)}}).attr("src",c.attr("data-"+j.data_attribute))}}),0!==j.event.indexOf("scroll")&&c.bind(j.event,function(){b.loaded||c.trigger("appear")})}),e.bind("resize",function(){g()}),/(?:iphone|ipod|ipad).*os 5/gi.test(navigator.appVersion)&&e.bind("pageshow",function(b){b.originalEvent&&b.originalEvent.persisted&&i.each(function(){a(this).trigger("appear")})}),a(c).ready(function(){g()}),this},a.belowthefold=function(c,f){var g;return g=f.container===d||f.container===b?(b.innerHeight?b.innerHeight:e.height())+e.scrollTop():a(f.container).offset().top+a(f.container).height(),g<=a(c).offset().top-f.threshold},a.rightoffold=function(c,f){var g;return g=f.container===d||f.container===b?e.width()+e.scrollLeft():a(f.container).offset().left+a(f.container).width(),g<=a(c).offset().left-f.threshold},a.abovethetop=function(c,f){var g;return g=f.container===d||f.container===b?e.scrollTop():a(f.container).offset().top,g>=a(c).offset().top+f.threshold+a(c).height()},a.leftofbegin=function(c,f){var g;return g=f.container===d||f.container===b?e.scrollLeft():a(f.container).offset().left,g>=a(c).offset().left+f.threshold+a(c).width()},a.inviewport=function(b,c){return!(a.rightoffold(b,c)||a.leftofbegin(b,c)||a.belowthefold(b,c)||a.abovethetop(b,c))},a.extend(a.expr[":"],{"below-the-fold":function(b){return a.belowthefold(b,{threshold:0})},"above-the-top":function(b){return!a.belowthefold(b,{threshold:0})},"right-of-screen":function(b){return a.rightoffold(b,{threshold:0})},"left-of-screen":function(b){return!a.rightoffold(b,{threshold:0})},"in-viewport":function(b){return a.inviewport(b,{threshold:0})},"above-the-fold":function(b){return!a.belowthefold(b,{threshold:0})},"right-of-fold":function(b){return a.rightoffold(b,{threshold:0})},"left-of-fold":function(b){return!a.rightoffold(b,{threshold:0})}})}(jQuery,window,document);

View File

@@ -6,36 +6,35 @@
},
"description": "Pathfinder is a system mapping tool for EVE ONLINE",
"main": "index.php",
"dependencies": {
},
"dependencies": {},
"devDependencies": {
"ansi-colors": "^3.1.0",
"fancy-log": "1.3.x",
"file-extension": "^4.0.5",
"flat": "^4.1.0",
"gulp": "next",
"gulp-requirejs-optimize": "1.3.x",
"gulp-brotli": "1.2.x",
"gulp-bytediff": "1.0.x",
"gulp-clean-css": "^3.10.0",
"gulp-compass": "2.1.x",
"gulp-debug": "^4.0.0",
"gulp-filter": "5.1.x",
"gulp-gzip": "1.x.x",
"gulp-if": "2.0.x",
"gulp-jshint": "2.1.x",
"gulp-rename": "^1.4.0",
"gulp-requirejs-optimize": "1.3.x",
"gulp-sourcemaps": "2.6.x",
"gulp-gzip": "1.x.x",
"gulp-brotli": "1.2.x",
"gulp-uglify": "^3.0.x",
"gulp-rename": "1.2.x",
"gulp-compass": "2.1.x",
"gulp-clean-css": "3.x.x",
"gulp-bytediff": "1.0.x",
"gulp-debug": "^3.1.x",
"node-notifier": "5.2.x",
"uglify-es": "^3.0.x",
"jshint": "^2.9.x",
"gulp-uglify": "^3.0.1",
"jshint": "^2.9.6",
"jshint-stylish": "^2.x.x",
"terminal-table": "0.0.x",
"pretty-bytes": "4.0.x",
"promised-del": "1.0.x",
"flat": "2.0.x",
"lodash.padend": "4.6.x",
"node-notifier": "^5.3.0",
"pretty-bytes": "^5.1.0",
"promised-del": "1.0.x",
"slash": "2.x.x",
"file-extension": "3.1.x",
"fancy-log": "1.3.x",
"ansi-colors": "1.x.x"
"terminal-table": "0.0.x",
"uglify-es": "^3.0.x"
},
"scripts": {
"gulp": "gulp"

Binary file not shown.

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