* #84 test data dump from CREST login

* updated "credits" dialog (Google+ link)
fixed login form layout

* updated Cortex Data-Mapper

* - #84 CREST Login (WIP)
- New CREST controller
- Database restructuring
- improved type-casting for some controller functions
- New login process
- Fixed some bugs during the setup process (/setup root)
- Added CREST request caching by response headers

* pathfinder-84 [Feature Request] CREST Pilot Tracking, many smaller Bugfixes

* pathfinder-84 [Feature Request] added develop JS files

* closed #121 fixed wormhole signature type caching

* closed #120 removed map-loading animation for larger maps (same behaviour as IGB)

* closed #119 fixed wormhole signature id count

* closed #114 Added check for already existing system when adding a new one. (fixed PDO 'duplicate entry' error)

* closed #112 fixed DataTables error for missing "status" data (signature table)

* closed #111 fixed convertDataToUTC(); client side date transformation

* closed #109 fixed system TrueSec rounding

* closed #103 fixed system updated timestamp in getData()

* fixed CSS class for secStatus in Routes module

* closed #121 fixed wormhole signature type caching

* changed dateTime format from German to US format
fixed some minor bugs in signatureTable module

* closed #81 fixed "signature type" overwriting by "signature reader" update

* closed #106 added new signature_types form C5/6 wormholes (gas/ore)

* closed #129 fixed parameter hinting

* closed #131 new "route search" algorithm, added current map systems to live search, added refresh/update functionality for each found route, added bulk route refresh function, added "meta map" route search (search on multiple maps), added route "filters" (restrict search on "stargates", "wormholes", "jumpbridges"), added route "filter" for wormholes (reduced/critical wormholes)
closed #89 fixed "loop connections" on same system
#84 added error messages for "invalid" CREST "Client ID"
added "bootboxjs" (customized styled checkboxes/radio buttons) CSS only
"Font Awesome" version upgrade 4.4.0 -> 4.61
"Bootbox.js" version upgrade 4.3.0 -> 4.4.0
fixed "system dialog" (added responsive layout)

* closed #134  fixed db column type DT_INT (8 bytes) to DT_BIGINT

* closed #138 added new cookie based login

* closed #137 fixed javascript errors on trying to establish an "invalid" connection

* - #84, #138 improved "character selection" on login page (expired cookies are deleted, character panel layout improvements)
- added new "Server info panel" to the login page
- added new cronjob to delete expired cookie authentication data

* #138 enables character switching between characters which have same user

* - PHP Framework upgrade 3.5.0 -> 3.5.1 (fixes some issues with CREST cURL caching, and SESSION management)
- #138 added "cookie logout" to "logout" menu entry

* - updated "feature page" with new feature descriptions and label
- added some new images to the "feature gallery"
- removed "beta" status from "magnetizing" feature on map menu
- hide "server status" panel on "mobile" breakpoint

* - #138 clear character authentication data on sold characters

* closed #142 added custom "onsuspect()" session handler

* #142 do not log suspect if no file is defined in pathfinder.ini

* #142 added NullSec Data/Relic sites to C1/2/3 wormholes as signature option

* #144 fixed "Character not found" warning

* #144 fixed "Character not found" warning

* closed #144 fixed broken routes panel in IGB

* updated README.md for upcoming release

* #147 response header validation

* #149 changed comment for 'BASE' framework var

* fixed map  import

* - added minimal SDE dump (EVE Online: Citadel)
- #147 improved CREST API error logging (WIP)
- improved SSO controller (removed access_token from public endpoints)

* closed #154 added alliance maps to CREST API

* - updated Gulp build dependencies
- increased CREST timeout from 3s -> 4s
- added "Accept" Headers for some CREST endpoints

* cloased #147

* - closed #153 added character verification check for getAll(); Signatures Ajax endpoint

* - updated README.md (added Slack developer chat information)

* Bugfix frig holes (#159)

* added missing frigate wormholes and fixed Q003 destination in shattered wormholes

* changed C7 to 0.0 for Q003

* - fixed broken "graph" data for system

* added a  "failover" system  for bad crest requests (HTTP status 5xx,.. )

* Red Gaint => Red Giant (#161)

* closed #163 added CREST endpoint support for "waypoints"

* fixed typo

* closed #160 fixed tooltip container

* - added new features to login page

* closes #154 added alliance map support

* fixed XML path for cronjobs

* fixed a bug with inactive "private" maps

* closes #175 added alternative environment configuration

* - v1.0.0  build
This commit is contained in:
Mark Friedrich
2016-06-03 23:05:34 +02:00
parent 0c0cd84730
commit ecd505a202
192 changed files with 7965 additions and 5751 deletions

View File

@@ -1,23 +1,33 @@
## *PATHFINDER*
Mapping tool for [*EVE ONLINE*](https://www.eveonline.com)
- Project [https://www.pathfinder-w.space](https://www.pathfinder-w.space)
- Project URL [https://www.pathfinder-w.space](https://www.pathfinder-w.space)
- **NEW** Features: [checkout](https://www.pathfinder-w.space#pf-landing-gallery)
- Official Forum post [https://forums.eveonline.com](https://forums.eveonline.com/default.aspx?g=posts&m=6021776#post6021776)
- Screenshots [imgur.com](http://imgur.com/a/k2aVa)
- Video [youtube.com](https://www.youtube.com/channel/UC7HU7XEoMbqRwqxDTbMjSPg)
- Community [google +](https://plus.google.com/u/0/b/110257318165279088853/110257318165279088853)
- Community [google +](https://plus.google.com/+Pathfinder-wSpace)
- Licence [MIT](http://opensource.org/licenses/MIT)
##### IMPORTANT Information:
**The setup and installation process in ``1.0.0RC1`` and is not backwards compatible with previous beta releases (check wiki)!**
#### Development
- **NEW** Test server
- URL: http://www.dev.pathfinder-w.space
- Running current `develop` branch
- Using Singularity (SISI) CREST (make sure to use your test-server client)
- Available for public testing (e.g. new feature,.. )
- Database will be cleared from time to time
- Installation guide:
- [wiki](https://github.com/exodus4d/pathfinder/wiki)
- Developer chat [Slack](https://slack.com) :
- https://pathfinder-eve-online.slack.com
- Please send me a mail for invite: pathfinder@exodus4d.de
**Feel free to check the code for bugs and security issues.
Issues should be reported in the [Issue](https://github.com/exodus4d/pathfinder/issues) section.**
If you are looking for installation help, please check the [wiki](https://github.com/exodus4d/pathfinder/wiki).
More information will be added once the beta is over and the first stable build is released.
***
## Project structure
### Project structure
```
|-- (0755) app --> backend [*.php]
@@ -34,8 +44,8 @@ More information will be added once the beta is over and the first stable build
|-- sql --> static DB data for import (pathfinder.sql)
|-- (0755) favicon --> Favicons
|-- (0755) js --> JS source files (raw)
|-- app --> "PASTHFINDER" core files (not used for production )
|-- lib --> 3rd partie extension/library (not used for production )
|-- app --> "PASTHFINDER" core files (not used for production)
|-- lib --> 3rd partie extension/library (not used for production)
|-- app.js --> require.js config (!required for production!)
|-- (0777) logs --> log files
|-- ...
@@ -47,7 +57,7 @@ More information will be added once the beta is over and the first stable build
|-- img --> images
|-- js --> JS dist/build folder and source maps (minified, uglified)
|-- templates --> templates
|-- sass --> SCSS source (not used for production )
|-- sass --> SCSS source (not used for production)
|-- ...
|-- (0777) tmp --> cache folder
|-- ...
@@ -57,15 +67,16 @@ More information will be added once the beta is over and the first stable build
--------------------------
CI/CD config files:
--------------------------
|-- build.js --> "RequireJs Optimizer" config (not used for production )
|-- config.rb --> "Compass" config (not used for production )
|-- build.js --> "RequireJs Optimizer" config (not used for production)
|-- config.rb --> "Compass" config (not used for production)
|-- gulpfile.js --> "Gulp" task config (not used for production )
|-- package.json --> "Node.js" dependency config (not used for production )
|-- README.md --> This file :) (not used for production )
|-- package.json --> "Node.js" dependency config (not used for production)
|-- README.md --> This file :) (not used for production)
```
## Thanks!
I´m very proud that **you** are using *PATHFINDER*!
***
### Thanks!
It took me month of time in development until this project got into the first *BETA*. If you like it, please help to improve it.
(report bugs, find security issues,...)

View File

@@ -39,9 +39,9 @@ AUTOLOAD = app/main/
FAVICON = /favicon
; load additional config files
; DO NOT load environment.ini, it is loaded automatically
[configs]
app/routes.ini = true
app/environment.ini = true
app/pathfinder.ini = true
app/requirements.ini = true
app/cron.ini = true

View File

@@ -17,8 +17,11 @@ importSystemData = Cron\CcpSystemsUpdate->importSystemData, @hourly
; disable outdated maps
deactivateMapData = Cron\MapUpdate->deactivateMapData, @hourly
; delete character log data
deleteLogData = Cron\CharacterUpdate->deleteLogData, @hourly
; delete disabled maps
deleteMapData = Cron\MapUpdate->deleteMapData, @downtime
; delete character log data
deleteLogData = Cron\CharacterUpdate->deleteLogData, @hourly
; delete expired character cookie authentication data
deleteAuthenticationData = Cron\CharacterUpdate->deleteAuthenticationData, @downtime

View File

@@ -2,16 +2,17 @@
[ENVIRONMENT]
; project environment (DEVELOP, PRODUCTION).
; This effects: DB connection, Mail-Server connection
; This effects: DB connection, Mail-Server, SSO, CREST configurations in this file
; configuration below
SERVER = DEVELOP
[ENVIRONMENT.DEVELOP]
; base dir (Default: "auto-detect"
; path to index.php (Default: leave blank == "auto-detect")
; -> e.g. set /pathfinder if your URL looks like https://www.[YOUR_DOMAIN]/pathfinder (subfolder)
BASE =
; deployment URL e.g. http://localhost
; deployment URL (e.g. http://localhost)
URL = http://pathfinder.local
; Verbosity level of the stack trace
; level of debug/error stack trace
DEBUG = 3
; main db
DB_DNS = mysql:host=localhost;port=3306;dbname=
@@ -21,18 +22,23 @@ DB_PASS =
; EVE-Online CCP Database export
DB_CCP_DNS = mysql:host=localhost;port=3306;dbname=
DB_CCP_NAME = eve_parallax_min
DB_CCP_NAME = eve_citadel_min
DB_CCP_USER = root
DB_CCP_PASS =
; CCP SSO settings
; CCP SSO settings (OAuth2) - visit: https://developers.eveonline.com/applications
CCP_CREST_URL = https://api-sisi.testeveonline.com
SSO_CCP_URL = https://sisilogin.testeveonline.com
SSO_CCP_CLIENT_ID =
SSO_CCP_SECRET_KEY =
; SMTP settings. see: https://developers.eveonline.com/applications
; CCP XML APIv2
CCP_XML = https://api.testeveonline.com
; SMTP settings (optional)
SMTP_HOST = localhost
SMTP_PORT = 25
SMTP_SCHEME = ""
SMTP_SCHEME = TLS
SMTP_USER = pathfinder
SMTP_PASS = root
@@ -40,10 +46,12 @@ SMTP_FROM = pathfinder@localhost.com
SMTP_ERROR = pathfinder@localhost.com
[ENVIRONMENT.PRODUCTION]
BASE = /www/htdocs/www.pathfinder-w.space
; deployment URL
; path to index.php (Default: leave blank == "auto-detect")
; -> e.g. set /pathfinder if your URL looks like https://www.[YOUR_DOMAIN]/pathfinder (subfolder)
BASE =
; deployment URL (e.g. https://www.pathfinder-w.space)
URL = https://www.pathfinder-w.space
; Verbosity level of the stack trace
; level of debug/error stack trace
DEBUG = 0
; main db
DB_DNS = mysql:host=localhost;port=3306;dbname=
@@ -58,10 +66,15 @@ DB_CCP_USER =
DB_CCP_PASS =
; CCP SSO settings
CCP_CREST_URL = https://crest-tq.eveonline.com
SSO_CCP_URL = https://login.eveonline.com
SSO_CCP_CLIENT_ID =
SSO_CCP_SECRET_KEY =
; SMTP settings
; CCP XML APIv2
CCP_XML = https://api.eveonline.com
; SMTP settings (optional)
SMTP_HOST = localhost
SMTP_PORT = 25
SMTP_SCHEME = TLS

View File

@@ -1,5 +1,52 @@
CHANGELOG
3.5.1 (31 December 2015)
* NEW: ttl attribute in <include> template tag
* NEW: allow anonymous function for template filter
* NEW: format modifier for international and custom currency symbol
* NEW: Image->data() returns image resource
* NEW: extract() get prefixed array keys from an assoc array
* NEW: Optimized and faster Template parser with full support for HTML5 empty tags
* NEW: Added support for {@token} encapsulation syntax in routes definition
* NEW: DB\SQL->exec(), automatically shift to 1-based query arguments
* NEW: abort() flush output
* Added referenced value to devoid()
* Template token filters are now resolved within Preview->token()
* Web->_curl: restrict redirections to HTTP
* Web->minify(), skip importing of external files
* Improved session and error handling in until()
* Get the error trace array with the new $format parameter
* Better support for unicode URLs
* Optimized TZ detection with date_default_timezone_get()
* format() Provide default decimal places
* Optimize code: remove redundant TTL checks
* Optimized timeout handling in Web->request()
* Improved PHPDoc hints
* Added missing russian DIACRITICS letters
* DB\Cursor: allow child implementation of reset()
* DB\Cursor: Copyfrom now does an internal call to set()
* DB\SQL: Provide the ability to disable SQL logging
* DB\SQL: improved query analysis to trigger fetchAll
* DB\SQL\Mapper: added support for binary table columns
* SQL,JIG,MONGO,CACHE Session handlers refactored and optimized
* SMTP Refactoring and optimization
* Bug fix: SMTP, Align quoted_printable_encode() with SMTP specs (dot-stuffing)
* Bug fix: SMTP, Send buffered optional headers to output
* Bug fix: SMTP, Content-Transfer-Encoding for non-TLS connections
* Bug fix: SMTP, Single attachment error
* Bug fix: Cursor->load not always mapping to first record
* Bug fix: dry SQL mapper should not trigger 'load'
* Bug fix: Code highlighting on empty text
* Bug fix: Image->resize, round dimensions instead of cast
* Bug fix: whitespace handling in $f3->compile()
* Bug fix: TTL of `View` and `Preview` (`Template`)
* Bug fix: token filter regex
* Bug fix: Template, empty attributes
* Bug fix: Preview->build() greedy regex
* Bug fix: Web->minify() single-line comment on last line
* Bug fix: Web->request(), follow_location with cURL and open_basedir
* Bug fix: Web->send() Single quotes around filename not interpreted correctly by some browsers
3.5.0 (2 June 2015)
* NEW: until() method for long polling
* NEW: abort() to disconnect HTTP client (and continue execution)

View File

@@ -45,7 +45,7 @@ final class Base extends Prefab implements ArrayAccess {
//@{ Framework details
const
PACKAGE='Fat-Free Framework',
VERSION='3.5.0-Release';
VERSION='3.5.1-Release';
//@}
//@{ HTTP status codes (RFC 2616)
@@ -179,8 +179,8 @@ final class Base extends Prefab implements ArrayAccess {
}
/**
* assemble url from alias name
* @return NULL
* Assemble url from alias name
* @return string
* @param $name string
* @param $params array|string
**/
@@ -215,7 +215,7 @@ final class Base extends Prefab implements ArrayAccess {
function compile($str) {
$fw=$this;
return preg_replace_callback(
'/(?<!\w)@(\w(?:[\w\.\[\]\(]|\->|::)*)/',
'/(?<!\w)@(\w(?:[\h\w\.\[\]\(]|\->|::)*)/',
function($var) use($fw) {
return '$'.preg_replace_callback(
'/\.(\w+)\(|\.(\w+)|\[((?:[^\[\]]*|(?R))*)\]/',
@@ -226,7 +226,7 @@ final class Base extends Prefab implements ArrayAccess {
('['.var_export($expr[1],TRUE).']')).'('):
('['.var_export(
isset($expr[3])?
$fw->compile($expr[3]):
trim($fw->compile($expr[3])):
(ctype_digit($expr[2])?
(int)$expr[2]:
$expr[2]),TRUE).']');
@@ -305,10 +305,11 @@ final class Base extends Prefab implements ArrayAccess {
/**
* Return TRUE if hive key is empty and not cached
* @return bool
* @param $key string
* @param $val mixed
* @return bool
**/
function devoid($key) {
function devoid($key,&$val=NULL) {
$val=$this->ref($key,FALSE);
return empty($val) &&
(!Cache::instance()->exists($this->hash($key).'.var',$val) ||
@@ -422,8 +423,7 @@ final class Base extends Prefab implements ArrayAccess {
// End session
session_unset();
session_destroy();
unset($_COOKIE[session_name()]);
header_remove('Set-Cookie');
$this->clear('COOKIE.'.session_name());
}
$this->sync('SESSION');
}
@@ -679,6 +679,19 @@ final class Base extends Prefab implements ArrayAccess {
return $num?($num/abs($num)):0;
}
/**
* Extract values of an associative array whose keys start with the given prefix
* @return array
* @param $arr array
* @param $prefix string
**/
function extract($arr,$prefix) {
$out=array();
foreach (preg_grep('/^'.preg_quote($prefix,'/').'/',array_keys($arr)) as $key)
$out[substr($key,strlen($prefix))]=$arr[$key];
return $out;
}
/**
* Convert class constants to array
* @return array
@@ -687,14 +700,7 @@ final class Base extends Prefab implements ArrayAccess {
**/
function constants($class,$prefix='') {
$ref=new ReflectionClass($class);
$out=array();
foreach (preg_grep('/^'.$prefix.'/',array_keys($ref->getconstants()))
as $val) {
$out[$key=substr($val,strlen($prefix))]=
constant((is_object($class)?get_class($class):$class).'::'.$prefix.$key);
}
unset($ref);
return $out;
return $this->extract($ref->getconstants(),$prefix);
}
/**
@@ -843,9 +849,12 @@ final class Base extends Prefab implements ArrayAccess {
return number_format(
$args[$pos],0,'',$thousands_sep);
case 'currency':
if (function_exists('money_format'))
$int=$cstm=false;
if (isset($prop) && $cstm=!$int=($prop=='int'))
$currency_symbol=$prop;
if (!$cstm && function_exists('money_format'))
return money_format(
'%n',$args[$pos]);
'%'.($int?'i':'n'),$args[$pos]);
$fmt=array(
0=>'(nc)',1=>'(n c)',
2=>'(nc)',10=>'+nc',
@@ -878,7 +887,8 @@ final class Base extends Prefab implements ArrayAccess {
$frac_digits,
$decimal_point,
$thousands_sep),
$currency_symbol),
$int?$int_curr_symbol
:$currency_symbol),
$fmt[(int)(
(${$pre.'_cs_precedes'}%2).
(${$pre.'_sign_posn'}%5).
@@ -891,8 +901,8 @@ final class Base extends Prefab implements ArrayAccess {
$thousands_sep).'%';
case 'decimal':
return number_format(
$args[$pos],$prop,$decimal_point,
$thousands_sep);
$args[$pos],isset($prop)?$prop:2,
$decimal_point,$thousands_sep);
}
break;
case 'date':
@@ -1022,7 +1032,7 @@ final class Base extends Prefab implements ArrayAccess {
**/
function status($code) {
$reason=@constant('self::HTTP_'.$code);
if (PHP_SAPI!='cli')
if (PHP_SAPI!='cli' && !headers_sent())
header($_SERVER['SERVER_PROTOCOL'].' '.$code.' '.$reason);
return $reason;
}
@@ -1089,11 +1099,12 @@ final class Base extends Prefab implements ArrayAccess {
}
/**
* Return formatted stack trace
* @return string
* Return filtered, formatted stack trace
* @return string|array
* @param $trace array|NULL
* @param $format bool
**/
function trace(array $trace=NULL) {
function trace(array $trace=NULL, $format=TRUE) {
if (!$trace) {
$trace=debug_backtrace(FALSE);
$frame=$trace[0];
@@ -1111,6 +1122,8 @@ final class Base extends Prefab implements ArrayAccess {
'__call|call_user_func)/',$frame['function']));
}
);
if (!$format)
return $trace;
$out='';
$eol="\n";
// Analyze stack trace
@@ -1369,7 +1382,7 @@ final class Base extends Prefab implements ArrayAccess {
$url=$this->rel($this->hive['URI']);
$case=$this->hive['CASELESS']?'i':'';
preg_match('/^'.
preg_replace('/@(\w+\b)/','(?P<\1>[^\/\?]+)',
preg_replace('/((\\\{)?@(\w+\b)(?(2)\\\}))/','(?P<\3>[^\/\?]+)',
str_replace('\*','([^\?]+)',preg_quote($pattern,'/'))).
'\/?(?:\?.*)?$/'.$case.'um',$url,$args);
return $args;
@@ -1394,7 +1407,7 @@ final class Base extends Prefab implements ArrayAccess {
array_multisort($paths,SORT_DESC,$keys,$vals);
$this->hive['ROUTES']=array_combine($keys,$vals);
// Convert to BASE-relative URL
$req=$this->rel($this->hive['URI']);
$req=$this->rel(urldecode($this->hive['URI']));
if ($cors=(isset($this->hive['HEADERS']['Origin']) &&
$this->hive['CORS']['origin'])) {
$cors=$this->hive['CORS'];
@@ -1428,7 +1441,7 @@ final class Base extends Prefab implements ArrayAccess {
if (is_numeric($key) && $key)
unset($args[$key]);
// Capture values of route pattern tokens
$this->hive['PARAMS']=$args=array_map('urldecode',$args);
$this->hive['PARAMS']=$args;
// Save matching route
$this->hive['ALIAS']=$alias;
$this->hive['PATTERN']=$pattern;
@@ -1437,9 +1450,10 @@ final class Base extends Prefab implements ArrayAccess {
implode(',',$cors['expose']):$cors['expose']));
if (is_string($handler)) {
// Replace route pattern tokens in handler if any
$handler=preg_replace_callback('/@(\w+\b)/',
$handler=preg_replace_callback('/({)?@(\w+\b)(?(1)})/',
function($id) use($args) {
return isset($args[$id[1]])?$args[$id[1]]:$id[0];
$pid=count($id)>2?2:1;
return isset($args[$id[$pid]])?$args[$id[$pid]]:$id[0];
},
$handler
);
@@ -1458,7 +1472,7 @@ final class Base extends Prefab implements ArrayAccess {
$cached=$cache->exists(
$hash=$this->hash($this->hive['VERB'].' '.
$this->hive['URI']).'.url',$data);
if ($cached && $cached[0]+$ttl>$now) {
if ($cached) {
if (isset($headers['If-Modified-Since']) &&
strtotime($headers['If-Modified-Since'])+
$ttl>$now) {
@@ -1520,10 +1534,13 @@ final class Base extends Prefab implements ArrayAccess {
// Unhandled HTTP method
header('Allow: '.implode(',',array_unique($allowed)));
if ($cors) {
header('Access-Control-Allow-Methods: OPTIONS,'.implode(',',$allowed));
header('Access-Control-Allow-Methods: OPTIONS,'.
implode(',',$allowed));
if ($cors['headers'])
header('Access-Control-Allow-Headers: '.(is_array($cors['headers'])?
implode(',',$cors['headers']):$cors['headers']));
header('Access-Control-Allow-Headers: '.
(is_array($cors['headers'])?
implode(',',$cors['headers']):
$cors['headers']));
if ($cors['ttl']>0)
header('Access-Control-Max-Age: '.$cors['ttl']);
}
@@ -1546,28 +1563,26 @@ final class Base extends Prefab implements ArrayAccess {
$time=time();
$limit=max(0,min($timeout,$max=ini_get('max_execution_time')-1));
$out='';
$flag=FALSE;
// Turn output buffering on
ob_start();
// Not for the weak of heart
while (
// No error occurred
!$this->hive['ERROR'] &&
// Still alive?
!connection_aborted() &&
// Got time left?
(time()-$time+1<$limit) &&
// Restart session
$flag=@session_start() &&
@session_start() &&
// CAUTION: Callback will kill host if it never becomes truthy!
!($out=$this->call($func,$args))) {
session_commit();
ob_flush();
flush();
// Hush down
sleep(1);
}
if ($flag) {
session_commit();
ob_flush();
flush();
}
ob_flush();
flush();
return $out;
}
@@ -1577,9 +1592,11 @@ final class Base extends Prefab implements ArrayAccess {
function abort() {
@session_start();
session_commit();
header('Content-Length: 0');
$out='';
while (ob_get_level())
ob_end_clean();
$out=ob_get_clean().$out;
header('Content-Length: '.strlen($out));
echo $out;
flush();
if (function_exists('fastcgi_finish_request'))
fastcgi_finish_request();
@@ -1826,7 +1843,7 @@ final class Base extends Prefab implements ArrayAccess {
$out='';
$pre=FALSE;
$text=trim($text);
if (!preg_match('/^<\?php/',$text)) {
if ($text && !preg_match('/^<\?php/',$text)) {
$text='<?php '.$text;
$pre=TRUE;
}
@@ -2083,9 +2100,9 @@ final class Base extends Prefab implements ArrayAccess {
'CONFIG'=>NULL,
'CORS'=>array(
'headers'=>'',
'origin'=>false,
'credentials'=>false,
'expose'=>false,
'origin'=>FALSE,
'credentials'=>FALSE,
'expose'=>FALSE,
'ttl'=>0),
'DEBUG'=>0,
'DIACRITICS'=>array(),
@@ -2131,7 +2148,7 @@ final class Base extends Prefab implements ArrayAccess {
'SERIALIZER'=>extension_loaded($ext='igbinary')?$ext:'php',
'TEMP'=>'tmp/',
'TIME'=>microtime(TRUE),
'TZ'=>(@ini_get('date.timezone'))?:'UTC',
'TZ'=>@date_default_timezone_get(),
'UI'=>'./',
'UNLOAD'=>NULL,
'UPLOADS'=>'./',
@@ -2500,8 +2517,7 @@ class View extends Prefab {
function render($file,$mime='text/html',array $hive=NULL,$ttl=0) {
$fw=Base::instance();
$cache=Cache::instance();
$cached=$cache->exists($hash=$fw->hash($file),$data);
if ($cached && $cached[0]+$ttl>microtime(TRUE))
if ($cache->exists($hash=$fw->hash($file),$data))
return $data;
foreach ($fw->split($fw->get('UI').';./') as $dir)
if (is_file($this->view=$fw->fixslashes($dir.$file))) {
@@ -2516,7 +2532,7 @@ class View extends Prefab {
foreach($this->trigger['afterrender'] as $func)
$data=$fw->call($func,$data);
if ($ttl)
$cache->set($hash,$data);
$cache->set($hash,$data,$ttl);
return $data;
}
user_error(sprintf(Base::E_Open,$file),E_USER_ERROR);
@@ -2539,7 +2555,7 @@ class Preview extends View {
//! MIME type
$mime,
//! token filter
$filter = array(
$filter=array(
'esc'=>'$this->esc',
'raw'=>'$this->raw',
'alias'=>'\Base::instance()->alias',
@@ -2552,15 +2568,24 @@ class Preview extends View {
* @param $str string
**/
function token($str) {
return trim(preg_replace('/\{\{(.+?)\}\}/s',trim('\1'),
$str=trim(preg_replace('/\{\{(.+?)\}\}/s',trim('\1'),
Base::instance()->compile($str)));
if (preg_match('/^(.+)(?<!\|)\|((?:\h*\w+(?:\h*[,;]?))+)$/s',
$str,$parts)) {
$str=trim($parts[1]);
foreach (Base::instance()->split($parts[2]) as $func)
$str=is_string($cmd=$this->filter($func))?$cmd.'('.$str.')':
'\Base::instance()->call('.
'$this->filter(\''.$func.'\'),array('.$str.'))';
}
return $str;
}
/**
* register token filter
* Register or get (a specific one or all) token filters
* @param string $key
* @param string $func
* @return array
* @param string|closure $func
* @return array|closure|string
*/
function filter($key=NULL,$func=NULL) {
if (!$key)
@@ -2578,19 +2603,15 @@ class Preview extends View {
protected function build($node) {
$self=$this;
return preg_replace_callback(
'/\{\-(.+?)\-\}|\{\{(.+?)\}\}(\n+)?/s',
'/\{\-(.+?)\-\}|\{\{(.+?)\}\}(\n+)?|(\{\*.*?\*\})/s',
function($expr) use($self) {
if ($expr[1])
return $expr[1];
$str=trim($self->token($expr[2]));
if (preg_match('/^([^|]+?)\h*\|(\h*\w+(?:\h*[,;]\h*\w+)*)/',
$str,$parts)) {
$str=$parts[1];
foreach (Base::instance()->split($parts[2]) as $func)
$str=$self->filter($func).'('.$str.')';
}
return '<?php echo '.$str.'; ?>'.
(isset($expr[3])?$expr[3]."\n":'');
return empty($expr[4])?
('<?php echo '.$str.'; ?>'.
(isset($expr[3])?$expr[3]."\n":'')):
'';
},
preg_replace_callback(
'/\{~(.+?)~\}/s',
@@ -2631,8 +2652,7 @@ class Preview extends View {
if (!is_dir($tmp=$fw->get('TEMP')))
mkdir($tmp,Base::MODE,TRUE);
foreach ($fw->split($fw->get('UI')) as $dir) {
$cached=$cache->exists($hash=$fw->hash($dir.$file),$data);
if ($cached && $cached[0]+$ttl>microtime(TRUE))
if ($cache->exists($hash=$fw->hash($dir.$file),$data))
return $data;
if (is_file($view=$fw->fixslashes($dir.$file))) {
if (!is_file($this->view=($tmp.
@@ -2659,7 +2679,7 @@ class Preview extends View {
foreach ($this->trigger['afterrender'] as $func)
$data = $fw->call($func, $data);
if ($ttl)
$cache->set($hash,$data);
$cache->set($hash,$data,$ttl);
return $data;
}
}

View File

@@ -195,7 +195,7 @@ class Basket extends Magic {
if (is_string($var))
$var=\Base::instance()->get($var);
foreach ($var as $key=>$val)
$this->item[$key]=$val;
$this->set($key,$val);
}
/**

View File

@@ -13,14 +13,14 @@
* | | < | <| -__|-- __|
* |__|__|__||__|__|_____|_____|
*
* Copyright (c) 2014 by ikkez
* Christian Knuth <ikkez0n3@gmail.com>
* Copyright (c) 2016 by ikkez
* Christian Knuth <mail@ikkez.de>
* https://github.com/ikkez/F3-Sugar/
*
* @package DB
* @version 1.4.1-dev
* @version 1.4.1
* @date 29.01.2016
* @since 24.04.2012
* @date 04.06.2015
*/
namespace DB;
@@ -173,7 +173,9 @@ class Cortex extends Cursor {
list($key, $relField) = explode('.',$val,2);
$this->relWhitelist[$key][(int)$exclude][] = $relField;
unset($fields[$i]);
$fields[] = $key;
}
$fields = array_unique($fields);
$schema = $this->whitelist ?: $this->mapper->fields();
if (!$schema && !$this->dbsType != 'sql' && $this->dry()) {
$schema = $this->load()->mapper->fields();

View File

@@ -103,7 +103,7 @@ abstract class Cursor extends \Magic implements \IteratorAggregate {
/**
* Get cursor's equivalent external iterator
* Causes a fatal error in PHP 5.3.5if uncommented
* Causes a fatal error in PHP 5.3.5 if uncommented
* return ArrayIterator
**/
abstract function getiterator();
@@ -119,7 +119,7 @@ abstract class Cursor extends \Magic implements \IteratorAggregate {
/**
* Return first record (mapper object) that matches criteria
* @return \DB\Cursor|FALSE
* @return static|FALSE
* @param $filter string|array
* @param $options array
* @param $ttl int
@@ -171,8 +171,9 @@ abstract class Cursor extends \Magic implements \IteratorAggregate {
* @param $ttl int
**/
function load($filter=NULL,array $options=NULL,$ttl=0) {
$this->reset();
return ($this->query=$this->find($filter,$options,$ttl)) &&
$this->skip(0)?$this->query[$this->ptr=0]:FALSE;
$this->skip(0)?$this->query[$this->ptr]:FALSE;
}
/**

View File

@@ -149,7 +149,7 @@ class Mapper extends \DB\Cursor {
/**
* Return records that match criteria
* @return \DB\JIG\Mapper[]|FALSE
* @return static[]|FALSE
* @param $filter array
* @param $options array
* @param $ttl int
@@ -431,7 +431,7 @@ class Mapper extends \DB\Cursor {
if ($func)
$var=call_user_func($func,$var);
foreach ($var as $key=>$val)
$this->document[$key]=$val;
$this->set($key,$val);
}
/**

View File

@@ -27,7 +27,15 @@ class Session extends Mapper {
protected
//! Session ID
$sid;
$sid,
//! Anti-CSRF token
$_csrf,
//! User agent
$_agent,
//! IP,
$_ip,
//! Suspect callback
$onsuspect;
/**
* Open session
@@ -44,6 +52,8 @@ class Session extends Mapper {
* @return TRUE
**/
function close() {
$this->reset();
$this->sid=NULL;
return TRUE;
}
@@ -53,9 +63,20 @@ class Session extends Mapper {
* @param $id string
**/
function read($id) {
if ($id!=$this->sid)
$this->load(array('@session_id=?',$this->sid=$id));
return $this->dry()?FALSE:$this->get('data');
$this->load(array('@session_id=?',$this->sid=$id));
if ($this->dry())
return FALSE;
if ($this->get('ip')!=$this->_ip || $this->get('agent')!=$this->_agent) {
$fw=\Base::instance();
if (!isset($this->onsuspect) || FALSE===$fw->call($this->onsuspect,array($this,$id))) {
//NB: `session_destroy` can't be called at that stage (`session_start` not completed)
$this->destroy($id);
$this->close();
$fw->clear('COOKIE.'.session_name());
$fw->error(403);
}
}
return $this->get('data');
}
/**
@@ -65,19 +86,10 @@ class Session extends Mapper {
* @param $data string
**/
function write($id,$data) {
$fw=\Base::instance();
$sent=headers_sent();
$headers=$fw->get('HEADERS');
if ($id!=$this->sid)
$this->load(array('@session_id=?',$this->sid=$id));
$csrf=$fw->hash($fw->get('ROOT').$fw->get('BASE')).'.'.
$fw->hash(mt_rand());
$this->set('session_id',$id);
$this->set('data',$data);
$this->set('csrf',$sent?$this->csrf():$csrf);
$this->set('ip',$fw->get('IP'));
$this->set('agent',
isset($headers['User-Agent'])?$headers['User-Agent']:'');
$this->set('ip',$this->_ip);
$this->set('agent',$this->_agent);
$this->set('stamp',time());
$this->save();
return TRUE;
@@ -90,9 +102,6 @@ class Session extends Mapper {
**/
function destroy($id) {
$this->erase(array('@session_id=?',$id));
setcookie(session_name(),'',strtotime('-1 year'));
unset($_COOKIE[session_name()]);
header_remove('Set-Cookie');
return TRUE;
}
@@ -107,19 +116,27 @@ class Session extends Mapper {
}
/**
* Return anti-CSRF token
* @return string|FALSE
**/
function csrf() {
return $this->dry()?FALSE:$this->get('csrf');
* Return session id (if session has started)
* @return string|NULL
**/
function sid() {
return $this->sid;
}
/**
* Return IP address
* @return string|FALSE
**/
* Return anti-CSRF token
* @return string
**/
function csrf() {
return $this->_csrf;
}
/**
* Return IP address
* @return string
**/
function ip() {
return $this->dry()?FALSE:$this->get('ip');
return $this->_ip;
}
/**
@@ -127,6 +144,8 @@ class Session extends Mapper {
* @return string|FALSE
**/
function stamp() {
if (!$this->sid)
session_start();
return $this->dry()?FALSE:$this->get('stamp');
}
@@ -135,17 +154,19 @@ class Session extends Mapper {
* @return string|FALSE
**/
function agent() {
return $this->dry()?FALSE:$this->get('agent');
return $this->_agent;
}
/**
* Instantiate class
* @param $db object
* @param $db \DB\Jig
* @param $file string
* @param $onsuspect callback
* @param $key string
**/
function __construct(\DB\Jig $db,$file='sessions',$onsuspect=NULL) {
function __construct(\DB\Jig $db,$file='sessions',$onsuspect=NULL,$key=NULL) {
parent::__construct($db,$file);
$this->onsuspect=$onsuspect;
session_set_save_handler(
array($this,'open'),
array($this,'close'),
@@ -155,26 +176,14 @@ class Session extends Mapper {
array($this,'cleanup')
);
register_shutdown_function('session_commit');
@session_start();
$fw=\Base::instance();
$headers=$fw->get('HEADERS');
if (($ip=$this->ip()) && $ip!=$fw->get('IP') ||
($agent=$this->agent()) &&
(!isset($headers['User-Agent']) ||
$agent!=$headers['User-Agent'])) {
if (isset($onsuspect))
$fw->call($onsuspect,array($this));
else {
session_destroy();
$fw->error(403);
}
}
$csrf=$fw->hash($fw->get('ROOT').$fw->get('BASE')).'.'.
$this->_csrf=$fw->hash($fw->get('ROOT').$fw->get('BASE')).'.'.
$fw->hash(mt_rand());
if ($this->load(array('@session_id=?',$this->sid=session_id()))) {
$this->set('csrf',$csrf);
$this->save();
}
if ($key)
$fw->set($key,$this->_csrf);
$this->_agent=isset($headers['User-Agent'])?$headers['User-Agent']:'';
$this->_ip=$fw->get('IP');
}
}

View File

@@ -84,7 +84,7 @@ class Mapper extends \DB\Cursor {
/**
* Convert array to mapper object
* @return \DB\Mongo\Mapper
* @return static
* @param $row array
**/
protected function factory($row) {
@@ -111,7 +111,7 @@ class Mapper extends \DB\Cursor {
/**
* Build query and execute
* @return \DB\Mongo\Mapper[]
* @return static[]
* @param $fields string
* @param $filter array
* @param $options array
@@ -177,7 +177,7 @@ class Mapper extends \DB\Cursor {
/**
* Return records that match criteria
* @return \DB\Mongo\Mapper[]
* @return static[]
* @param $filter array
* @param $options array
* @param $ttl int
@@ -308,7 +308,7 @@ class Mapper extends \DB\Cursor {
if ($func)
$var=call_user_func($func,$var);
foreach ($var as $key=>$val)
$this->document[$key]=$val;
$this->set($key,$val);
}
/**

View File

@@ -27,7 +27,15 @@ class Session extends Mapper {
protected
//! Session ID
$sid;
$sid,
//! Anti-CSRF token
$_csrf,
//! User agent
$_agent,
//! IP,
$_ip,
//! Suspect callback
$onsuspect;
/**
* Open session
@@ -44,6 +52,8 @@ class Session extends Mapper {
* @return TRUE
**/
function close() {
$this->reset();
$this->sid=NULL;
return TRUE;
}
@@ -53,9 +63,20 @@ class Session extends Mapper {
* @param $id string
**/
function read($id) {
if ($id!=$this->sid)
$this->load(array('session_id'=>$this->sid=$id));
return $this->dry()?FALSE:$this->get('data');
$this->load(array('session_id'=>$this->sid=$id));
if ($this->dry())
return FALSE;
if ($this->get('ip')!=$this->_ip || $this->get('agent')!=$this->_agent) {
$fw=\Base::instance();
if (!isset($this->onsuspect) || FALSE===$fw->call($this->onsuspect,array($this,$id))) {
//NB: `session_destroy` can't be called at that stage (`session_start` not completed)
$this->destroy($id);
$this->close();
$fw->clear('COOKIE.'.session_name());
$fw->error(403);
}
}
return $this->get('data');
}
/**
@@ -65,19 +86,10 @@ class Session extends Mapper {
* @param $data string
**/
function write($id,$data) {
$fw=\Base::instance();
$sent=headers_sent();
$headers=$fw->get('HEADERS');
if ($id!=$this->sid)
$this->load(array('session_id'=>$this->sid=$id));
$csrf=$fw->hash($fw->get('ROOT').$fw->get('BASE')).'.'.
$fw->hash(mt_rand());
$this->set('session_id',$id);
$this->set('data',$data);
$this->set('csrf',$sent?$this->csrf():$csrf);
$this->set('ip',$fw->get('IP'));
$this->set('agent',
isset($headers['User-Agent'])?$headers['User-Agent']:'');
$this->set('ip',$this->_ip);
$this->set('agent',$this->_agent);
$this->set('stamp',time());
$this->save();
return TRUE;
@@ -90,9 +102,6 @@ class Session extends Mapper {
**/
function destroy($id) {
$this->erase(array('session_id'=>$id));
setcookie(session_name(),'',strtotime('-1 year'));
unset($_COOKIE[session_name()]);
header_remove('Set-Cookie');
return TRUE;
}
@@ -107,45 +116,57 @@ class Session extends Mapper {
}
/**
* Return anti-CSRF token
* @return string|FALSE
**/
* Return session id (if session has started)
* @return string|NULL
**/
function sid() {
return $this->sid;
}
/**
* Return anti-CSRF token
* @return string
**/
function csrf() {
return $this->dry()?FALSE:$this->get('csrf');
return $this->_csrf;
}
/**
* Return IP address
* @return string|FALSE
**/
* Return IP address
* @return string
**/
function ip() {
return $this->dry()?FALSE:$this->get('ip');
return $this->_ip;
}
/**
* Return Unix timestamp
* @return string|FALSE
**/
* Return Unix timestamp
* @return string|FALSE
**/
function stamp() {
if (!$this->sid)
session_start();
return $this->dry()?FALSE:$this->get('stamp');
}
/**
* Return HTTP user agent
* @return string|FALSE
**/
* Return HTTP user agent
* @return string
**/
function agent() {
return $this->dry()?FALSE:$this->get('agent');
return $this->_agent;
}
/**
* Instantiate class
* @param $db object
* @param $db \DB\Mongo
* @param $table string
* @param $onsuspect callback
* @param $key string
**/
function __construct(\DB\Mongo $db,$table='sessions',$onsuspect=NULL) {
function __construct(\DB\Mongo $db,$table='sessions',$onsuspect=NULL,$key=NULL) {
parent::__construct($db,$table);
$this->onsuspect=$onsuspect;
session_set_save_handler(
array($this,'open'),
array($this,'close'),
@@ -155,26 +176,14 @@ class Session extends Mapper {
array($this,'cleanup')
);
register_shutdown_function('session_commit');
@session_start();
$fw=\Base::instance();
$headers=$fw->get('HEADERS');
if (($ip=$this->ip()) && $ip!=$fw->get('IP') ||
($agent=$this->agent()) &&
(!isset($headers['User-Agent']) ||
$agent!=$headers['User-Agent'])) {
if (isset($onsuspect))
$fw->call($onsuspect,array($this));
else {
session_destroy();
$fw->error(403);
}
}
$csrf=$fw->hash($fw->get('ROOT').$fw->get('BASE')).'.'.
$this->_csrf=$fw->hash($fw->get('ROOT').$fw->get('BASE')).'.'.
$fw->hash(mt_rand());
if ($this->load(array('session_id'=>$this->sid=session_id()))) {
$this->set('csrf',$csrf);
$this->save();
}
if ($key)
$fw->set($key,$this->_csrf);
$this->_agent=isset($headers['User-Agent'])?$headers['User-Agent']:'';
$this->_ip=$fw->get('IP');
}
}

View File

@@ -91,6 +91,8 @@ class SQL {
return \PDO::PARAM_BOOL;
case 'integer':
return \PDO::PARAM_INT;
case 'resource':
return \PDO::PARAM_LOB;
default:
return \PDO::PARAM_STR;
}
@@ -112,6 +114,8 @@ class SQL {
return (bool)$val;
case \PDO::PARAM_STR:
return (string)$val;
case \PDO::PARAM_LOB:
return (binary)$val;
}
}
@@ -149,6 +153,11 @@ class SQL {
for ($i=0;$i<$count;$i++) {
$cmd=$cmds[$i];
$arg=$args[$i];
// ensure 1-based arguments
if (array_key_exists(0,$arg)) {
array_unshift($arg,'');
unset($arg[0]);
}
if (!preg_replace('/(^\s+|[\s;]+$)/','',$cmd))
continue;
$now=microtime(TRUE);
@@ -198,8 +207,8 @@ class SQL {
$this->rollback();
user_error('PDOStatement: '.$error[2],E_USER_ERROR);
}
if (preg_match('/^\s*'.
'(?:EXPLAIN|SELECT|PRAGMA|SHOW|RETURNING)\b/is',$cmd) ||
if (preg_match('/(?:^[\s\(]*'.
'(?:EXPLAIN|SELECT|PRAGMA|SHOW)|RETURNING)\b/is',$cmd) ||
(preg_match('/^\s*(?:CALL|EXEC)\b/is',$cmd) &&
$query->columnCount())) {
$result=$query->fetchall(\PDO::FETCH_ASSOC);
@@ -245,11 +254,14 @@ class SQL {
}
/**
* Return SQL profiler results
* Return SQL profiler results (or disable logging)
* @param $flag bool
* @return string
**/
function log() {
return $this->log;
function log($flag=TRUE) {
if ($flag)
return $this->log;
$this->log=FALSE;
}
/**
@@ -333,7 +345,10 @@ class SQL {
\PDO::PARAM_INT:
(preg_match('/bool/i',$row[$val[2]])?
\PDO::PARAM_BOOL:
\PDO::PARAM_STR),
(preg_match('/blob|bytea|image|binary/i',
$row[$val[2]])?
\PDO::PARAM_LOB:
\PDO::PARAM_STR)),
'default'=>is_string($row[$val[3]])?
preg_replace('/^\s*([\'"])(.*)\1\s*/','\2',
$row[$val[3]]):$row[$val[3]],
@@ -422,7 +437,7 @@ class SQL {
}
/**
* Redirect call to MongoDB object
* Redirect call to PDO object
* @return mixed
* @param $func string
* @param $args array

View File

@@ -190,7 +190,7 @@ class Mapper extends \DB\Cursor {
/**
* Build query string and execute
* @return \DB\SQL\Mapper[]
* @return static[]
* @param $fields string
* @param $filter string|array
* @param $options array
@@ -294,7 +294,7 @@ class Mapper extends \DB\Cursor {
/**
* Return records that match criteria
* @return \DB\SQL\Mapper[]
* @return static[]
* @param $filter string|array
* @param $options array
* @param $ttl int
@@ -362,7 +362,7 @@ class Mapper extends \DB\Cursor {
$field['value']=$dry?NULL:$out->adhoc[$key]['value'];
unset($field);
}
if (isset($this->trigger['load']))
if (!$dry && isset($this->trigger['load']))
\Base::instance()->call($this->trigger['load'],$this);
return $out;
}
@@ -559,14 +559,8 @@ class Mapper extends \DB\Cursor {
if ($func)
$var=call_user_func($func,$var);
foreach ($var as $key=>$val)
if (in_array($key,array_keys($this->fields))) {
$field=&$this->fields[$key];
if ($field['value']!==$val) {
$field['value']=$val;
$field['changed']=TRUE;
}
unset($field);
}
if (in_array($key,array_keys($this->fields)))
$this->set($key,$val);
}
/**

View File

@@ -27,7 +27,15 @@ class Session extends Mapper {
protected
//! Session ID
$sid;
$sid,
//! Anti-CSRF token
$_csrf,
//! User agent
$_agent,
//! IP,
$_ip,
//! Suspect callback
$onsuspect;
/**
* Open session
@@ -44,6 +52,8 @@ class Session extends Mapper {
* @return TRUE
**/
function close() {
$this->reset();
$this->sid=NULL;
return TRUE;
}
@@ -53,9 +63,20 @@ class Session extends Mapper {
* @param $id string
**/
function read($id) {
if ($id!=$this->sid)
$this->load(array('session_id=?',$this->sid=$id));
return $this->dry()?FALSE:$this->get('data');
$this->load(array('session_id=?',$this->sid=$id));
if ($this->dry())
return FALSE;
if ($this->get('ip')!=$this->_ip || $this->get('agent')!=$this->_agent) {
$fw=\Base::instance();
if (!isset($this->onsuspect) || FALSE===$fw->call($this->onsuspect,array($this,$id))) {
//NB: `session_destroy` can't be called at that stage (`session_start` not completed)
$this->destroy($id);
$this->close();
$fw->clear('COOKIE.'.session_name());
$fw->error(403);
}
}
return $this->get('data');
}
/**
@@ -65,19 +86,10 @@ class Session extends Mapper {
* @param $data string
**/
function write($id,$data) {
$fw=\Base::instance();
$sent=headers_sent();
$headers=$fw->get('HEADERS');
if ($id!=$this->sid)
$this->load(array('session_id=?',$this->sid=$id));
$csrf=$fw->hash($fw->get('ROOT').$fw->get('BASE')).'.'.
$fw->hash(mt_rand());
$this->set('session_id',$id);
$this->set('data',$data);
$this->set('csrf',$sent?$this->csrf():$csrf);
$this->set('ip',$fw->get('IP'));
$this->set('agent',
isset($headers['User-Agent'])?$headers['User-Agent']:'');
$this->set('ip',$this->_ip);
$this->set('agent',$this->_agent);
$this->set('stamp',time());
$this->save();
return TRUE;
@@ -90,9 +102,6 @@ class Session extends Mapper {
**/
function destroy($id) {
$this->erase(array('session_id=?',$id));
setcookie(session_name(),'',strtotime('-1 year'));
unset($_COOKIE[session_name()]);
header_remove('Set-Cookie');
return TRUE;
}
@@ -106,20 +115,28 @@ class Session extends Mapper {
return TRUE;
}
/**
* Return session id (if session has started)
* @return string|NULL
**/
function sid() {
return $this->sid;
}
/**
* Return anti-CSRF token
* @return string|FALSE
* @return string
**/
function csrf() {
return $this->dry()?FALSE:$this->get('csrf');
return $this->_csrf;
}
/**
* Return IP address
* @return string|FALSE
* @return string
**/
function ip() {
return $this->dry()?FALSE:$this->get('ip');
return $this->_ip;
}
/**
@@ -127,25 +144,28 @@ class Session extends Mapper {
* @return string|FALSE
**/
function stamp() {
if (!$this->sid)
session_start();
return $this->dry()?FALSE:$this->get('stamp');
}
/**
* Return HTTP user agent
* @return string|FALSE
* @return string
**/
function agent() {
return $this->dry()?FALSE:$this->get('agent');
return $this->_agent;
}
/**
* Instantiate class
* @param $db object
* @param $db \DB\SQL
* @param $table string
* @param $force bool
* @param $onsuspect callback
* @param $key string
**/
function __construct(\DB\SQL $db,$table='sessions',$force=TRUE,$onsuspect=NULL) {
function __construct(\DB\SQL $db,$table='sessions',$force=TRUE,$onsuspect=NULL,$key=NULL) {
if ($force) {
$eol="\n";
$tab="\t";
@@ -160,7 +180,6 @@ class Session extends Mapper {
$table.' ('.$eol.
$tab.$db->quotekey('session_id').' VARCHAR(40),'.$eol.
$tab.$db->quotekey('data').' TEXT,'.$eol.
$tab.$db->quotekey('csrf').' TEXT,'.$eol.
$tab.$db->quotekey('ip').' VARCHAR(40),'.$eol.
$tab.$db->quotekey('agent').' VARCHAR(255),'.$eol.
$tab.$db->quotekey('stamp').' INTEGER,'.$eol.
@@ -169,6 +188,7 @@ class Session extends Mapper {
);
}
parent::__construct($db,$table);
$this->onsuspect=$onsuspect;
session_set_save_handler(
array($this,'open'),
array($this,'close'),
@@ -178,26 +198,14 @@ class Session extends Mapper {
array($this,'cleanup')
);
register_shutdown_function('session_commit');
@session_start();
$fw=\Base::instance();
$headers=$fw->get('HEADERS');
if (($ip=$this->ip()) && $ip!=$fw->get('IP') ||
($agent=$this->agent()) &&
(!isset($headers['User-Agent']) ||
$agent!=$headers['User-Agent'])) {
if (isset($onsuspect))
$fw->call($onsuspect,array($this));
else {
session_destroy();
$fw->error(403);
}
}
$csrf=$fw->hash($fw->get('ROOT').$fw->get('BASE')).'.'.
$this->_csrf=$fw->hash($fw->get('ROOT').$fw->get('BASE')).'.'.
$fw->hash(mt_rand());
if ($this->load(array('session_id=?',$this->sid=session_id()))) {
$this->set('csrf',$csrf);
$this->save();
}
if ($key)
$fw->set($key,$this->_csrf);
$this->_agent=isset($headers['User-Agent'])?$headers['User-Agent']:'';
$this->_ip=$fw->get('IP');
}
}

View File

@@ -230,9 +230,9 @@ class Image {
$ratio=($origw=imagesx($this->data))/($origh=imagesy($this->data));
if (!$crop) {
if ($width/$ratio<=$height)
$height=$width/$ratio;
$height=round($width/$ratio);
else
$width=$height*$ratio;
$width=round($height*$ratio);
}
if (!$enlarge) {
$width=min($origw,$width);
@@ -245,12 +245,12 @@ class Image {
// Resize
if ($crop) {
if ($width/$ratio<=$height) {
$cropw=$origh*$width/$height;
$cropw=round($origh*$width/$height);
imagecopyresampled($tmp,$this->data,
0,0,($origw-$cropw)/2,0,$width,$height,$cropw,$origh);
}
else {
$croph=$origw*$height/$width;
$croph=round($origw*$height/$width);
imagecopyresampled($tmp,$this->data,
0,0,0,($origh-$croph)/2,$width,$height,$origw,$croph);
}
@@ -489,6 +489,14 @@ class Image {
return ob_get_clean();
}
/**
* Return image resource
* @return resource
**/
function data() {
return $this->data;
}
/**
* Save current state
* @return object

View File

@@ -25,7 +25,15 @@ class Session {
protected
//! Session ID
$sid;
$sid,
//! Anti-CSRF token
$_csrf,
//! User agent
$_agent,
//! IP,
$_ip,
//! Suspect callback
$onsuspect;
/**
* Open session
@@ -42,6 +50,7 @@ class Session {
* @return TRUE
**/
function close() {
$this->sid=NULL;
return TRUE;
}
@@ -51,9 +60,20 @@ class Session {
* @param $id string
**/
function read($id) {
if ($id!=$this->sid)
$this->sid=$id;
return Cache::instance()->exists($id.'.@',$data)?$data['data']:FALSE;
$this->sid=$id;
if (!$data=Cache::instance()->get($id.'.@'))
return FALSE;
if ($data['ip']!=$this->_ip || $data['agent']!=$this->_agent) {
$fw=Base::instance();
if (!isset($this->onsuspect) || FALSE===$fw->call($this->onsuspect,array($this,$id))) {
//NB: `session_destroy` can't be called at that stage (`session_start` not completed)
$this->destroy($id);
$this->close();
$fw->clear('COOKIE.'.session_name());
$fw->error(403);
}
}
return $data['data'];
}
/**
@@ -64,20 +84,12 @@ class Session {
**/
function write($id,$data) {
$fw=Base::instance();
$sent=headers_sent();
$headers=$fw->get('HEADERS');
$csrf=$fw->hash($fw->get('ROOT').$fw->get('BASE')).'.'.
$fw->hash(mt_rand());
$jar=$fw->get('JAR');
if ($id!=$this->sid)
$this->sid=$id;
Cache::instance()->set($id.'.@',
array(
'data'=>$data,
'csrf'=>$sent?$this->csrf():$csrf,
'ip'=>$fw->get('IP'),
'agent'=>isset($headers['User-Agent'])?
$headers['User-Agent']:'',
'ip'=>$this->_ip,
'agent'=>$this->_agent,
'stamp'=>time()
),
$jar['expire']?($jar['expire']-time()):0
@@ -92,9 +104,6 @@ class Session {
**/
function destroy($id) {
Cache::instance()->clear($id.'.@');
setcookie(session_name(),'',strtotime('-1 year'));
unset($_COOKIE[session_name()]);
header_remove('Set-Cookie');
return TRUE;
}
@@ -109,50 +118,55 @@ class Session {
}
/**
* Return anti-CSRF token
* @return string|FALSE
**/
* Return session id (if session has started)
* @return string|NULL
**/
function sid() {
return $this->sid;
}
/**
* Return anti-CSRF token
* @return string
**/
function csrf() {
return Cache::instance()->
exists(($this->sid?:session_id()).'.@',$data)?
$data['csrf']:FALSE;
return $this->_csrf;
}
/**
* Return IP address
* @return string|FALSE
**/
* Return IP address
* @return string
**/
function ip() {
return Cache::instance()->
exists(($this->sid?:session_id()).'.@',$data)?
$data['ip']:FALSE;
return $this->_ip;
}
/**
* Return Unix timestamp
* @return string|FALSE
**/
* Return Unix timestamp
* @return string|FALSE
**/
function stamp() {
return Cache::instance()->
exists(($this->sid?:session_id()).'.@',$data)?
$data['stamp']:FALSE;
if (!$this->sid)
session_start();
return Cache::instance()->exists($this->sid.'.@',$data)?
$data['stamp']:FALSE;
}
/**
* Return HTTP user agent
* @return string|FALSE
**/
* Return HTTP user agent
* @return string
**/
function agent() {
return Cache::instance()->
exists(($this->sid?:session_id()).'.@',$data)?
$data['agent']:FALSE;
return $this->_agent;
}
/**
* Instantiate class
* @param $onsuspect callback
* @param $key string
**/
function __construct($onsuspect=NULL) {
function __construct($onsuspect=NULL,$key=NULL) {
$this->onsuspect=$onsuspect;
session_set_save_handler(
array($this,'open'),
array($this,'close'),
@@ -162,30 +176,14 @@ class Session {
array($this,'cleanup')
);
register_shutdown_function('session_commit');
@session_start();
$fw=\Base::instance();
$headers=$fw->get('HEADERS');
if (($ip=$this->ip()) && $ip!=$fw->get('IP') ||
($agent=$this->agent()) &&
(!isset($headers['User-Agent']) ||
$agent!=$headers['User-Agent'])) {
if (isset($onsuspect))
$fw->call($onsuspect,array($this));
else {
session_destroy();
$fw->error(403);
}
}
$csrf=$fw->hash($fw->get('ROOT').$fw->get('BASE')).'.'.
$this->_csrf=$fw->hash($fw->get('ROOT').$fw->get('BASE')).'.'.
$fw->hash(mt_rand());
$jar=$fw->get('JAR');
if (Cache::instance()->exists(($this->sid=session_id()).'.@',$data)) {
$data['csrf']=$csrf;
Cache::instance()->set($this->sid.'.@',
$data,
$jar['expire']?($jar['expire']-time()):0
);
}
if ($key)
$fw->set($key,$this->_csrf);
$this->_agent=isset($headers['User-Agent'])?$headers['User-Agent']:'';
$this->_ip=$fw->get('IP');
}
}

View File

@@ -182,12 +182,13 @@ class SMTP extends Magic {
stream_socket_enable_crypto(
$socket,TRUE,STREAM_CRYPTO_METHOD_TLS_CLIENT);
$reply=$this->dialog('EHLO '.$fw->get('HOST'),$log);
if (preg_match('/8BITMIME/',$reply))
$headers['Content-Transfer-Encoding']='8bit';
else {
$headers['Content-Transfer-Encoding']='quoted-printable';
$message=quoted_printable_encode($message);
}
}
if (preg_match('/8BITMIME/',$reply))
$headers['Content-Transfer-Encoding']='8bit';
else {
$headers['Content-Transfer-Encoding']='quoted-printable';
$message=preg_replace('/^\.(.+)/m',
'..$1',quoted_printable_encode($message));
}
if ($this->user && $this->pw && preg_match('/AUTH/',$reply)) {
// Authenticate
@@ -204,9 +205,9 @@ class SMTP extends Magic {
$str='';
// Stringify headers
foreach ($headers as $key=>&$val) {
if (!in_array($key,$reqd)) {
if (!in_array($key,$reqd) && (!$this->attachments ||
$key!='Content-Type' && $key!='Content-Transfer-Encoding'))
$str.=$key.': '.$val.$eol;
}
if (in_array($key,array('From','To','Cc','Bcc')) &&
!preg_match('/[<>]/',$val))
$val='<'.$val.'>';
@@ -221,12 +222,13 @@ class SMTP extends Magic {
$this->dialog('DATA',$log);
if ($this->attachments) {
// Replace Content-Type
$hash=uniqid(NULL,TRUE);
$type=$headers['Content-Type'];
$headers['Content-Type']='multipart/mixed; '.
'boundary="'.$hash.'"';
unset($headers['Content-Type']);
$enc=$headers['Content-Transfer-Encoding'];
unset($headers['Content-Transfer-Encoding']);
$hash=uniqid(NULL,TRUE);
// Send mail headers
$out='';
$out='Content-Type: multipart/mixed; boundary="'.$hash.'"'.$eol;
foreach ($headers as $key=>$val)
if ($key!='Bcc')
$out.=$key.': '.$val.$eol;
@@ -235,16 +237,17 @@ class SMTP extends Magic {
$out.=$eol;
$out.='--'.$hash.$eol;
$out.='Content-Type: '.$type.$eol;
$out.=$eol;
$out.='Content-Transfer-Encoding: '.$enc.$eol;
$out.=$str.$eol;
$out.=$message.$eol;
foreach ($this->attachments as $attachment) {
if (is_array($attachment['filename'])) {
list($alias,$file)=each($attachment);
list($alias,$file)=each($attachment['filename']);
$filename=$alias;
$attachment['filename']=$file;
}
else
$filename=basename($attachment);
$filename=basename($attachment['filename']);
$out.='--'.$hash.$eol;
$out.='Content-Type: application/octet-stream'.$eol;
$out.='Content-Transfer-Encoding: base64'.$eol;
@@ -253,8 +256,8 @@ class SMTP extends Magic {
$out.='Content-Disposition: attachment; '.
'filename="'.$filename.'"'.$eol;
$out.=$eol;
$out.=chunk_split(
base64_encode(file_get_contents($attachment))).$eol;
$out.=chunk_split(base64_encode(
file_get_contents($attachment['filename']))).$eol;
}
$out.=$eol;
$out.='--'.$hash.'--'.$eol;
@@ -287,7 +290,7 @@ class SMTP extends Magic {
* @param $user string
* @param $pw string
**/
function __construct($host,$port,$scheme,$user,$pw) {
function __construct($host='localhost',$port=25,$scheme=null,$user=null,$pw=null) {
$this->headers=array(
'MIME-Version'=>'1.0',
'Content-Type'=>'text/plain; '.

View File

@@ -69,6 +69,7 @@ class Template extends Preview {
\Base::instance()->stringify($pair[2]));
},$pairs)).')+get_defined_vars()':
'get_defined_vars()';
$ttl=isset($attrib['ttl'])?(int)$attrib['ttl']:0;
return
'<?php '.(isset($attrib['if'])?
('if ('.$this->token($attrib['if']).') '):'').
@@ -76,7 +77,7 @@ class Template extends Preview {
(preg_match('/^\{\{(.+?)\}\}$/',$attrib['href'])?
$this->token($attrib['href']):
Base::instance()->stringify($attrib['href'])).','.
'$this->mime,'.$hive.'); ?>');
'$this->mime,'.$hive.','.$ttl.'); ?>');
}
/**
@@ -269,45 +270,40 @@ class Template extends Preview {
**/
function parse($text) {
// Build tree structure
for ($ptr=0,$len=strlen($text),$tree=array(),$node=&$tree,
$stack=array(),$depth=0,$tmp='';$ptr<$len;)
if (preg_match('/^<(\/?)(?:F3:)?'.
for ($ptr=0,$w=5,$len=strlen($text),$tree=array(),$tmp='';$ptr<$len;)
if (preg_match('/^(.{0,'.$w.'}?)<(\/?)(?:F3:)?'.
'('.$this->tags.')\b((?:\h+[\w-]+'.
'(?:\h*=\h*(?:"(?:.+?)"|\'(?:.+?)\'))?|'.
'(?:\h*=\h*(?:"(?:.*?)"|\'(?:.*?)\'))?|'.
'\h*\{\{.+?\}\})*)\h*(\/?)>/is',
substr($text,$ptr),$match)) {
if (strlen($tmp))
$node[]=$tmp;
if (strlen($tmp)||$match[1])
$tree[]=$tmp.$match[1];
// Element node
if ($match[1]) {
if ($match[2]) {
// Find matching start tag
$save=$depth;
$found=FALSE;
while ($depth>0) {
$depth--;
foreach ($stack[$depth] as $item)
if (is_array($item) && isset($item[$match[2]])) {
// Start tag found
$found=TRUE;
break 2;
}
$stack=array();
for($i=count($tree)-1;$i>=0;$i--) {
$item = $tree[$i];
if (is_array($item) && array_key_exists($match[3],$item)
&& !isset($item[$match[3]][0])) {
// Start tag found
$tree[$i][$match[3]]+=array_reverse($stack);
$tree=array_slice($tree,0,$i+1);
break;
} else $stack[]=$item;
}
if (!$found)
// Unbalanced tag
$depth=$save;
$node=&$stack[$depth];
}
else {
// Start tag
$stack[$depth]=&$node;
$node=&$node[][$match[2]];
if ($match[3]) {
$node=&$tree[][$match[3]];
$node=array();
if ($match[4]) {
// Process attributes
preg_match_all(
'/(?:\b([\w-]+)\h*'.
'(?:=\h*(?:"(.*?)"|\'(.*?)\'))?|'.
'(\{\{.+?\}\}))/s',
$match[3],$attr,PREG_SET_ORDER);
$match[4],$attr,PREG_SET_ORDER);
foreach ($attr as $kv)
if (isset($kv[4]))
$node['@attrib'][]=$kv[4];
@@ -318,26 +314,23 @@ class Template extends Preview {
(isset($kv[3]) && $kv[3]!==''?
$kv[3]:NULL));
}
if ($match[4])
// Empty tag
$node=&$stack[$depth];
else
$depth++;
}
$tmp='';
$ptr+=strlen($match[0]);
$w=5;
}
else {
// Text node
$tmp.=substr($text,$ptr,1);
$ptr++;
$tmp.=substr($text,$ptr,$w);
$ptr+=$w;
if ($w<50)
$w++;
}
if (strlen($tmp))
// Append trailing text
$node[]=$tmp;
$tree[]=$tmp;
// Break references
unset($node);
unset($stack);
return $tree;
}

View File

@@ -133,7 +133,7 @@ class Web extends Prefab {
header('Content-Type: '.($mime?:$this->mime($file)));
if ($force)
header('Content-Disposition: attachment; '.
'filename='.var_export(basename($file),TRUE));
'filename="'.basename($file).'"');
header('Accept-Ranges: bytes');
header('Content-Length: '.$size);
header('X-Powered-By: '.Base::instance()->get('PACKAGE'));
@@ -259,10 +259,13 @@ class Web extends Prefab {
**/
protected function _curl($url,$options) {
$curl=curl_init($url);
curl_setopt($curl,CURLOPT_FOLLOWLOCATION,
$options['follow_location']);
if (!ini_get('open_basedir'))
curl_setopt($curl,CURLOPT_FOLLOWLOCATION,
$options['follow_location']);
curl_setopt($curl,CURLOPT_MAXREDIRS,
$options['max_redirects']);
curl_setopt($curl,CURLOPT_PROTOCOLS,CURLPROTO_HTTP|CURLPROTO_HTTPS);
curl_setopt($curl,CURLOPT_REDIR_PROTOCOLS,CURLPROTO_HTTP|CURLPROTO_HTTPS);
curl_setopt($curl,CURLOPT_CUSTOMREQUEST,$options['method']);
if (isset($options['header']))
curl_setopt($curl,CURLOPT_HTTPHEADER,$options['header']);
@@ -288,6 +291,11 @@ class Web extends Prefab {
curl_exec($curl);
curl_close($curl);
$body=ob_get_clean();
if ($options['follow_location'] &&
preg_match('/^Location: (.+)$/m',implode(PHP_EOL,$headers),$loc)) {
$options['max_redirects']--;
return $this->request($loc[1],$options);
}
return array(
'body'=>$body,
'headers'=>$headers,
@@ -357,7 +365,8 @@ class Web extends Prefab {
if (!$socket)
return FALSE;
stream_set_blocking($socket,TRUE);
stream_set_timeout($socket,$options['timeout']);
stream_set_timeout($socket,isset($options['timeout'])?
$options['timeout']:ini_get('default_socket_timeout'));
fputs($socket,$options['method'].' '.$parts['path'].
($parts['query']?('?'.$parts['query']):'').' HTTP/1.0'.$eol
);
@@ -565,7 +574,7 @@ class Web extends Prefab {
$src=$fw->read($save);
for ($ptr=0,$len=strlen($src);$ptr<$len;) {
if (preg_match('/^@import\h+url'.
'\(\h*([\'"])(.+?)\1\h*\)[^;]*;/',
'\(\h*([\'"])((?!(?:https?:)?\/\/).+?)\1\h*\)[^;]*;/',
substr($src,$ptr),$parts)) {
$path=dirname($file);
$data.=$this->minify(
@@ -586,7 +595,8 @@ class Web extends Prefab {
// Single-line comment
$str=strstr(
substr($src,$ptr+2),"\n",TRUE);
$ptr+=strlen($str)+2;
$ptr+=(empty($str))?
strlen(substr($src,$ptr)):strlen($str)+2;
}
else {
// Presume it's a regex pattern
@@ -785,7 +795,7 @@ class Web extends Prefab {
'ù'=>'u','ű'=>'u','ů'=>'u','ư'=>'u','ū'=>'u','ǚ'=>'u',
'ǜ'=>'u','ǔ'=>'u','ǖ'=>'u','ũ'=>'u','ü'=>'ue','в'=>'v',
'ŵ'=>'w','ы'=>'y','ÿ'=>'y','ý'=>'y','ŷ'=>'y','ź'=>'z',
'ž'=>'z','з'=>'z','ż'=>'z','ж'=>'zh'
'ž'=>'z','з'=>'z','ż'=>'z','ж'=>'zh','ь'=>'','ъ'=>''
)+Base::instance()->get('DIACRITICS'))))),'-');
}

View File

@@ -7,53 +7,26 @@
*/
namespace Controller;
use Controller\Api as Api;
use Model;
class AccessController extends Controller {
/**
* event handler
* @param $f3
* @param \Base $f3
*/
function beforeroute($f3) {
function beforeroute(\Base $f3) {
parent::beforeroute($f3);
// Any CMS route of a child class of this one, requires a
// valid logged in user!
$loginCheck = $this->_checkLogIn();
// Any route/endpoint of a child class of this one,
// requires a valid logged in user!
$loginCheck = $this->checkLogTimer($f3);
if( !$loginCheck ){
// no user found or LogIn timer expired
$this->logOut($f3);
$this->logout($f3);
}
}
/**
* checks weather a user is currently logged in
* @return bool
*/
private function _checkLogIn(){
$loginCheck = false;
if($this->f3->get('SESSION.user.time') > 0){
// check logIn time
$logInTime = new \DateTime();
$logInTime->setTimestamp($this->f3->get('SESSION.user.time'));
$now = new \DateTime();
$timeDiff = $now->diff($logInTime);
$minutes = $timeDiff->days * 60 * 24 * 60;
$minutes += $timeDiff->h * 60;
$minutes += $timeDiff->i;
if($minutes <= $this->f3->get('PATHFINDER.TIMER.LOGGED')){
$loginCheck = true;
}
}
return $loginCheck;
}
}

View File

@@ -7,25 +7,24 @@
*/
namespace controller\api;
use Controller;
use Model;
class Access extends \Controller\AccessController {
class Access extends Controller\AccessController {
/**
* event handler
* @param $f3
* @param \Base $f3
*/
function beforeroute($f3) {
parent::beforeroute($f3);
function beforeroute(\Base $f3) {
// set header for all routes
header('Content-type: application/json');
parent::beforeroute($f3);
}
/**
* search user/corporation or alliance by name
* @param $f3
* search character/corporation or alliance by name
* @param \Base $f3
* @param $params
*/
public function search($f3, $params){
@@ -41,8 +40,8 @@ class Access extends \Controller\AccessController {
$accessModel = null;
switch($searchType){
case 'user':
$accessModel = Model\BasicModel::getNew('UserModel');
case 'character':
$accessModel = Model\BasicModel::getNew('CharacterModel');
break;
case 'corporation':
$accessModel = Model\BasicModel::getNew('CorporationModel');
@@ -55,12 +54,12 @@ class Access extends \Controller\AccessController {
if( is_object($accessModel) ){
// find "active" entries that have their "sharing" option activated
$accessList = $accessModel->find( array(
$accessList = $accessModel->find( [
"LOWER(name) LIKE :token AND " .
"active = 1 AND " .
"shared = 1 ",
':token' => '%' . $searchToken . '%'
));
]);
if($accessList){
foreach($accessList as $accessObject){

View File

@@ -7,27 +7,26 @@
*/
namespace Controller\Api;
use Controller;
use Model;
class Connection extends \Controller\AccessController{
class Connection extends Controller\AccessController{
/**
* @param $f3
* @param \Base $f3
*/
function beforeroute($f3) {
parent::beforeroute($f3);
function beforeroute(\Base $f3) {
// set header for all routes
header('Content-type: application/json');
parent::beforeroute($f3);
}
/**
* 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 $f3
* @param \Base $f3
*/
public function save($f3){
public function save(\Base $f3){
$postData = (array)$f3->get('POST');
$newConnectionData = [];
@@ -38,26 +37,34 @@ class Connection extends \Controller\AccessController{
$mapData = (array)$postData['mapData'];
$connectionData = (array)$postData['connectionData'];
$user = $this->_getUser();
$activeCharacter = $this->getCharacter();
if($activeCharacter){
if($user){
// get map model and check map access
/**
* @var Model\MapModel $map
*/
$map = Model\BasicModel::getNew('MapModel');
$map->getById( (int)$mapData['id'] );
if( $map->hasAccess($user) ){
$source = $map->getSystem( (int)$connectionData['source'] );
$target = $map->getSystem( (int)$connectionData['target'] );
if( $map->hasAccess($activeCharacter) ){
$source = $map->getSystemById( $connectionData['source'] );
$target = $map->getSystemById( $connectionData['target'] );
if(
!is_null($source) &&
!is_null($target)
){
/**
* @var $connection Model\ConnectionModel
*/
$connection = Model\BasicModel::getNew('ConnectionModel');
$connection->getById( (int)$connectionData['id'] );
// search if systems are neighbors
$routeController = new Route();
$routeController->initJumpData();
$route = $routeController->findRoute($connectionData['sourceName'], $connectionData['targetName'], 1);
if($route['routePossible'] == true){
@@ -90,18 +97,25 @@ class Connection extends \Controller\AccessController{
echo json_encode($newConnectionData);
}
public function delete($f3){
/**
* delete connection
* @param \Base $f3
* @throws \Exception
*/
public function delete(\Base $f3){
$connectionIds = $f3->get('POST.connectionIds');
$user = $this->_getUser();
$connection = Model\BasicModel::getNew('ConnectionModel');
if($activeCharacter = $this->getCharacter()){
/**
* @var Model\ConnectionModel $connection
*/
$connection = Model\BasicModel::getNew('ConnectionModel');
foreach($connectionIds as $connectionId){
$connection->getById($connectionId);
$connection->delete( $activeCharacter );
foreach($connectionIds as $connectionId){
$connection->getById($connectionId);
$connection->delete($user);
$connection->reset();
$connection->reset();
}
}
echo json_encode([]);

View File

@@ -7,6 +7,7 @@
*/
namespace Controller\Api;
use Controller;
use Model;
/**
@@ -14,25 +15,23 @@ use Model;
* Class Map
* @package Controller\Api
*/
class Map extends \Controller\AccessController {
class Map extends Controller\AccessController {
/**
* event handler
* @param $f3
* @param \Base $f3
*/
function beforeroute($f3) {
function beforeroute(\Base $f3) {
// set header for all routes
header('Content-type: application/json');
parent::beforeroute($f3);
}
/**
* Get all required static config data for program initialization
* @param $f3
* @param \Base $f3
*/
public function init($f3){
public function init(\Base $f3){
// expire time in seconds
$expireTimeHead = 60 * 60 * 12;
@@ -40,10 +39,11 @@ class Map extends \Controller\AccessController {
$f3->expire($expireTimeHead);
$initData = [];
$return = (object) [];
$return->error = [];
// static program data ------------------------------------------------
$initData['timer'] = $f3->get('PATHFINDER.TIMER');
$return->timer = $f3->get('PATHFINDER.TIMER');
// get all available map types ----------------------------------------
$mapType = Model\BasicModel::getNew('MapTypeModel');
@@ -60,7 +60,7 @@ class Map extends \Controller\AccessController {
$mapTypeData[$rowData->name] = $data;
}
$initData['mapTypes'] = $mapTypeData;
$return->mapTypes = $mapTypeData;
// get all available map scopes ---------------------------------------
$mapScope = Model\BasicModel::getNew('MapScopeModel');
@@ -73,7 +73,7 @@ class Map extends \Controller\AccessController {
];
$mapScopeData[$rowData->name] = $data;
}
$initData['mapScopes'] = $mapScopeData;
$return->mapScopes = $mapScopeData;
// get all available system status ------------------------------------
$systemStatus = Model\BasicModel::getNew('SystemStatusModel');
@@ -87,7 +87,7 @@ class Map extends \Controller\AccessController {
];
$systemScopeData[$rowData->name] = $data;
}
$initData['systemStatus'] = $systemScopeData;
$return->systemStatus = $systemScopeData;
// get all available system types -------------------------------------
$systemType = Model\BasicModel::getNew('SystemTypeModel');
@@ -100,7 +100,7 @@ class Map extends \Controller\AccessController {
];
$systemTypeData[$rowData->name] = $data;
}
$initData['systemType'] = $systemTypeData;
$return->systemType = $systemTypeData;
// get available connection scopes ------------------------------------
$connectionScope = Model\BasicModel::getNew('ConnectionScopeModel');
@@ -114,7 +114,7 @@ class Map extends \Controller\AccessController {
];
$connectionScopeData[$rowData->name] = $data;
}
$initData['connectionScopes'] = $connectionScopeData;
$return->connectionScopes = $connectionScopeData;
// get available character status -------------------------------------
$characterStatus = Model\BasicModel::getNew('CharacterStatusModel');
@@ -128,24 +128,40 @@ class Map extends \Controller\AccessController {
];
$characterStatusData[$rowData->name] = $data;
}
$initData['characterStatus'] = $characterStatusData;
$return->characterStatus = $characterStatusData;
// get max number of shared entities per map --------------------------
$maxSharedCount = [
'user' => $f3->get('PATHFINDER.MAX_SHARED_USER'),
'character' => $f3->get('PATHFINDER.MAX_SHARED_CHARACTER'),
'corporation' => $f3->get('PATHFINDER.MAX_SHARED_CORPORATION'),
'alliance' => $f3->get('PATHFINDER.MAX_SHARED_ALLIANCE'),
];
$initData['maxSharedCount'] = $maxSharedCount;
$return->maxSharedCount = $maxSharedCount;
echo json_encode($initData);
// get program routes -------------------------------------------------
$return->routes = [
'ssoLogin' => $this->getF3()->alias( 'sso', ['action' => 'requestAuthorization'] )
];
// get SSO error messages that should be shown immediately ------------
// -> e.g. errors while character switch from previous HTTP requests
if( $f3->exists(Controller\Ccp\Sso::SESSION_KEY_SSO_ERROR) ){
$ssoError = (object) [];
$ssoError->type = 'error';
$ssoError->title = 'Login failed';
$ssoError->message = $f3->get(Controller\Ccp\Sso::SESSION_KEY_SSO_ERROR);
$return->error[] = $ssoError;
$f3->clear(Controller\Ccp\Sso::SESSION_KEY_SSO_ERROR);
}
echo json_encode($return);
}
/**
* import new map data
* @param $f3
* @param \Base $f3
*/
public function import($f3){
public function import(\Base $f3){
$importData = (array)$f3->get('POST');
$return = (object) [];
@@ -155,13 +171,23 @@ class Map extends \Controller\AccessController {
isset($importData['typeId']) &&
count($importData['mapData']) > 0
){
$user = $this->_getUser();
$activeCharacter = $this->getCharacter();
if($user){
$activeCharacter = $user->getActiveUserCharacter();
if($activeCharacter){
/**
* @var $map Model\MapModel
*/
$map = Model\BasicModel::getNew('MapModel');
/**
* @var $system Model\SystemModel
*/
$system = Model\BasicModel::getNew('SystemModel');
/**
* @var $connection Model\ConnectionModel
*/
$connection = Model\BasicModel::getNew('ConnectionModel');
foreach($importData['mapData'] as $mapData){
@@ -194,8 +220,8 @@ class Map extends \Controller\AccessController {
$system->setData($systemData);
$system->mapId = $map;
$system->createdCharacterId = $activeCharacter->characterId;
$system->updatedCharacterId = $activeCharacter->characterId;
$system->createdCharacterId = $activeCharacter;
$system->updatedCharacterId = $activeCharacter;
$system->save();
$tempSystemIdMapping[$oldId] = $system->id;
@@ -203,7 +229,6 @@ class Map extends \Controller\AccessController {
}
}
foreach($mapData['data']['connections'] as $connectionData){
// check if source and target IDs match with new system ID
if(
@@ -226,15 +251,13 @@ class Map extends \Controller\AccessController {
// map access info should not automatically imported
if($map->isPrivate()){
$map->setAccess($user);
$map->setAccess($activeCharacter);
}elseif($map->isCorporation()){
$corporation = $activeCharacter->getCharacter()->getCorporation();
if($corporation){
if($corporation = $activeCharacter->getCorporation()){
$map->setAccess($corporation);
}
}elseif($map->isAlliance()){
$alliance = $activeCharacter->getCharacter()->getAlliance();
if($alliance){
if($alliance = $activeCharacter->getAlliance()){
$map->setAccess($alliance);
}
}
@@ -260,7 +283,7 @@ class Map extends \Controller\AccessController {
}
}else{
// user not found
$return->error[] = $this->getUserLoggedOffError();
$return->error[] = $this->getLogoutError();
}
}else{
// map data missing
@@ -276,25 +299,28 @@ class Map extends \Controller\AccessController {
/**
* save a new map or update an existing map
* @param $f3
* @param \Base $f3
*/
public function save($f3){
public function save(\Base $f3){
$formData = (array)$f3->get('POST.formData');
$return = (object) [];
$return->error = [];
if( isset($formData['id']) ){
$activeCharacter = $this->getCharacter(0);
$user = $this->_getUser(0);
if($activeCharacter){
if($user){
/**
* @var $map Model\MapModel
*/
$map = Model\BasicModel::getNew('MapModel');
$map->getById( (int)$formData['id'] );
if(
$map->dry() ||
$map->hasAccess($user)
$map->hasAccess($activeCharacter)
){
// new map
$map->setData($formData);
@@ -303,118 +329,117 @@ class Map extends \Controller\AccessController {
// save global map access. Depends on map "type"
if($map->isPrivate()){
// share map between users -> set access
if(isset($formData['mapUsers'])){
// share map between characters -> set access
if(isset($formData['mapCharacters'])){
// avoid abuse -> respect share limits
$accessUsers = array_slice( $formData['mapUsers'], 0, $f3->get('PATHFINDER.MAX_SHARED_USER') );
$accessCharacters = array_slice( $formData['mapCharacters'], 0, $f3->get('PATHFINDER.MAX_SHARED_CHARACTER') );
// clear map access. In case something has removed from access list
$map->clearAccess();
$tempUser = Model\BasicModel::getNew('UserModel');
/**
* @var $tempCharacter Model\CharacterModel
*/
$tempCharacter = Model\BasicModel::getNew('CharacterModel');
foreach($accessUsers as $userId){
$tempUser->getById( (int)$userId );
foreach($accessCharacters as $characterId){
$tempCharacter->getById( (int)$characterId );
if(
!$tempUser->dry() &&
$tempUser->shared == 1 // check if map shared is enabled
!$tempCharacter->dry() &&
$tempCharacter->shared == 1 // check if map shared is enabled
){
$map->setAccess($tempUser);
$map->setAccess($tempCharacter);
}
$tempUser->reset();
$tempCharacter->reset();
}
}
// the current user itself should always have access
// the current character itself should always have access
// just in case he removed himself :)
$map->setAccess($user);
$map->setAccess($activeCharacter);
}elseif($map->isCorporation()){
$activeCharacter = $user->getActiveUserCharacter();
$corporation = $activeCharacter->getCorporation();
if($activeCharacter){
$corporation = $activeCharacter->getCharacter()->getCorporation();
if($corporation){
// the current user has to have a corporation when
// working on corporation maps!
if($corporation){
// the current user has to have a corporation when
// working on corporation maps!
// share map between corporations -> set access
if(isset($formData['mapCorporations'])){
// avoid abuse -> respect share limits
$accessCorporations = array_slice( $formData['mapCorporations'], 0, $f3->get('PATHFINDER.MAX_SHARED_CORPORATION') );
// share map between corporations -> set access
if(isset($formData['mapCorporations'])){
// avoid abuse -> respect share limits
$accessCorporations = array_slice( $formData['mapCorporations'], 0, $f3->get('PATHFINDER.MAX_SHARED_CORPORATION') );
// clear map access. In case something has removed from access list
$map->clearAccess();
// clear map access. In case something has removed from access list
$map->clearAccess();
/**
* @var $tempCorporation Model\CorporationModel
*/
$tempCorporation = Model\BasicModel::getNew('CorporationModel');
$tempCorporation = Model\BasicModel::getNew('CorporationModel');
foreach($accessCorporations as $corporationId){
$tempCorporation->getById( (int)$corporationId );
foreach($accessCorporations as $corporationId){
$tempCorporation->getById( (int)$corporationId );
if(
!$tempCorporation->dry() &&
$tempCorporation->shared == 1 // check if map shared is enabled
){
$map->setAccess($tempCorporation);
}
$tempCorporation->reset();
if(
!$tempCorporation->dry() &&
$tempCorporation->shared == 1 // check if map shared is enabled
){
$map->setAccess($tempCorporation);
}
}
// the corporation of the current user should always have access
$map->setAccess($corporation);
$tempCorporation->reset();
}
}
// the corporation of the current user should always have access
$map->setAccess($corporation);
}
}elseif($map->isAlliance()){
$activeCharacter = $user->getActiveUserCharacter();
$alliance = $activeCharacter->getAlliance();
if($activeCharacter){
$alliance = $activeCharacter->getCharacter()->getAlliance();
if($alliance){
// the current user has to have a alliance when
// working on alliance maps!
if($alliance){
// the current user has to have a alliance when
// working on alliance maps!
// share map between alliances -> set access
if(isset($formData['mapAlliances'])){
// avoid abuse -> respect share limits
$accessAlliances = array_slice( $formData['mapAlliances'], 0, $f3->get('PATHFINDER.MAX_SHARED_ALLIANCE') );
// share map between alliances -> set access
if(isset($formData['mapAlliances'])){
// avoid abuse -> respect share limits
$accessAlliances = array_slice( $formData['mapAlliances'], 0, $f3->get('PATHFINDER.MAX_SHARED_ALLIANCE') );
// clear map access. In case something has removed from access list
$map->clearAccess();
// clear map access. In case something has removed from access list
$map->clearAccess();
/**
* @var $tempAlliance Model\AllianceModel
*/
$tempAlliance = Model\BasicModel::getNew('AllianceModel');
$tempAlliance = Model\BasicModel::getNew('AllianceModel');
foreach($accessAlliances as $allianceId){
$tempAlliance->getById( (int)$allianceId );
foreach($accessAlliances as $allianceId){
$tempAlliance->getById( (int)$allianceId );
if(
!$tempAlliance->dry() &&
$tempAlliance->shared == 1 // check if map shared is enabled
){
$map->setAccess($tempAlliance);
}
$tempAlliance->reset();
if(
!$tempAlliance->dry() &&
$tempAlliance->shared == 1 // check if map shared is enabled
){
$map->setAccess($tempAlliance);
}
$tempAlliance->reset();
}
// the alliance of the current user should always have access
$map->setAccess($alliance);
}
// the alliance of the current user should always have access
$map->setAccess($alliance);
}
}
// reload the same map model (refresh)
// this makes sure all data is up2date
$map->getById( $map->id, 0 );
$return->mapData = $map->getData();
}else{
// map access denied
$captchaError = (object) [];
@@ -423,7 +448,6 @@ class Map extends \Controller\AccessController {
$return->error[] = $captchaError;
}
}
}else{
// map id field missing
$idError = (object) [];
@@ -437,17 +461,19 @@ class Map extends \Controller\AccessController {
/**
* delete a map and all dependencies
* @param $f3
* @param \Base $f3
*/
public function delete($f3){
public function delete(\Base $f3){
$mapData = (array)$f3->get('POST.mapData');
$activeCharacter = $this->getCharacter();
$user = $this->_getUser();
if($user){
if($activeCharacter){
/**
* @var $map Model\MapModel
*/
$map = Model\BasicModel::getNew('MapModel');
$map->getById($mapData['id']);
$map->delete($user);
$map->delete( $activeCharacter );
}
echo json_encode([]);
@@ -455,34 +481,28 @@ class Map extends \Controller\AccessController {
/**
* update map data
* function is called continuously
* @param $f3
* -> function is called continuously (trigger) by any active client
* @param \Base $f3
*/
public function updateData($f3){
// cache time(s) per user should be equal or less than this function is called
// prevent request flooding
$responseTTL = $f3->get('PATHFINDER.TIMER.UPDATE_SERVER_MAP.DELAY') / 1000;
public function updateData(\Base $f3){
$mapData = (array)$f3->get('POST.mapData');
$user = $this->_getUser();
$activeCharacter = $this->getCharacter();
$return = (object) [];
$return->error = [];
if($user){
// -> get active character
$activeCharacter = $user->getActiveUserCharacter();
if($activeCharacter){
$cacheKey = 'user_map_data_' . $activeCharacter->id;
$cacheKey = 'user_map_data_' . $activeCharacter->_id;
// if there is any system/connection change data submitted -> save new data
if(
$f3->exists($cacheKey) === false ||
!empty($mapData)
!empty($mapData) ||
!$f3->exists($cacheKey)
){
// get current map data ========================================================
$maps = $user->getMaps();
$maps = $activeCharacter->getMaps();
// loop all submitted map data that should be saved
// -> currently there will only be ONE map data change submitted -> single loop
@@ -532,7 +552,7 @@ class Map extends \Controller\AccessController {
unset($systemData['updated']);
$system = $filteredMap->systems->current();
$system->setData($systemData);
$system->updatedCharacterId = $activeCharacter->characterId;
$system->updatedCharacterId = $activeCharacter;
$system->save();
// a system belongs to ONE map -> speed up for multiple maps
@@ -561,7 +581,7 @@ class Map extends \Controller\AccessController {
unset($connectionData['updated']);
$connection = $filteredMap->connections->current();
$connection->setData($connectionData);
$connection->save($user);
$connection->save();
// a connection belongs to ONE map -> speed up for multiple maps
unset($connectionData[$i]);
@@ -574,6 +594,11 @@ class Map extends \Controller\AccessController {
// format map Data for return
$return->mapData = self::getFormattedMapData($maps);
// cache time(s) per user should be equal or less than this function is called
// prevent request flooding
$responseTTL = (int)$f3->get('PATHFINDER.TIMER.UPDATE_SERVER_MAP.DELAY') / 1000;
$f3->set($cacheKey, $return, $responseTTL);
}else{
// get from cache
@@ -582,23 +607,24 @@ class Map extends \Controller\AccessController {
}else{
// user logged off
$return->error[] = $this->getUserLoggedOffError();
$return->error[] = $this->getLogoutError();
}
echo json_encode( $return );
}
/**
* get formatted map data
* @param $mapModels
* @return Model\MapModel[]
* @return array
*/
public static function getFormattedMapData($mapModels){
$mapData = [];
foreach($mapModels as $mapModel){
foreach($mapModels as &$mapModel){
/**
* @var $mapModel Model\MapModel
*/
$allMapData = $mapModel->getData();
$mapData[] = [
'config' => $allMapData->mapData,
'data' => [
@@ -613,33 +639,23 @@ class Map extends \Controller\AccessController {
/**
* update map data api
* function is called continuously
* @param $f3
* -> function is called continuously by any active client
* @param \Base $f3
*/
public function updateUserData($f3){
// cache time(s) should be equal or less than request trigger time
// prevent request flooding
$responseTTL = $f3->get('PATHFINDER.TIMER.UPDATE_SERVER_USER_DATA.DELAY') / 1000;
// if the cache key will be set -> cache request
$cacheKey = null;
public function updateUserData(\Base $f3){
$return = (object) [];
$return->error = [];
$activeCharacter = $this->getCharacter(0);
$user = $this->_getUser();
if($user){
if($activeCharacter){
if( !empty($f3->get('POST.mapIds')) ){
$mapIds = (array)$f3->get('POST.mapIds');
// check if data for specific system is requested
$systemData = (array)$f3->get('POST.systemData');
// update current location (IGB data)
$user->updateCharacterLog(60 * 5);
// update current location
// -> suppress temporary timeout errors
$activeCharacter = $activeCharacter->updateLog(['suppressTimeoutErrors' => true]);
// if data is requested extend the cache key in order to get new data
$requestSystemData = (object) [];
@@ -649,24 +665,23 @@ class Map extends \Controller\AccessController {
// IMPORTANT for now -> just update a single map (save performance)
$mapIds = array_slice($mapIds, 0, 1);
// the userMasData is cached per map (this must be changed if multiple maps
// the userMapData is cached per map (this must be changed if multiple maps
// will be allowed in future...
$tempId = (int)$mapIds[0];
$cacheKey = 'user_data_' . $tempId . '_' . $requestSystemData->systemId;
if( $f3->exists($cacheKey) === false ){
if( !$f3->exists($cacheKey) ){
foreach($mapIds as $mapId){
$map = $user->getMap($mapId);
$map = $activeCharacter->getMap( (int)$mapId);
if( !is_null($map) ){
$return->mapUserData[] = $map->getUserData();
// request signature data for a system if user has map access!
if( $map->id === $requestSystemData->mapId ){
$system = $map->getSystem( $requestSystemData->systemId );
$system = $map->getSystemById( $requestSystemData->systemId );
if( !is_null($system) ){
// data for the current selected system
// data for currently selected system
$return->system = $system->getData();
$return->system->signatures = $system->getSignaturesData();
}
@@ -674,6 +689,10 @@ class Map extends \Controller\AccessController {
}
}
// cache time (seconds) should be equal or less than request trigger time
// prevent request flooding
$responseTTL = (int)$f3->get('PATHFINDER.TIMER.UPDATE_SERVER_USER_DATA.DELAY') / 1000;
// cache response
$f3->set($cacheKey, $return, $responseTTL);
}else{
@@ -686,14 +705,12 @@ class Map extends \Controller\AccessController {
// get current user data -> this should not be cached because each user has different personal data
// even if they have multiple characters using the same map!
$return->userData = $user->getData();
$return->userData = $activeCharacter->getUser()->getData();
}else{
// user logged off
$return->error[] = $this->getUserLoggedOffError();
$return->error[] = $this->getLogoutError();
}
echo json_encode( $return );
}

View File

@@ -18,10 +18,16 @@ use Model;
class Route extends \Controller\AccessController {
/**
* cache time for static jump data
* cache time for static jump data (e.g. K-Space stargates)
* @var int
*/
private $jumpDataCacheTime = 86400;
private $staticJumpDataCacheTime = 86400;
/**
* cache time for dynamic jump data (e.g. W-Space systems, Jumpbridges. ...)
* @var int
*/
private $dynamicJumpDataCacheTime = 10;
/**
* array system information grouped by systemId
@@ -41,11 +47,27 @@ class Route extends \Controller\AccessController {
*/
private $idArray = [];
/**
* set jump data for route search
* -> this function is required for route search! (Don´t forget)
* @param array $mapIds
* @param array $filterData
*/
public function initJumpData($mapIds = [], $filterData = []){
// add static data (e.g. K-Space stargates,..)
$this->setStaticJumpData();
// add map specific data
$this->setDynamicJumpData($mapIds, $filterData);
}
/**
* set static system jump data for this instance
* the data is fixed and should not change
* -> jump data includes JUST "static" connections (Stargates)
* -> this data is equal for EACH route search (does not depend on map data)
*/
private function setSystemJumpData(){
private function setStaticJumpData(){
$cacheKey = 'staticJumpData';
$f3 = $this->getF3();
@@ -59,42 +81,167 @@ class Route extends \Controller\AccessController {
$f3->exists($cacheKeyJumpArray) &&
$f3->exists($cacheKeyIdArray)
){
// get cached values
// get cached values
$this->nameArray = $f3->get($cacheKeyNamedArray);
$this->jumpArray = $f3->get($cacheKeyJumpArray);
$this->idArray = $f3->get($cacheKeyIdArray);
}else{
// nothing cached
$pfDB = $this->getDB('PF');
$query = "SELECT * FROM system_neighbour";
$rows = $pfDB->exec($query, null, $this->jumpDataCacheTime);
$rows = $this->getDB()->exec($query, null, $this->staticJumpDataCacheTime);
if(count($rows) > 0){
foreach($rows as $row){
$regionId = $row['regionId'];
$constId = $row['constellationId'];
$systemName = strtoupper($row['systemName']);
$systemId = $row['systemId'];
$secStatus = $row['trueSec'];
$this->updateJumpData($rows);
$this->nameArray[$systemId][0] = $systemName;
$this->nameArray[$systemId][1] = $regionId;
$this->nameArray[$systemId][2] = $constId;
$this->nameArray[$systemId][3] = $secStatus;
// static data should be cached
$f3->set($cacheKeyNamedArray, $this->nameArray, $this->staticJumpDataCacheTime);
$f3->set($cacheKeyJumpArray, $this->jumpArray, $this->staticJumpDataCacheTime);
$f3->set($cacheKeyIdArray, $this->idArray, $this->staticJumpDataCacheTime);
}
}
}
$this->idArray[strtoupper($systemName)] = $systemId;
/**
* set/add dynamic system jump data for specific "mapId"´s
* -> this data is dynamic and could change on any map change
* -> (e.g. new system added, connection added/updated, ...)
* @param array $mapIds
* @param array $filterData
*/
private function setDynamicJumpData($mapIds = [], $filterData = []){
$this->jumpArray[$systemName]= explode(":", strtoupper($row['jumpNodes']));
array_push($this->jumpArray[$systemName],$systemId);
if( !empty($mapIds) ){
// make sure, mapIds are integers (protect against SQL injections)
$mapIds = array_map('intval', $mapIds);
// connection filter --------------------------------------------------------
$whereQuery = "";
$includeScopes = [];
$includeTypes = [];
if( $filterData['stargates'] === true){
// include "stargates" for search
$includeScopes[] = 'stargate';
$includeTypes[] = 'stargate';
}
if( $filterData['jumpbridges'] === true ){
// add jumpbridge connections for search
$includeScopes[] = 'jumpbridge';
$includeTypes[] = 'jumpbridge';
}
if( $filterData['wormholes'] === true ){
// add wormhole connections for search
$includeScopes[] = 'wh';
$includeTypes[] = 'wh_fresh';
if( $filterData['wormholesReduced'] === true ){
$includeTypes[] = 'wh_reduced';
}
$f3->set($cacheKeyNamedArray, $this->nameArray, $this->jumpDataCacheTime);
$f3->set($cacheKeyJumpArray, $this->jumpArray, $this->jumpDataCacheTime);
$f3->set($cacheKeyIdArray, $this->idArray, $this->jumpDataCacheTime);
if( $filterData['wormholesCritical'] === true ){
$includeTypes[] = 'wh_critical';
}
}
// search connections -------------------------------------------------------
if( !empty($includeScopes) ){
$whereQuery .= " connection.scope IN ('" . implode("', '", $includeScopes) . "') AND ";
if( !empty($includeTypes) ){
$whereQuery .= " connection.type REGEXP '" . implode("|", $includeTypes) . "' AND ";
}
$query = "SELECT
system_src.regionId regionId,
system_src.constellationId constellationId,
system_src.name systemName,
system_src.systemId systemId,
(
SELECT
GROUP_CONCAT( NULLIF(system_tar.name, NULL) SEPARATOR ':')
FROM
connection INNER JOIN
system system_tar ON
system_tar.id = connection.source OR
system_tar.id = connection.target
WHERE
(
connection.source = system_src.id OR
connection.target = system_src.id
) AND
" . $whereQuery . "
connection.active = 1 AND
system_tar.id != system_src.id AND
system_tar.active = 1
) jumpNodes,
system_src.trueSec trueSec
FROM
system system_src INNER JOIN
map ON
map.id = system_src.mapId
WHERE
system_src.mapId IN (" . implode(', ', $mapIds) . ") AND
system_src.active = 1 AND
map.active = 1
HAVING
-- skip systems without neighbors (e.g. WHs)
jumpNodes IS NOT NULL
";
$rows = $this->getDB()->exec($query, null, $this->dynamicJumpDataCacheTime);
if(count($rows) > 0){
// update jump data for this instance
$this->updateJumpData($rows);
}
}
}
}
/**
* update jump data for this instance
* -> data is either coming from CCPs static export OR from map specific data
* @param array $rows
*/
private function updateJumpData($rows = []){
foreach($rows as $row){
$regionId = (int)$row['regionId'];
$constId = (int)$row['constellationId'];
$systemName = strtoupper($row['systemName']);
$systemId = (int)$row['systemId'];
$secStatus = (float)$row['trueSec'];
// fill "nameArray" data ----------------------------------------------------
if( !isset($this->nameArray[$systemId]) ){
$this->nameArray[$systemId][0] = $systemName;
$this->nameArray[$systemId][1] = $regionId;
$this->nameArray[$systemId][2] = $constId;
$this->nameArray[$systemId][3] = $secStatus;
}
// fill "idArray" data ------------------------------------------------------
if( !isset($this->idArray[$systemName]) ){
$this->idArray[$systemName] = $systemId;
}
// fill "jumpArray" data ----------------------------------------------------
if( !is_array($this->jumpArray[$systemName]) ){
$this->jumpArray[$systemName] = [];
}
$this->jumpArray[$systemName] = array_merge( explode(':', strtoupper($row['jumpNodes'])), $this->jumpArray[$systemName] );
// add systemId to end (if not already there)
if(end($this->jumpArray[$systemName]) != $systemId){
array_push($this->jumpArray[$systemName],$systemId);
}
}
}
@@ -284,8 +431,6 @@ class Route extends \Controller\AccessController {
!empty($systemTo)
){
$this->setSystemJumpData();
$from = strtoupper( $systemFrom );
$to = strtoupper( $systemTo );
@@ -294,7 +439,6 @@ class Route extends \Controller\AccessController {
if( isset($this->jumpArray[$from]) ){
// check if the system we are looking for is a direct neighbour
foreach( $this->jumpArray[$from] as $n ) {
@@ -352,45 +496,145 @@ class Route extends \Controller\AccessController {
return $routeData;
}
/**
* get key for route cache
* @param $mapIds
* @param $systemFrom
* @param $systemTo
* @param array $filterData
* @return string
*/
private function getRouteCacheKey($mapIds, $systemFrom, $systemTo, $filterData = []){
$keyParts = [
implode('_', $mapIds),
self::formatHiveKey($systemFrom),
self::formatHiveKey($systemTo)
];
$keyParts += $filterData;
$key = 'route_' . hash('md5', implode('_', $keyParts));
return $key;
}
/**
* search multiple route between two systems
* @param $f3
* @param \Base $f3
*/
public function search($f3){
$routesData = $data = (array)$f3->get('POST.routeData');
$requestData = (array)$f3->get('POST');
$activeCharacter = $this->getCharacter();
$return = (object) [];
$return->error = [];
$return->routesData = [];
foreach($routesData as $routeData){
$cacheKey = self::formatHiveKey($routeData['systemFrom']) . '_' . self::formatHiveKey($routeData['systemTo']);
if(
$activeCharacter &&
!empty($requestData['routeData'])
){
if($f3->exists($cacheKey)){
// get data from cache
$return->routesData[] = $f3->get($cacheKey);
}else{
// no cached route data found
$foundRoutData = $this->findRoute($routeData['systemFrom'], $routeData['systemTo']);
$routesData = (array)$requestData['routeData'];
// cache if route was found
if(
isset($foundRoutData['routePossible']) &&
$foundRoutData['routePossible'] === true
){
$f3->set($cacheKey, $foundRoutData, $this->jumpDataCacheTime);
// map data where access was already checked -> cached data
$validMaps = [];
/**
* @var $map Model\MapModel
*/
$map = Model\BasicModel::getNew('MapModel');
foreach($routesData as $key => $routeData){
// mapIds are optional. If mapIds is empty or not set
// route search is limited to CCPs static data
$mapData = (array)$routeData['mapIds'];
$mapData = array_flip( array_map('intval', $mapData) );
// check map access (filter requested mapIDs and format) --------------------
array_walk($mapData, function(&$item, &$key, $data){
if( isset($data[1][$key]) ){
// character has mas access -> do not check again
$item = $data[1][$key];
}else{
// check map access for current character
$data[0]->getById($key);
if( $data[0]->hasAccess($data[2]) ){
$item = ['id' => $key, 'name' => $data[0]->name];
}else{
$item = false;
}
$data[0]->reset();
}
}, [$map, $validMaps, $activeCharacter]);
// filter maps with NO access right
$mapData = array_filter($mapData);
$mapIds = array_column($mapData, 'id');
// add map data to cache array
$validMaps += $mapData;
// search route with filter options
$filterData = [
'stargates' => (bool) $routeData['stargates'],
'jumpbridges' => (bool) $routeData['jumpbridges'],
'wormholes' => (bool) $routeData['wormholes'],
'wormholesReduced' => (bool) $routeData['wormholesReduced'],
'wormholesCritical' => (bool) $routeData['wormholesCritical']
];
$returnRoutData = [
'systemFrom' => $routeData['systemFrom'],
'systemTo' => $routeData['systemTo'],
'maps' => $mapData,
'mapIds' => $mapIds
];
// add filter options for each route as well
$returnRoutData += $filterData;
if(count($mapIds) > 0){
$cacheKey = $this->getRouteCacheKey(
$mapIds,
$routeData['systemFrom'],
$routeData['systemTo'],
$filterData
);
if($f3->exists($cacheKey)){
// get data from cache
$returnRoutData = $f3->get($cacheKey);
}else{
// set jump data for following route search
$this->initJumpData($mapIds, $filterData);
// no cached route data found
$foundRoutData = $this->findRoute($routeData['systemFrom'], $routeData['systemTo']);
$returnRoutData = array_merge($returnRoutData, $foundRoutData);
// cache if route was found
if(
isset($returnRoutData['routePossible']) &&
$returnRoutData['routePossible'] === true
){
$f3->set($cacheKey, $returnRoutData, $this->dynamicJumpDataCacheTime);
}
}
}
$return->routesData[] = $foundRoutData;
$return->routesData[] = $returnRoutData;
}
}
echo json_encode($return);
}
}

View File

@@ -7,43 +7,49 @@
*/
namespace Controller\Api;
use Controller;
use Model;
class Signature extends \Controller\AccessController{
class Signature extends Controller\AccessController{
/**
* event handler
* @param $f3
* @param \Base $f3
*/
function beforeroute($f3) {
parent::beforeroute($f3);
function beforeroute(\Base $f3) {
// set header for all routes
header('Content-type: application/json');
parent::beforeroute($f3);
}
/**
* get signature data for systems
* @param $f3
* -> return value of this is limited to a "SINGLE" system
* @param \Base $f3
*/
public function getAll($f3){
$signatureData = [];
$systemIds = $f3->get('POST.systemIds');
$systemIds = (array)$f3->get('POST.systemIds');
$user = $this->_getUser();
if( !empty($systemIds) ){
$activeCharacter = $this->getCharacter();
$system = Model\BasicModel::getNew('SystemModel');
if($activeCharacter){
/**
* @var Model\SystemModel $system
*/
$system = Model\BasicModel::getNew('SystemModel');
foreach($systemIds as $systemId){
$system->getById($systemId);
if(
!$system->dry() &&
$system->hasAccess($activeCharacter)
){
$signatureData = $system->getSignaturesData();
}
foreach($systemIds as $systemId){
$system->getById($systemId);
if(!$system->dry()){
// check access
if($system->hasAccess($user)){
$signatureData = $system->getSignaturesData();
$system->reset();
}
}
}
@@ -74,11 +80,13 @@ class Signature extends \Controller\AccessController{
}
if( !is_null($signatureData) ){
$user = $this->_getUser();
$activeCharacter = $this->getCharacter();
if($user){
$activeUserCharacter = $user->getActiveUserCharacter();
$activeCharacter = $activeUserCharacter->getCharacter();
if($activeCharacter){
/**
* @var Model\SystemModel $system
*/
$system = Model\BasicModel::getNew('SystemModel');
// update/add all submitted signatures
@@ -91,12 +99,15 @@ class Signature extends \Controller\AccessController{
if(!$system->dry()){
// update/save signature
/**
* @var $signature Model\SystemSignatureModel
*/
$signature = null;
if( isset($data['pk']) ){
// try to get system by "primary key"
$signature = $system->getSignatureById($user, (int)$data['pk']);
$signature = $system->getSignatureById($activeCharacter, (int)$data['pk']);
}elseif( isset($data['name']) ){
$signature = $system->getSignatureByName($user, $data['name']);
$signature = $system->getSignatureByName($activeCharacter, $data['name']);
}
if( is_null($signature) ){
@@ -135,8 +146,13 @@ class Signature extends \Controller\AccessController{
// description should not be updated
unset( $data['description'] );
// prevent some data from overwrite manually changes
// wormhole typeID can not figured out/saved by the sig reader dialog
if($data['groupId'] == 5){
// -> type could not be identified -> do not overwrite them (e.g. sig update)
if(
$data['groupId'] == 5 ||
$data['typeId'] == 0
){
unset( $data['typeId'] );
}
@@ -155,6 +171,9 @@ class Signature extends \Controller\AccessController{
// get a fresh signature object with the new data. This is a bad work around!
// but i could not figure out what the problem was when using the signature model, saved above :(
// -> some caching problems
/**
* @var $newSignature Model\SystemSignatureModel
*/
$newSignature = Model\BasicModel::getNew('SystemSignatureModel');
$newSignature->getById( $signature->id, 0);
@@ -173,23 +192,23 @@ class Signature extends \Controller\AccessController{
/**
* delete signatures
* @param $f3
* @param \Base $f3
*/
public function delete($f3){
$signatureIds = $f3->get('POST.signatureIds');
$activeCharacter = $this->getCharacter();
$user = $this->_getUser();
/**
* @var Model\SystemSignatureModel $signature
*/
$signature = Model\BasicModel::getNew('SystemSignatureModel');
foreach($signatureIds as $signatureId){
$signature->getById($signatureId);
$signature->delete($user);
$signature->delete( $activeCharacter );
$signature->reset();
}
echo json_encode([]);
}
}

View File

@@ -7,20 +7,22 @@
*/
namespace Controller\Api;
use Controller\Ccp\Sso;
use Data\Mapper as Mapper;
use Model;
class System extends \Controller\AccessController {
private $mainQuery = "SELECT
map_sys.constellationID connstallation_id,
map_sys.solarSystemID system_id,
map_sys.solarSystemName system_name,
ROUND( map_sys.security, 2) system_security,
map_con.constellationName constallation_name,
map_reg.regionID region_id,
map_reg.regionName region_name,
'' type,
map_sys.constellationID `connstallation_id`,
map_sys.solarSystemID `system_id`,
map_sys.solarSystemName `system_name`,
map_sys.security `system_security`,
map_con.constellationName `constallation_name`,
map_reg.regionID `region_id`,
map_reg.regionName `region_name`,
'0' `trueSec`,
'' `type`,
IFNULL(
(
SELECT
@@ -33,7 +35,7 @@ class System extends \Controller\AccessController {
system_effect.groupID = 995 AND
map_norm.solarSystemID = map_sys.solarSystemID
LIMIT 1
), '') effect,
), '') `effect`,
IFNULL(
(
SELECT
@@ -43,7 +45,7 @@ class System extends \Controller\AccessController {
WHERE
map_worm_class.locationID = map_sys.regionID
LIMIT 1
), 7) security
), 7) `security`
FROM
mapSolarSystems map_sys INNER JOIN
mapConstellations map_con ON
@@ -63,9 +65,9 @@ class System extends \Controller\AccessController {
private $limitQuery = "";
/**
* @param $f3
* @param \Base $f3
*/
function beforeroute($f3) {
function beforeroute(\Base $f3) {
parent::beforeroute($f3);
@@ -92,7 +94,8 @@ class System extends \Controller\AccessController {
* get static system Data from CCPs Static DB export
* search column for IDs can be (solarSystemID, regionID, constellationID)
* @param array $columnIDs
* @return null
* @param string $column
* @return Model\SystemModel[]
* @throws \Exception
*/
protected function _getSystemModelByIds($columnIDs = [], $column = 'solarSystemID'){
@@ -110,10 +113,12 @@ class System extends \Controller\AccessController {
// format result
$mapper = new Mapper\CcpSystemsMapper($rows);
$ccpSystemsData = $mapper->getData();
foreach($ccpSystemsData as $ccpSystemData){
/**
* @var Model\SystemModel $system
*/
$system = Model\BasicModel::getNew('SystemModel');
$system->setData($ccpSystemData);
$systemModels[] = $system;
@@ -142,10 +147,10 @@ class System extends \Controller\AccessController {
/**
* search systems by name
* @param $f3
* @param $params
* @param \Base $f3
* @param array $params
*/
public function search($f3, $params){
public function search(\Base $f3, $params){
$ccpDB = $this->getDB('CCP');
@@ -155,6 +160,15 @@ class System extends \Controller\AccessController {
$searchToken = $params['arg1'];
}
// some "edge cases for testing if rounding works correct
//$searchToken = 'H472-N'; // -0.000001 -> 0.0
//$searchToken = 'X1E-OQ'; // -0.099426 -> -0.10
//$searchToken = 'BKK4-H'; // -0.049954 -> -0.05
//$searchToken = 'Uhtafal'; // 0.499612 -> 0.5 (HS)
//$searchToken = 'Oshaima'; // 0.453128 -> 0.5 (HS)
//$searchToken = 'Ayeroilen'; // 0.446568 -> 0.4 (LS)
//$searchToken = 'Enderailen'; // 0.448785 -> 0.4 (LS)
$this->whereQuery = "WHERE
map_sys.solarSystemName LIKE '%" . $searchToken . "%'";
@@ -172,10 +186,9 @@ class System extends \Controller\AccessController {
/**
* save a new system to a a map
* @param $f3
* @param \Base $f3
*/
public function save($f3){
public function save(\Base $f3){
$newSystemData = [];
$postData = (array)$f3->get('POST');
@@ -187,92 +200,70 @@ class System extends \Controller\AccessController {
isset($postData['systemData']) &&
isset($postData['mapData'])
){
$user = $this->_getUser();
$activeCharacter = $this->getCharacter();
if($user){
if($activeCharacter){
$systemData = (array)$postData['systemData'];
$mapData = (array)$postData['mapData'];
$activeCharacter = $user->getActiveUserCharacter();
if( isset($systemData['id']) ){
// update existing system
// update existing system (e.g. changed system description) -------------------
/**
* @var $system Model\SystemModel
*/
$system = Model\BasicModel::getNew('SystemModel');
$system->getById($systemData['id']);
if( !$system->dry() ){
if( $system->hasAccess($user) ){
if( $system->hasAccess($activeCharacter) ){
// system model found
$systemModel = $system;
}
}
}elseif( isset($mapData['id']) ){
// save NEW system
// save NEW system ------------------------------------------------------------
/**
* @var $map Model\MapModel
*/
$map = Model\BasicModel::getNew('MapModel');
$map->getById($mapData['id']);
if( !$map->dry() ){
if( $map->hasAccess($user) ){
$systemData['mapId'] = $map;
// get static system data (CCP DB)
if(
!$map->dry() &&
$map->hasAccess($activeCharacter)
){
// make sure system is not already on map
// --> (e.g. multiple simultaneously save() calls for the same system)
if( is_null( $systemModel = $map->getSystemByCCPId($systemData['systemId']) ) ){
// system not found on map -> get static system data (CCP DB)
$systemModel = array_values( $this->_getSystemModelByIds([$systemData['systemId']]) )[0];
$systemModel->createdCharacterId = $activeCharacter->characterId;
$systemModel->createdCharacterId = $activeCharacter;
}
// map is not changeable for a system! (security)
$systemData['mapId'] = $map;
}
}
}
}
if( !is_null($systemModel) ){
// set/update system
$systemModel->setData($systemData);
$systemModel->updatedCharacterId = $activeCharacter->characterId;
$systemModel->updatedCharacterId = $activeCharacter;
$systemModel->save();
$newSystemData = $systemModel->getData();
}
echo json_encode($newSystemData);
}
/**
* delete systems and all its connections
* @param $f3
*/
public function delete($f3){
$systemIds = $f3->get('POST.systemIds');
$user = $this->_getUser();
if($user){
$system = Model\BasicModel::getNew('SystemModel');
foreach((array)$systemIds as $systemId){
$system->getById($systemId);
$system->delete($user);
$system->reset();
}
}
echo json_encode([]);
}
/**
* get system log data from CCP API import
* system Kills, Jumps,....
* @param $f3
* @param \Base $f3
*/
public function graphData($f3){
public function graphData(\Base $f3){
$graphData = [];
$systemIds = $f3->get('POST.systemIds');
@@ -288,14 +279,13 @@ class System extends \Controller\AccessController {
];
foreach($systemIds as $systemId){
foreach($logTables as $label => $ModelClass){
$systemLogModel = Model\BasicModel::getNew($ModelClass);
// 10min cache (could be up to 1h cache time)
$systemLogModel->getByForeignKey('systemId', $systemId, array(), 60 * 10);
$systemLogModel->getByForeignKey('systemId', $systemId, [], 60 * 10);
if(!$systemLogModel->dry()){
if( !$systemLogModel->dry() ){
$counter = 0;
for( $i = $logEntryCount; $i >= 1; $i--){
$column = 'value' . $i;
@@ -313,7 +303,6 @@ class System extends \Controller\AccessController {
$counter++;
}
}
}
}
@@ -322,25 +311,21 @@ class System extends \Controller\AccessController {
/**
* get system data for all systems within a constellation
* @param $f3
* @param $params
* @param \Base $f3
* @param array $params
*/
public function constellationData($f3, $params){
public function constellationData(\Base $f3, $params){
$return = (object) [];
$return->error = [];
$return->systemData = [];
$constellationId = 0;
if( $activeCharacter = $this->getCharacter() ){
$constellationId = 0;
$user = $this->_getUser();
if($user){
// check for search parameter
if( isset($params['arg1']) ){
$constellationId = (int)$params['arg1'];
}
$cacheKey = 'CACHE_CONSTELLATION_SYSTEMS_' . self::formatHiveKey($constellationId);
if($f3->exists($cacheKey)){
@@ -361,7 +346,66 @@ class System extends \Controller\AccessController {
echo json_encode($return);
}
/**
* set destination for specific systemIds
* @param \Base $f3
*/
public function setDestination(\Base $f3){
$postData = (array)$f3->get('POST');
$return = (object) [];
$return->error = [];
$return->systemData = [];
if(
($activeCharacter = $this->getCharacter()) &&
!empty($postData['systemData'])
){
$return->clearOtherWaypoints = (bool)$postData['clearOtherWaypoints'];
$return->first = (bool)$postData['first'];
/**
* @var Sso $ssoController
*/
$ssoController = self::getController('Sso');
foreach($postData['systemData'] as $systemData){
$waypointData = $ssoController->setWaypoint($activeCharacter, $systemData['systemId'], [
'clearOtherWaypoints' => $return->clearOtherWaypoints,
'first' => $return->first,
]);
if($waypointData['systemId']){
$return->systemData[] = $systemData;
}elseif( isset($waypointData['error']) ){
$return->error[] = $waypointData['error'];
}
}
}
echo json_encode($return);
}
/**
* delete systems and all its connections
* @param \Base $f3
*/
public function delete(\Base $f3){
$systemIds = (array)$f3->get('POST.systemIds');
if($activeCharacter = $this->getCharacter()){
/**
* @var Model\SystemModel $system
*/
$system = Model\BasicModel::getNew('SystemModel');
foreach((array)$systemIds as $systemId){
$system->getById($systemId);
$system->delete($activeCharacter);
$system->reset();
}
}
echo json_encode([]);
}
}

View File

@@ -15,87 +15,109 @@ use DB;
class User extends Controller\Controller{
// captcha specific session keys
const SESSION_CAPTCHA_ACCOUNT_UPDATE = 'SESSION.CAPTCHA.ACCOUNT.UPDATE';
const SESSION_CAPTCHA_ACCOUNT_DELETE = 'SESSION.CAPTCHA.ACCOUNT.DELETE';
// user specific session keys
const SESSION_KEY_USER = 'SESSION.USER';
const SESSION_KEY_USER_ID = 'SESSION.USER.ID';
const SESSION_KEY_USER_NAME = 'SESSION.USER.NAME';
// character specific session keys
const SESSION_KEY_CHARACTER = 'SESSION.CHARACTER';
const SESSION_KEY_CHARACTER_ID = 'SESSION.CHARACTER.ID';
const SESSION_KEY_CHARACTER_NAME = 'SESSION.CHARACTER.NAME';
const SESSION_KEY_CHARACTER_TIME = 'SESSION.CHARACTER.TIME';
const SESSION_KEY_CHARACTER_ACCESS_TOKEN = 'SESSION.CHARACTER.ACCESS_TOKEN';
const SESSION_KEY_CHARACTER_REFRESH_TOKEN = 'SESSION.CHARACTER.REFRESH_TOKEN';
// log text
const LOG_LOGGED_IN = 'userId: %s, userName: %s, charId: %s, charName: %s';
/**
* valid reasons for captcha images
* @var array
* @var string array
*/
private static $captchaReason = ['createAccount', 'deleteAccount'];
private static $captchaReason = [self::SESSION_CAPTCHA_ACCOUNT_UPDATE, self::SESSION_CAPTCHA_ACCOUNT_DELETE];
/**
* login function
* @param $f3
* login a valid character
* @param Model\CharacterModel $characterModel
* @return bool
*/
public function logIn($f3){
$data = $data = $f3->get('POST');
protected function loginByCharacter(Model\CharacterModel &$characterModel){
$login = false;
$return = (object) [];
if($user = $characterModel->getUser()){
// set user/character data to session -------------------
$this->f3->set(self::SESSION_KEY_USER, [
'ID' => $user->_id,
'NAME' => $user->name
]);
$user = null;
$dateTime = new \DateTime();
$this->f3->set(self::SESSION_KEY_CHARACTER, [
'ID' => $characterModel->_id,
'NAME' => $characterModel->name,
'TIME' => $dateTime->getTimestamp()
]);
if($data['loginData']){
$loginData = $data['loginData'];
$user = $this->logUserIn( $loginData['userName'], $loginData['userPassword'] );
// save user login information ---------------------------
$characterModel->touch('lastLogin');
$characterModel->save();
// write login log --------------------------------------
self::getLogger( $this->f3->get('PATHFINDER.LOGFILES.LOGIN') )->write(
sprintf(self::LOG_LOGGED_IN,
$user->_id,
$user->name,
$characterModel->_id,
$characterModel->name
)
);
$login = true;
}
// set "vague" error
if(is_null($user)){
$return->error = [];
$loginError = (object) [];
$loginError->type = 'login';
$return->error[] = $loginError;
}else{
// update/check api data
$user->updateApiData();
return $login;
}
// route user to map app
$return->reroute = rtrim(self::getEnvironmentData('URL'), '/') . $f3->alias('map');
/**
* validate cookie character information
* -> return character data (if valid)
* @param \Base $f3
*/
public function getCookieCharacter($f3){
$data = $f3->get('POST');
$return = (object) [];
$return->error = [];
if( !empty($data['cookie']) ){
if( !empty($cookieData = $this->getCookieByName($data['cookie']) )){
// cookie data is valid -> validate data against DB (security check!)
if( !empty($characters = $this->getCookieCharacters(array_slice($cookieData, 0, 1, true))) ){
// character is valid and allowed to login
$return->character = reset($characters)->getData();
}else{
$characterError = (object) [];
$characterError->type = 'warning';
$characterError->message = 'This can happen through "invalid cookie data", "login restrictions", "CREST problems".';
$return->error[] = $characterError;
}
}
}
echo json_encode($return);
}
/**
* core function for user login
* @param $userName
* @param $password
* @return Model\UserModel|null
*/
private function logUserIn($userName, $password){
// try to verify user
$user = $this->_verifyUser($userName, $password);
if( !is_null($user)){
// user is verified -> ready for login
// set Session login
$dateTime = new \DateTime();
$this->f3->set('SESSION.user', [
'time' => $dateTime->getTimestamp(),
'name' => $user->name,
'id' => $user->id
]);
// save user login information
$user->touch('lastLogin');
$user->save();
// save log
$logText = "id: %s, name: %s, ip: %s";
self::getLogger( $this->f3->get('PATHFINDER.LOGFILES.LOGIN') )->write(
sprintf($logText, $user->id, $user->name, $this->f3->get('IP'))
);
}
return $user;
}
/**
* get captcha image and store key to session
* @param $f3
* @param \Base $f3
*/
public function getCaptcha($f3){
public function getCaptcha(\Base $f3){
$data = $f3->get('POST');
$return = (object) [];
@@ -117,7 +139,7 @@ class User extends Controller\Controller{
'fonts/oxygen-bold-webfont.ttf',
14,
6,
'SESSION.' . $reason,
$reason,
'',
$colorText,
$colorBG
@@ -136,337 +158,132 @@ class User extends Controller\Controller{
/**
* delete the character log entry for the current active (main) character
* @param $f3
* @param \Base $f3
*/
public function deleteLog($f3){
$user = $this->_getUser();
if($user){
$activeUserCharacter = $user->getActiveUserCharacter();
if($activeUserCharacter){
$character = $activeUserCharacter->getCharacter();
if($characterLog = $character->getLog()){
$characterLog->erase();
}
public function deleteLog(\Base $f3){
$activeCharacter = $this->getCharacter();
if($activeCharacter){
if($characterLog = $activeCharacter->getLog()){
$characterLog->erase();
}
}
}
/**
* log the current user out + clear character system log data
* @param $f3
* @param \Base $f3
*/
public function logOut($f3){
public function logout(\Base $f3){
$this->deleteLog($f3);
parent::logOut($f3);
parent::logout($f3);
}
/**
* save/update "map sharing" configurations for all map types
* the user has access to
* @param $f3
* update user account data
* -> a fresh user automatically generated on first login with a new character
* -> see CREST SSO login
* @param \Base $f3
*/
public function saveSharingConfig($f3){
$data = $f3->get('POST');
$return = (object) [];
$privateSharing = 0;
$corporationSharing = 0;
$allianceSharing = 0;
$user = $this->_getUser();
if($user){
// form values
if(isset($data['formData'])){
$formData = $data['formData'];
if(isset($formData['privateSharing'])){
$privateSharing = 1;
}
if(isset($formData['corporationSharing'])){
$corporationSharing = 1;
}
if(isset($formData['allianceSharing'])){
$allianceSharing = 1;
}
}
$user->shared = $privateSharing;
$user->save();
// update corp/ally ---------------------------------------------------------------
$activeUserCharacter = $user->getActiveUserCharacter();
if(is_object($activeUserCharacter)){
$corporation = $activeUserCharacter->getCharacter()->getCorporation();
$alliance = $activeUserCharacter->getCharacter()->getAlliance();
if(is_object($corporation)){
$corporation->shared = $corporationSharing;
$corporation->save();
}
if(is_object($alliance)){
$alliance->shared = $allianceSharing;
$alliance->save();
}
}
$return->userData = $user->getData();
}
echo json_encode($return);
}
/**
* search for a registration key model
* e.g. for new user registration with "invite" feature enabled
* @param $email
* @param $registrationKey
* @return bool|Model\RegistrationKeyModel
* @throws Exception
*/
protected function getRegistrationKey($email, $registrationKey){
$registrationKeyModel = Model\BasicModel::getNew('RegistrationKeyModel');
$registrationKeyModel->load([
'registrationKey = :registrationKey AND
email = :email AND
used = 0 AND
active = 1',
':registrationKey' => $registrationKey,
':email' => $email
]);
if( $registrationKeyModel->dry() ){
return false;
}else{
return $registrationKeyModel;
}
}
/**
* check if there is already an active Key for a mail
* @param $email
* @param bool|false $used
* @return bool|null
* @throws Exception
*/
protected function findRegistrationKey($email, $used = false){
$queryPart = 'email = :email AND active = 1';
if(is_int($used)){
$queryPart .= ' AND used = ' . $used;
}
$registrationKeyModel = Model\BasicModel::getNew('RegistrationKeyModel');
$registrationKeyModels = $registrationKeyModel->find([
$queryPart,
':email' => $email
]);
if( is_object($registrationKeyModels) ){
return $registrationKeyModels;
}else{
return false;
}
}
/**
* save/update user account data
* @param $f3
*/
public function saveAccount($f3){
public function saveAccount(\Base $f3){
$data = $f3->get('POST');
$return = (object) [];
$return->error = [];
$captcha = $f3->get('SESSION.createAccount');
$captcha = $f3->get(self::SESSION_CAPTCHA_ACCOUNT_UPDATE);
// reset captcha -> forces user to enter new one
$f3->clear('SESSION.createAccount');
$f3->clear(self::SESSION_CAPTCHA_ACCOUNT_UPDATE);
$newUserData = null;
// check for new user
$loginAfterSave = false;
// valid registration key Model is required for new registration
// if "invite" feature is enabled
$registrationKeyModel = false;
if( isset($data['settingsData']) ){
$settingsData = $data['settingsData'];
if( isset($data['formData']) ){
$formData = $data['formData'];
try{
$user = $this->_getUser(0);
if($activeCharacter = $this->getCharacter(0)){
$user = $activeCharacter->getUser();
// captcha is send -> check captcha
if(
isset($settingsData['captcha']) &&
!empty($settingsData['captcha'])
){
if($settingsData['captcha'] === $captcha){
// change/set sensitive user data requires captcha!
if($user === false){
// check if registration key invite function is enabled
if($f3->get('PATHFINDER.REGISTRATION.INVITE') === 1 ){
$registrationKeyModel = $this->getRegistrationKey( $settingsData['email'], $settingsData['registrationKey'] );
if($registrationKeyModel === false){
throw new Exception\RegistrationException('Registration key invalid', 'registrationKey');
}
}
// new user registration
$user = $mapType = Model\BasicModel::getNew('UserModel');
$loginAfterSave = true;
// captcha is send -> check captcha ---------------------------------
if(
isset($formData['captcha']) &&
!empty($formData['captcha'])
){
if($formData['captcha'] === $captcha){
// change/set sensitive user data requires captcha!
// set username
if(
isset($settingsData['name']) &&
!empty($settingsData['name'])
isset($formData['name']) &&
!empty($formData['name'])
){
$user->name = $settingsData['name'];
}
}
// change/set email
if(
isset($settingsData['email']) &&
isset($settingsData['email_confirm']) &&
!empty($settingsData['email']) &&
!empty($settingsData['email_confirm']) &&
$settingsData['email'] == $settingsData['email_confirm']
){
$user->email = $settingsData['email'];
}
// change/set password
if(
isset($settingsData['password']) &&
isset($settingsData['password_confirm']) &&
!empty($settingsData['password']) &&
!empty($settingsData['password_confirm']) &&
$settingsData['password'] == $settingsData['password_confirm']
){
$user->password = $settingsData['password'];
}
}else{
// captcha was send but not valid -> return error
$captchaError = (object) [];
$captchaError->type = 'error';
$captchaError->message = 'Captcha does not match';
$return->error[] = $captchaError;
}
}
// saving additional user info requires valid user object (no captcha required)
if($user){
// save API data
if(
isset($settingsData['keyId']) &&
isset($settingsData['vCode']) &&
is_array($settingsData['keyId']) &&
is_array($settingsData['vCode'])
){
// get all existing API models for this user
$apiModels = $user->getAPIs();
foreach($settingsData['keyId'] as $i => $keyId){
$api = null;
// search for existing API model
foreach($apiModels as $key => $apiModel){
if($apiModel->keyId == $keyId){
$api = $apiModel;
// make sure model is up2data -> cast()
$api->cast();
unset($apiModels[$key]);
break;
}
$user->name = $formData['name'];
}
if(is_null($api)){
// new API Key
$api = Model\BasicModel::getNew('UserApiModel');
$api->userId = $user;
// set email
if(
isset($formData['email']) &&
isset($formData['email_confirm']) &&
!empty($formData['email']) &&
!empty($formData['email_confirm']) &&
$formData['email'] == $formData['email_confirm']
){
$user->email = $formData['email'];
}
$api->keyId = $keyId;
$api->vCode = $settingsData['vCode'][$i];
$api->save();
// save/update user model
// this will fail if model validation fails!
$user->save();
$characterCount = $api->updateCharacters();
}else{
// captcha was send but not valid -> return error
$captchaError = (object) [];
$captchaError->type = 'error';
$captchaError->message = 'Captcha does not match';
$return->error[] = $captchaError;
}
}
if($characterCount == 0){
// no characters found -> return warning
$characterError = (object) [];
$characterError->type = 'warning';
$characterError->message = 'API verification failed. No Characters found for KeyId ' . $api->keyId;
$return->error[] = $characterError;
}
// sharing config ---------------------------------------------------
if(isset($formData['share'])){
$privateSharing = 0;
$corporationSharing = 0;
$allianceSharing = 0;
if(isset($formData['privateSharing'])){
$privateSharing = 1;
}
// delete API models that no longer exists
foreach($apiModels as $apiModel){
$apiModel->delete();
if(isset($formData['corporationSharing'])){
$corporationSharing = 1;
}
// get fresh updated user object (API info may have has changed)
$user = $this->_getUser(0);
}
if(isset($formData['allianceSharing'])){
$allianceSharing = 1;
}
// set main character
if( isset($settingsData['mainCharacterId']) ){
$user->setMainCharacterId((int)$settingsData['mainCharacterId']);
}
// update private/corp/ally
$corporation = $activeCharacter->getCorporation();
$alliance = $activeCharacter->getAlliance();
// check if the user already has a main character
// if not -> save the next best character as main
$mainUserCharacter = $user->getMainUserCharacter();
if(is_object($corporation)){
$corporation->shared = $corporationSharing;
$corporation->save();
}
// set main character if no main character exists
if(is_null($mainUserCharacter)){
$user->setMainCharacterId();
}
if(is_object($alliance)){
$alliance->shared = $allianceSharing;
$alliance->save();
}
// save/update user model
// this will fail if model validation fails!
$user->save();
if(is_object($registrationKeyModel)){
$registrationKeyModel->used = 1;
$registrationKeyModel->save();
}
// log user in (in case he is new
if($loginAfterSave){
$this->logUserIn( $user->name, $settingsData['password'] );
// return reroute path
$return->reroute = rtrim(self::getEnvironmentData('URL'), '/') . $this->f3->alias('map');
$activeCharacter->shared = $privateSharing;
$activeCharacter->save();
}
// get fresh updated user object
$user = $this->_getUser(0);
$newUserData = $user->getData();
}
}catch(Exception\ValidationException $e){
$validationError = (object) [];
$validationError->type = 'error';
@@ -483,109 +300,6 @@ class User extends Controller\Controller{
// return new/updated user data
$return->userData = $newUserData;
}
echo json_encode($return);
}
/**
* send mail with registration key
* -> check INVITE in pathfinder.ini
* @param $f3
* @throws Exception
*/
public function sendInvite($f3){
$data = $f3->get('POST.settingsData');
$return = (object) [];
// check invite limit
// get handed out key count
$tempRegistrationKeyModel = Model\BasicModel::getNew('RegistrationKeyModel');
$tempRegistrationKeyModels = $tempRegistrationKeyModel->find([ '
email != "" AND
active = 1'
]);
$totalKeys = 0;
if(is_object($tempRegistrationKeyModels)){
$totalKeys = $tempRegistrationKeyModels->count();
}
if(
$f3->get('PATHFINDER.REGISTRATION.INVITE') == 1 &&
$totalKeys < $f3->get('PATHFINDER.REGISTRATION.INVITE_LIMIT')
){
// key limit not reached
if(
isset($data['email']) &&
!empty($data['email'])
){
$email = trim($data['email']);
// check if mail is valid
if( \Audit::instance()->email($email) ){
// new key for this mail is allowed
$registrationKeyModel = $this->findRegistrationKey($email, 0);
if($registrationKeyModel === false){
// check for total number of invites (active and inactive) -> prevent spamming
$allRegistrationKeysByMail = $this->findRegistrationKey($email);
if(
$allRegistrationKeysByMail == false ||
$allRegistrationKeysByMail->count() < 3
){
// get a fresh key
$registrationKeyModel = Model\BasicModel::getNew('RegistrationKeyModel');
$registrationKeyModel->load(['
used = 0 AND
active = 1 AND
email = "" ',
':email' => $email
], ['limit' => 1]);
}else{
$validationError = (object) [];
$validationError->type = 'warning';
$validationError->message = 'The number of keys is limited by Email. You can not get more keys';
$return->error[] = $validationError;
}
}else{
$registrationKeyModel = $registrationKeyModel[0];
}
// send "old" key again or send a new key
if( is_object($registrationKeyModel) ){
$msg = 'Your personal Registration Key: ' . $registrationKeyModel->registrationKey;
$mailController = new MailController();
$status = $mailController->sendInviteKey($email, $msg);
if( $status ){
$registrationKeyModel->email = $email;
$registrationKeyModel->ip = $this->f3->get('IP');
$registrationKeyModel->save();
}
}
}else{
$validationError = (object) [];
$validationError->type = 'error';
$validationError->field = 'email';
$validationError->message = 'Email is not valid';
$return->error[] = $validationError;
}
}
}else{
$validationError = (object) [];
$validationError->type = 'warning';
$validationError->message = 'The pool of beta keys has been exhausted, please try again in a few days/weeks';
$return->error[] = $validationError;
}
echo json_encode($return);
@@ -593,31 +307,26 @@ class User extends Controller\Controller{
/**
* delete current user account from DB
* @param $f3
* @param \Base $f3
*/
public function deleteAccount($f3){
public function deleteAccount(\Base $f3){
$data = $f3->get('POST.formData');
$return = (object) [];
$captcha = $f3->get('SESSION.deleteAccount');
$captcha = $f3->get(self::SESSION_CAPTCHA_ACCOUNT_DELETE);
// reset captcha -> forces user to enter new one
$f3->clear('SESSION.deleteAccount');
$f3->clear(self::SESSION_CAPTCHA_ACCOUNT_DELETE);
if(
isset($data['captcha']) &&
!empty($data['captcha']) &&
$data['captcha'] === $captcha
){
$user = $this->_getUser(0);
$activeCharacter = $this->getCharacter(0);
$user = $activeCharacter->getUser();
$validUser = $this->_verifyUser( $user->name, $data['password']);
if(
is_object($validUser) &&
is_object($user) &&
$user->id === $validUser->id
){
if($user){
// send delete account mail
$msg = 'Hello ' . $user->name . ',<br><br>';
$msg .= 'your account data has been successfully deleted.';
@@ -635,15 +344,9 @@ class User extends Controller\Controller{
// remove user
$user->erase();
$this->logOut($f3);
$this->logout($f3);
die();
}
}else{
// password does not match current user pw
$passwordError = (object) [];
$passwordError->type = 'error';
$passwordError->message = 'Invalid password';
$return->error[] = $passwordError;
}
}else{
// captcha not valid -> return error

View File

@@ -7,15 +7,29 @@
*/
namespace Controller;
use Controller\Ccp as Ccp;
use Model;
class AppController extends Controller {
/**
* show main login (index) page
* @param $f3
* event handler after routing
* @param \Base $f3
*/
public function init($f3) {
public function afterroute(\Base $f3){
parent::afterroute($f3);
// clear all SSO related temp data
if( $f3->exists(Ccp\Sso::SESSION_KEY_SSO) ){
$f3->clear(Ccp\Sso::SESSION_KEY_SSO);
}
}
/**
* show main login (index) page
* @param \Base $f3
*/
public function init(\Base $f3) {
// page title
$f3->set('pageTitle', 'Login');
@@ -30,6 +44,12 @@ class AppController extends Controller {
// JS main file
$f3->set('jsView', 'login');
// characters from cookies
$f3->set('cookieCharacters', $this->getCookieByName(self::COOKIE_PREFIX_CHARACTER, true));
$f3->set('getCharacterGrid', function($characters){
return ( ((12 / count($characters)) <= 3) ? 3 : (12 / count($characters)) );
});
}
}

View File

@@ -0,0 +1,894 @@
<?php
/**
* Created by PhpStorm.
* User: Exodus
* Date: 23.01.2016
* Time: 17:18
*
* Handles access to EVE-Online "CREST API" and "SSO" auth functions
* - Add your API credentials in "environment.ini"
* - Check "PATHFINDER.API" in "pathfinder.ini" for correct API URLs
* Hint: \Web::instance()->request automatically caches responses by their response "Cache-Control" header!
*/
namespace Controller\Ccp;
use Controller;
use Controller\Api as Api;
use Data\Mapper as Mapper;
use Model;
use Lib;
class Sso extends Api\User{
/**
* @var int timeout (seconds) for API calls
*/
const CREST_TIMEOUT = 4;
/**
* @var int expire time (seconds) for an valid "accessToken"
*/
const ACCESS_KEY_EXPIRE_TIME = 20 * 60;
// SSO specific session keys
const SESSION_KEY_SSO = 'SESSION.SSO';
const SESSION_KEY_SSO_ERROR = 'SESSION.SSO.ERROR';
const SESSION_KEY_SSO_STATE = 'SESSION.SSO.STATE';
const SESSION_KEY_SSO_FROM_MAP = 'SESSION.SSO.FROM_MAP';
// cache keys
const CACHE_KEY_LOCATION_DATA = 'CACHED.LOCATION.%s';
// error messages
const ERROR_CCP_SSO_URL = 'Invalid "ENVIRONMENT.[ENVIRONMENT].SSO_CCP_URL" url. %s';
const ERROR_CCP_CREST_URL = 'Invalid "ENVIRONMENT.[ENVIRONMENT].CCP_CREST_URL" url. %s';
const ERROR_CCP_CLIENT_ID = 'Missing "ENVIRONMENT.[ENVIRONMENT].SSO_CCP_CLIENT_ID".';
const ERROR_RESOURCE_DEPRECATED = 'Resource: %s has been marked as deprecated. %s';
const ERROR_ACCESS_TOKEN = 'Unable to get a valid "access_token. %s';
const ERROR_VERIFY_CHARACTER = 'Unable to verify character data. %s';
const ERROR_GET_ENDPOINT = 'Unable to get endpoint data. $s';
const ERROR_FIND_ENDPOINT = 'Unable to find endpoint: %s';
const ERROR_LOGIN_FAILED = 'Failed authentication due to technical problems: %s';
const ERROR_CHARACTER_VERIFICATION = 'Character verification failed from CREST';
const ERROR_CHARACTER_FORBIDDEN = 'Character "%s" is not authorized to log in';
const ERROR_SERVICE_TIMEOUT = 'CCP SSO service timeout (%ss). Try again later';
const ERROR_COOKIE_LOGIN = 'Login from Cookie failed. Please retry by CCP SSO';
/**
* CREST "Scopes" are used by pathfinder
* -> Enable scopes: https://developers.eveonline.com
* @var array
*/
private $requestScopes = [
// 'characterFittingsRead',
// 'characterFittingsWrite',
'characterLocationRead',
'characterNavigationWrite'
];
/**
* redirect user to CCP SSO page and request authorization
* -> cf. Controller->getCookieCharacters() ( equivalent cookie based login)
* @param \Base $f3
*/
public function requestAuthorization($f3){
if( !empty($ssoCcpClientId = Controller\Controller::getEnvironmentData('SSO_CCP_CLIENT_ID')) ){
$params = $f3->get('GET');
if(
isset($params['characterId']) &&
( $activeCharacter = $this->getCharacter(0) )
){
// authentication restricted to a characterId -----------------------------------------------
// restrict login to this characterId e.g. for character switch on map page
$characterId = (int)trim($params['characterId']);
/**
* @var Model\CharacterModel $character
*/
$character = Model\BasicModel::getNew('CharacterModel');
$character->getById($characterId, 0);
// check if character is valid and exists
if(
!$character->dry() &&
$character->hasUserCharacter() &&
($activeCharacter->getUser()->_id === $character->getUser()->_id)
){
// requested character belongs to current user
// -> update character vom CREST (e.g. corp changed,..)
$updateStatus = $character->updateFromCrest();
if( empty($updateStatus) ){
// make sure character data is up2date!
// -> this is not the case if e.g. userCharacters was removed "ownerHash" changed...
$character->getById($character->_id);
if(
$character->hasUserCharacter() &&
$character->isAuthorized()
){
$loginCheck = $this->loginByCharacter($character);
if($loginCheck){
// set "login" cookie
$this->setLoginCookie($character);
// route to "map"
$f3->reroute('@map');
}
}
}
}
// redirect to map map page on successful login
$f3->set(self::SESSION_KEY_SSO_FROM_MAP, true);
}
// redirect to CCP SSO ----------------------------------------------------------------------
// used for "state" check between request and callback
$state = bin2hex(mcrypt_create_iv(12, MCRYPT_DEV_URANDOM));
$f3->set(self::SESSION_KEY_SSO_STATE, $state);
$urlParams = [
'response_type' => 'code',
'redirect_uri' => Controller\Controller::getEnvironmentData('URL') . $f3->build('/sso/callbackAuthorization'),
'client_id' => Controller\Controller::getEnvironmentData('SSO_CCP_CLIENT_ID'),
'scope' => implode(' ', $this->requestScopes),
'state' => $state
];
$ssoAuthUrl = self::getAuthorizationEndpoint() . '?' . http_build_query($urlParams, '', '&', PHP_QUERY_RFC3986 );
$f3->status(302);
$f3->reroute($ssoAuthUrl);
}else{
// SSO clientId missing
$f3->set(self::SESSION_KEY_SSO_ERROR, self::ERROR_CCP_CLIENT_ID);
self::getCrestLogger()->write(self::ERROR_CCP_CLIENT_ID);
$f3->reroute('@login');
}
}
/**
* callback handler for CCP SSO user Auth
* -> see requestAuthorization()
* @param \Base $f3
*/
public function callbackAuthorization($f3){
$getParams = (array)$f3->get('GET');
// users can log in either from @login (new user) or @map (existing user) root alias
// -> in case login fails, users should be redirected differently
$authFromMapAlias = false;
if($f3->exists(self::SESSION_KEY_SSO_STATE)){
// check response and validate 'state'
if(
isset($getParams['code']) &&
isset($getParams['state']) &&
!empty($getParams['code']) &&
!empty($getParams['state']) &&
$f3->get(self::SESSION_KEY_SSO_STATE) === $getParams['state']
){
// check if user came from map (for redirect)
if( $f3->get(self::SESSION_KEY_SSO_FROM_MAP) ){
$authFromMapAlias = true;
}
// clear 'state' for new next login request
$f3->clear(self::SESSION_KEY_SSO_STATE);
$f3->clear(self::SESSION_KEY_SSO_FROM_MAP);
$accessData = $this->getCrestAccessData($getParams['code']);
if(
isset($accessData->accessToken) &&
isset($accessData->refreshToken)
){
// login succeeded -> get basic character data for current login
$verificationCharacterData = $this->verifyCharacterData($accessData->accessToken);
if( !is_null($verificationCharacterData)){
// check if login is restricted to a characterID
// verification available data. Data is needed for "ownerHash" check
// get character data from CREST
$characterData = $this->getCharacterData($accessData->accessToken);
if( isset($characterData->character) ){
// add "ownerHash" and CREST tokens
$characterData->character['ownerHash'] = $verificationCharacterData->CharacterOwnerHash;
$characterData->character['crestAccessToken'] = $accessData->accessToken;
$characterData->character['crestRefreshToken'] = $accessData->refreshToken;
// add/update static character data
$characterModel = $this->updateCharacter($characterData);
if( !is_null($characterModel) ){
// check if character is authorized to log in
if($characterModel->isAuthorized()){
// character is authorized to log in
// -> update character log (current location,...)
$characterModel = $characterModel->updateLog();
// check if there is already an active user logged in
if($activeCharacter = $this->getCharacter()){
// connect character with current user
$user = $activeCharacter->getUser();
}elseif( is_null( $user = $characterModel->getUser()) ){
// no user found (new character) -> create new user and connect to character
$user = Model\BasicModel::getNew('UserModel');
$user->name = $characterModel->name;
$user->save();
}
/**
* @var $userCharactersModel Model\UserCharacterModel
*/
if( is_null($userCharactersModel = $characterModel->userCharacter) ){
$userCharactersModel = Model\BasicModel::getNew('UserCharacterModel');
$userCharactersModel->characterId = $characterModel;
}
// user might have changed
$userCharactersModel->userId = $user;
$userCharactersModel->save();
// get updated character model
$characterModel = $userCharactersModel->getCharacter();
// login by character
$loginCheck = $this->loginByCharacter($characterModel);
if($loginCheck){
// set "login" cookie
$this->setLoginCookie($characterModel);
// route to "map"
$f3->reroute('@map');
}else{
$f3->set(self::SESSION_KEY_SSO_ERROR, sprintf(self::ERROR_LOGIN_FAILED, $characterModel->name));
}
}else{
// character is not authorized to log in
$f3->set(self::SESSION_KEY_SSO_ERROR, sprintf(self::ERROR_CHARACTER_FORBIDDEN, $characterModel->name));
}
}
}
}else{
// failed to verify character by CREST
$f3->set(self::SESSION_KEY_SSO_ERROR, self::ERROR_CHARACTER_VERIFICATION);
}
}else{
// CREST "accessData" missing (e.g. timeout)
$f3->set(self::SESSION_KEY_SSO_ERROR, sprintf(self::ERROR_SERVICE_TIMEOUT, self::CREST_TIMEOUT));
}
}else{
// invalid CREST response
$f3->set(self::SESSION_KEY_SSO_ERROR, sprintf(self::ERROR_LOGIN_FAILED, 'Invalid response'));
}
}
if($authFromMapAlias){
// on error -> route back to map
$f3->reroute('@map');
}else{
// on error -> route back to login form
$f3->reroute('@login');
}
}
/**
* login by cookie name
* @param \Base $f3
*/
public function login(\Base $f3){
$data = (array)$f3->get('GET');
$cookieName = empty($data['cookie']) ? '' : $data['cookie'];
$character = null;
if( !empty($cookieName) ){
if( !empty($cookieData = $this->getCookieByName($cookieName) )){
// cookie data is valid -> validate data against DB (security check!)
if( !empty($characters = $this->getCookieCharacters(array_slice($cookieData, 0, 1, true))) ){
// character is valid and allowed to login
$character = $characters[$cookieName];
}
}
}
if( is_object($character)){
// login by character
$loginCheck = $this->loginByCharacter($character);
if($loginCheck){
// route to "map"
$f3->reroute('@map');
}
}
// on error -> route back to login form
$f3->set(self::SESSION_KEY_SSO_ERROR, self::ERROR_COOKIE_LOGIN);
$f3->reroute('@login');
}
/**
* get a valid "access_token" for oAuth 2.0 verification
* -> if $authCode is set -> request NEW "access_token"
* -> else check for existing (not expired) "access_token"
* -> else try to refresh auth and get fresh "access_token"
* @param bool $authCode
* @return null|\stdClass
*/
public function getCrestAccessData($authCode){
$accessData = null;
if( !empty($authCode) ){
// Authentication Code is set -> request new "accessToken"
$accessData = $this->verifyAuthorizationCode($authCode);
}else{
// Unable to get Token -> trigger error
self::getCrestLogger()->write(sprintf(self::ERROR_ACCESS_TOKEN, $authCode));
}
return $accessData;
}
/**
* verify authorization code, and get an "access_token" data
* @param $authCode
* @return \stdClass
*/
protected function verifyAuthorizationCode($authCode){
$requestParams = [
'grant_type' => 'authorization_code',
'code' => $authCode
];
return $this->requestAccessData($requestParams);
}
/**
* get new "access_token" by an existing "refresh_token"
* -> if "access_token" is expired, this function gets a fresh one
* @param $refreshToken
* @return \stdClass
*/
public function refreshAccessToken($refreshToken){
$requestParams = [
'grant_type' => 'refresh_token',
'refresh_token' => $refreshToken
];
return $this->requestAccessData($requestParams);
}
/**
* request an "access_token" AND "refresh_token" data
* -> this can either be done by sending a valid "authorization code"
* OR by providing a valid "refresh_token"
* @param $requestParams
* @return \stdClass
*/
protected function requestAccessData($requestParams){
$verifyAuthCodeUrl = self::getVerifyAuthorizationCodeEndpoint();
$verifyAuthCodeUrlParts = parse_url($verifyAuthCodeUrl);
$accessData = (object) [];
$accessData->accessToken = null;
$accessData->refreshToken = null;
if($verifyAuthCodeUrlParts){
$contentType = 'application/x-www-form-urlencoded';
$requestOptions = [
'timeout' => self::CREST_TIMEOUT,
'method' => 'POST',
'user_agent' => $this->getUserAgent(),
'header' => [
'Authorization: Basic ' . $this->getAuthorizationHeader(),
'Content-Type: ' . $contentType,
'Host: ' . $verifyAuthCodeUrlParts['host']
]
];
// content (parameters to send with)
$requestOptions['content'] = http_build_query($requestParams);
$apiResponse = Lib\Web::instance()->request($verifyAuthCodeUrl, $requestOptions);
if($apiResponse['body']){
$authCodeRequestData = json_decode($apiResponse['body'], true);
if( !empty($authCodeRequestData) ){
if( isset($authCodeRequestData['access_token']) ){
// this token is required for endpoints that require Auth
$accessData->accessToken = $authCodeRequestData['access_token'];
}
if(isset($authCodeRequestData['refresh_token'])){
// this token is used to refresh/get a new access_token when expires
$accessData->refreshToken = $authCodeRequestData['refresh_token'];
}
}
}else{
self::getCrestLogger()->write(
sprintf(
self::ERROR_ACCESS_TOKEN,
print_r($requestParams, true)
)
);
}
}else{
self::getCrestLogger()->write(
sprintf(self::ERROR_CCP_SSO_URL, __METHOD__)
);
}
return $accessData;
}
/**
* verify character data by "access_token"
* -> get some basic information (like character id)
* -> if more character information is required, use CREST endpoints request instead
* @param $accessToken
* @return mixed|null
*/
public function verifyCharacterData($accessToken){
$verifyUserUrl = self::getVerifyUserEndpoint();
$verifyUrlParts = parse_url($verifyUserUrl);
$characterData = null;
if($verifyUrlParts){
$requestOptions = [
'timeout' => self::CREST_TIMEOUT,
'method' => 'GET',
'user_agent' => $this->getUserAgent(),
'header' => [
'Authorization: Bearer ' . $accessToken,
'Host: ' . $verifyUrlParts['host']
]
];
$apiResponse = Lib\Web::instance()->request($verifyUserUrl, $requestOptions);
if($apiResponse['body']){
$characterData = json_decode($apiResponse['body']);
}else{
self::getCrestLogger()->write(sprintf(self::ERROR_VERIFY_CHARACTER, __METHOD__));
}
}else{
self::getCrestLogger()->write(sprintf(self::ERROR_CCP_SSO_URL, __METHOD__));
}
return $characterData;
}
/**
* get all available Endpoints
* @param $accessToken
* @param array $additionalOptions
* @return mixed|null
*/
protected function getEndpoints($accessToken = '', $additionalOptions = []){
$crestUrl = self::getCrestEndpoint();
$additionalOptions['accept'] = 'application/vnd.ccp.eve.Api-v3+json';
$endpoint = $this->getEndpoint($crestUrl, $accessToken, $additionalOptions);
return $endpoint;
}
/**
* get a specific endpoint by its $resourceUrl
* @param string $resourceUrl endpoint API url
* @param string $accessToken CREST access token
* @param array $additionalOptions optional request options (pathfinder specific)
* @return mixed|null
*/
protected function getEndpoint($resourceUrl, $accessToken = '', $additionalOptions = []){
$resourceUrlParts = parse_url($resourceUrl);
$endpoint = null;
if($resourceUrlParts){
$requestOptions = [
'timeout' => self::CREST_TIMEOUT,
'method' => 'GET',
'user_agent' => $this->getUserAgent(),
'header' => [
'Host: login.eveonline.com',
'Host: ' . $resourceUrlParts['host']
]
];
// some endpoints don´t require an "access_token" (e.g. public crest data)
if( !empty($accessToken) ){
$requestOptions['header'][] = 'Authorization: Bearer ' . $accessToken;
}
// if specific contentType is required -> add it to request header
// CREST versioning can be done by calling different "Accept:" Headers
if( isset($additionalOptions['accept']) ){
$requestOptions['header'][] = 'Accept: ' . $additionalOptions['accept'];
}
$apiResponse = Lib\Web::instance()->request($resourceUrl, $requestOptions, $additionalOptions);
if(
$apiResponse['timeout'] === false &&
$apiResponse['headers']
){
// check headers for error
$this->checkResponseHeaders($apiResponse['headers'], $requestOptions);
if($apiResponse['body']){
$endpoint = json_decode($apiResponse['body'], true);
}else{
self::getCrestLogger()->write(sprintf(self::ERROR_GET_ENDPOINT, __METHOD__));
}
}
}else{
self::getCrestLogger()->write(sprintf(self::ERROR_CCP_CREST_URL, __METHOD__));
}
return $endpoint;
}
/**
* recursively walk down the CREST API tree by a given $path array
* -> return "leaf" endpoint
* @param $endpoint
* @param $accessToken
* @param array $path
* @param array $additionalOptions
* @return null
*/
protected function walkEndpoint($endpoint, $accessToken, $path = [], $additionalOptions = []){
$targetEndpoint = null;
if( !empty($path) ){
$newNode = array_shift($path);
if(isset($endpoint[$newNode])){
$currentEndpoint = $endpoint[$newNode];
if(isset($currentEndpoint['href'])){
$newEndpoint = $this->getEndpoint($currentEndpoint['href'], $accessToken, $additionalOptions);
$targetEndpoint = $this->walkEndpoint($newEndpoint, $accessToken, $path, $additionalOptions);
}else{
// leaf found
$targetEndpoint = $currentEndpoint;
}
}else{
// endpoint not found
self::getCrestLogger()->write(sprintf(self::ERROR_FIND_ENDPOINT, $newNode));
}
}else{
$targetEndpoint = $endpoint;
}
return $targetEndpoint;
}
/**
* get character data
* @param $accessToken
* @param array $additionalOptions
* @return object
*/
public function getCharacterData($accessToken, $additionalOptions = []){
$endpoints = $this->getEndpoints($accessToken, $additionalOptions);
$characterData = (object) [];
$endpoint = $this->walkEndpoint($endpoints, $accessToken, [
'decode',
'character'
], $additionalOptions);
if( !empty($endpoint) ){
$crestCharacterData = (new Mapper\CrestCharacter($endpoint))->getData();
$characterData->character = $crestCharacterData
;
if(isset($endpoint['corporation'])){
$characterData->corporation = (new Mapper\CrestCorporation($endpoint['corporation']))->getData();
}
// IMPORTANT: alliance data is not yet available over CREST!
// -> we need to request them over the XML api
/*
if(isset($endpoint['alliance'])){
$characterData->alliance = (new Mapper\CrestAlliance($endpoint['alliance']))->getData();
}
*/
$xmlCharacterData = (new Xml())->getPublicCharacterData( (int)$crestCharacterData['id'] );
if(isset($xmlCharacterData['alli'])){
$characterData->alliance = $xmlCharacterData['alli'];
}
}
return $characterData;
}
/**
* get current character location data (result is cached!)
* -> solarSystem data where character is currently active
* @param $accessToken
* @param int $ttl
* @param array $additionalOptions
* @return array|mixed
*/
public function getCharacterLocationData($accessToken, $ttl = 10, $additionalOptions = []){
// null == CREST call failed (e.g. timeout)
$locationData = [
'timeout' => false
];
// in addition to the cURL caching (based on cache-control headers,
// the final location data is cached additionally -> speed up
$cacheKey = sprintf(self::CACHE_KEY_LOCATION_DATA, 'TOKEN_' . hash('md5', $accessToken));
if( !$this->getF3()->exists($cacheKey) ){
$endpoints = $this->getEndpoints($accessToken, $additionalOptions);
$additionalOptions['accept'] = 'application/vnd.ccp.eve.CharacterLocation-v1+json';
$endpoint = $this->walkEndpoint($endpoints, $accessToken, [
'decode',
'character',
'location'
], $additionalOptions);
if( !is_null($endpoint) ){
// request succeeded (e.g. no timeout)
if(isset($endpoint['solarSystem'])){
$locationData['system'] = (new Mapper\CrestSystem($endpoint['solarSystem']))->getData();
}
if(isset($endpoint['station'])){
$locationData['station'] = (new Mapper\CrestStation($endpoint['station']))->getData();
}
$this->getF3()->set($cacheKey, $locationData, $ttl);
}else{
// timeout
$locationData['timeout'] = true;
}
}else{
$locationData = $this->getF3()->get($cacheKey);
}
return $locationData;
}
/**
* set new ingame waypoint
* @param Model\CharacterModel $character
* @param int $systemId
* @param array $options
* @return array
*/
public function setWaypoint( Model\CharacterModel $character, $systemId, $options = []){
$crestUrlParts = parse_url( self::getCrestEndpoint() );
$waypointData = [];
if( $crestUrlParts ){
$endpointUrl = self::getCrestEndpoint() . '/characters/' . $character->_id . '/navigation/waypoints/';
$systemEndpoint = self::getCrestEndpoint() . '/solarsystems/' . $systemId . '/';
// request body
$content = [
'clearOtherWaypoints' => (bool)$options['clearOtherWaypoints'],
'first' => (bool)$options['first'],
'solarSystem' => [
'href' => $systemEndpoint,
'id' => (int)$systemId
]
];
$requestOptions = [
'timeout' => self::CREST_TIMEOUT,
'method' => 'POST',
'user_agent' => $this->getUserAgent(),
'header' => [
'Scope: characterNavigationWrite',
'Authorization: Bearer ' . $character->getAccessToken(),
'Host: ' . $crestUrlParts['host'],
'Content-Type: application/vnd.ccp.eve.PostWaypoint-v1+json;charset=utf-8',
],
'content' => json_encode($content, JSON_UNESCAPED_SLASHES)
];
$apiResponse = Lib\Web::instance()->request($endpointUrl, $requestOptions);
if( isset($apiResponse['body']) ){
$responseData = json_decode($apiResponse['body']);
if( empty($responseData) ){
$waypointData['systemId'] = (int)$systemId;
}elseif(
isset($responseData->message) &&
isset($responseData->key)
){
// waypoint could not be set...
$error = (object) [];
$error->type = 'error';
$error->message = $responseData->key;
$waypointData['error'] = $error;
}
}
}
return $waypointData;
}
/**
* update character
* @param $characterData
* @return \Model\CharacterModel
* @throws \Exception
*/
protected function updateCharacter($characterData){
$characterModel = null;
$corporationModel = null;
$allianceModel = null;
if( isset($characterData->corporation) ){
/**
* @var Model\CorporationModel $corporationModel
*/
$corporationModel = Model\BasicModel::getNew('CorporationModel');
$corporationModel->getById($characterData->corporation['id'], 0);
$corporationModel->copyfrom($characterData->corporation);
$corporationModel->save();
}
if( isset($characterData->alliance) ){
/**
* @var Model\AllianceModel $allianceModel
*/
$allianceModel = Model\BasicModel::getNew('AllianceModel');
$allianceModel->getById($characterData->alliance['id'], 0);
$allianceModel->copyfrom($characterData->alliance);
$allianceModel->save();
}
if( isset($characterData->character) ){
/**
* @var Model\CharacterModel $characterModel
*/
$characterModel = Model\BasicModel::getNew('CharacterModel');
$characterModel->getById($characterData->character['id'], 0);
$characterModel->copyfrom($characterData->character);
$characterModel->corporationId = $corporationModel;
$characterModel->allianceId = $allianceModel;
$characterModel = $characterModel->save();
}
return $characterModel;
}
/**
* get CREST server status (online/offline)
* @return \stdClass object
*/
public function getCrestServerStatus(){
$endpoints = $this->getEndpoints();
// set default status e.g. Endpoints don´t work
$data = (object) [];
$data->crestOffline = true;
$data->serverName = 'EVE ONLINE';
$data->serviceStatus = [
'eve' => 'offline',
'server' => 'offline',
];
$data->userCounts = [
'eve' => 0
];
$endpoint = $this->walkEndpoint($endpoints, '', ['serverName']);
if( !empty($endpoint) ){
$data->crestOffline = false;
$data->serverName = (string) $endpoint;
}
$endpoint = $this->walkEndpoint($endpoints, '', ['serviceStatus']);
if( !empty($endpoint) ){
$data->crestOffline = false;
$data->serviceStatus = (new Mapper\CrestServiceStatus($endpoint))->getData();
}
$endpoint = $this->walkEndpoint($endpoints, '', ['userCounts']);
if( !empty($endpoint) ){
$data->crestOffline = false;
$data->userCounts = (new Mapper\CrestUserCounts($endpoint))->getData();
}
return $data;
}
/**
* check response "Header" data for errors
* @param $headers
* @param string $requestUrl
* @param string $contentType
*/
protected function checkResponseHeaders($headers, $requestUrl = '', $contentType = ''){
$headers = (array)$headers;
if( preg_grep('/^X-Deprecated/i', $headers) ){
self::getCrestLogger()->write(sprintf(self::ERROR_RESOURCE_DEPRECATED, $requestUrl, $contentType));
}
}
/**
* get "Authorization:" Header data
* -> This header is required for any Auth-required endpoints!
* @return string
*/
protected function getAuthorizationHeader(){
return base64_encode(
Controller\Controller::getEnvironmentData('SSO_CCP_CLIENT_ID') . ':'
. Controller\Controller::getEnvironmentData('SSO_CCP_SECRET_KEY')
);
}
/**
* get CCP CREST url from configuration file
* -> throw error if url is broken/missing
* @return string
*/
static function getCrestEndpoint(){
$url = '';
if( \Audit::instance()->url(self::getEnvironmentData('CCP_CREST_URL')) ){
$url = self::getEnvironmentData('CCP_CREST_URL');
}else{
$error = sprintf(self::ERROR_CCP_CREST_URL, __METHOD__);
self::getCrestLogger()->write($error);
\Base::instance()->error(502, $error);
}
return $url;
}
/**
* get CCP SSO url from configuration file
* -> throw error if url is broken/missing
* @return string
*/
static function getSsoUrlRoot(){
$url = '';
if( \Audit::instance()->url(self::getEnvironmentData('SSO_CCP_URL')) ){
$url = self::getEnvironmentData('SSO_CCP_URL');
}else{
$error = sprintf(self::ERROR_CCP_SSO_URL, __METHOD__);
self::getCrestLogger()->write($error);
\Base::instance()->error(502, $error);
}
return $url;
}
static function getAuthorizationEndpoint(){
return self::getSsoUrlRoot() . '/oauth/authorize';
}
static function getVerifyAuthorizationCodeEndpoint(){
return self::getSsoUrlRoot() . '/oauth/token';
}
static function getVerifyUserEndpoint(){
return self::getSsoUrlRoot() . '/oauth/verify';
}
/**
* get logger for CREST logging
* @return \Log
*/
static function getCrestLogger(){
return parent::getLogger('crest');
}
}

View File

@@ -0,0 +1,74 @@
<?php
/**
* Created by PhpStorm.
* User: Exodus
* Date: 23.05.2016
* Time: 18:25
*/
namespace controller\ccp;
use Data\Mapper as Mapper;
use Controller;
use Lib;
class Xml extends Controller\Controller{
/**
* get HTTP request options for API (curl) request
* @return array
*/
protected function getRequestOptions(){
$requestOptions = [
'timeout' => 4,
'user_agent' => $this->getUserAgent(),
'follow_location' => false // otherwise CURLOPT_FOLLOWLOCATION will fail
];
return $requestOptions;
}
/**
* request character data from CCP API
* @param int $characterId
* @return array
*/
public function getPublicCharacterData($characterId){
$characterData = [];
$apiPath = self::getEnvironmentData('CCP_XML') . '/eve/CharacterInfo.xml.aspx';
$baseOptions = $this->getRequestOptions();
$requestOptions = [
'method' => 'GET',
'content' => [
'characterID' => (int)$characterId
]
];
$requestOptions = array_merge($baseOptions, $requestOptions);
$apiResponse = Lib\Web::instance()->request($apiPath, $requestOptions );
if(
$apiResponse['body'] &&
($xml = simplexml_load_string($apiResponse['body']))
){
if(
isset($xml->result) &&
is_object($rowApiData = $xml->result->children())
){
foreach($rowApiData as $item){
// map attributes to array
if(count($item->children()) == 0){
$characterData[$item->getName()] = strval($item);
}
}
}
}
$data = (new Mapper\CcpCharacterMapper($characterData))->getData();
return $data;
}
}

View File

@@ -1,184 +0,0 @@
<?php
/**
* Created by PhpStorm.
* User: exodus4d
* Date: 28.03.15
* Time: 17:01
*/
namespace Controller;
use Model;
/**
* CCP API controller
* Class CcpApiController
* @package Controller
*/
class CcpApiController extends Controller{
/**
* get HTTP request options for API (curl) request
* @return array
*/
protected function getRequestOptions(){
$requestOptions = [
'timeout' => 8,
'method' => 'POST',
'user_agent' => $this->getUserAgent(),
'follow_location' => false // otherwise CURLOPT_FOLLOWLOCATION will fail
];
return $requestOptions;
}
/**
* request character information from CCP API
* @param $keyID
* @param $vCode
* @return bool|\SimpleXMLElement
*/
public function requestCharacters($keyID, $vCode){
$apiPath = $this->getF3()->get('PATHFINDER.API.CCP_XML') . '/account/APIKeyInfo.xml.aspx';
$xml = false;
// build request URL
$options = $this->getRequestOptions();
$options['content'] = http_build_query( [
'keyID' => $keyID,
'vCode' => $vCode
]);
$apiResponse = \Web::instance()->request($apiPath, $options );
if($apiResponse['body']){
$xml = simplexml_load_string($apiResponse['body']);
}
return $xml;
}
/**
* update all character information for a given apiModel
* @param $userApiModel
* @return int
* @throws \Exception
*/
public function updateCharacters($userApiModel){
$xml = $this->requestCharacters($userApiModel->keyId, $userApiModel->vCode);
$characterCount = 0;
// important -> user API model must be up2date
// if not -> matched userCharacter cant be found
$userApiModel->getById($userApiModel->id, 0);
if($xml){
// request successful
$rowApiData = $xml->result->key->rowset;
if(
is_object($rowApiData) &&
$rowApiData->children()
){
$characterModel = Model\BasicModel::getNew('CharacterModel');
$corporationModel = Model\BasicModel::getNew('CorporationModel');
$allianceModel = Model\BasicModel::getNew('AllianceModel');
foreach($rowApiData->children() as $characterApiData){
// map attributes to array
$attributeData = current( $characterApiData->attributes() );
$newCharacter = true;
$characterId = (int)$attributeData['characterID'];
$characterModel->getById($characterId, 0);
$corporationModelTemp = null;
$allianceModelTemp = null;
// check if corporation already exists
if($attributeData['corporationID'] > 0){
$corporationModel->getById($attributeData['corporationID'], 0);
if( $corporationModel->dry() ){
$corporationModel->id = $attributeData['corporationID'];
$corporationModel->name = $attributeData['corporationName'];
$corporationModel->save();
}
$corporationModelTemp = $corporationModel;
}
// check if alliance already exists
if($attributeData['allianceID'] > 0){
$allianceModel->getById($attributeData['allianceID'], 0);
if( $allianceModel->dry() ){
$allianceModel->id = $attributeData['allianceID'];
$allianceModel->name = $attributeData['allianceName'];
$allianceModel->save();
}
$allianceModelTemp = $allianceModel;
}
if($userApiModel->userCharacters){
$userApiModel->userCharacters->rewind();
while($userApiModel->userCharacters->valid()){
$tempCharacterModel = $userApiModel->userCharacters->current()->getCharacter();
// character already exists -> update
if($tempCharacterModel->id == $characterId){
$characterModel = $tempCharacterModel;
// unset userCharacter -> all leftover models are no longer part of this API
// --> delete leftover models at the end
$userApiModel->userCharacters->offsetUnset($userApiModel->userCharacters->key());
$newCharacter = false;
break;
}else{
$userApiModel->userCharacters->next();
}
}
$userApiModel->userCharacters->rewind();
}
$characterModel->id = $characterId;
$characterModel->name = $attributeData['characterName'];
$characterModel->corporationId = $corporationModelTemp;
$characterModel->allianceId = $allianceModelTemp;
$characterModel->factionId = $attributeData['factionID'];
$characterModel->factionName = $attributeData['factionName'];
$characterModel->save();
if($newCharacter){
// new character for this API
$userCharactersModel = Model\BasicModel::getNew('UserCharacterModel', 0);
$userCharactersModel->userId = $userApiModel->userId;
$userCharactersModel->apiId = $userApiModel;
$userCharactersModel->characterId = $characterModel;
$userCharactersModel->save();
}
$corporationModel->reset();
$allianceModel->reset();
$characterModel->reset();
$characterCount++;
}
}
// delete leftover userCharacters from this API
if(count($userApiModel->userCharacters) > 0){
while($userApiModel->userCharacters->valid()){
$userApiModel->userCharacters->current()->erase();
$userApiModel->userCharacters->next();
}
}
}
return $characterCount;
}
}

View File

@@ -1,500 +0,0 @@
<?php
/**
* Created by PhpStorm.
* User: Exodus
* Date: 23.01.2016
* Time: 17:18
*
* Handles access to EVE-Online "CREST API" and "SSO" auth functions
* - Add your API credentials in "environment.ini"
* - Check "PATHFINDER.API" in "pathfinder.ini" for correct API URLs
* Hint: \Web::instance()->request automatically caches responses by their response "Cache-Control" header!
*/
namespace Controller;
use Data\Mapper as Mapper;
use Model;
class CcpSsoController extends Controller {
const SESSION_KEY_ACCESS_TOKEN = 'SESSION.sso.access_token';
const SESSION_KEY_REFRESH_TOKEN = 'SESSION.sso.refresh_token';
const ERROR_CCP_SSO_URL = 'Invalid "PATHFINDER.API.CCP_SSO" url. %s';
const ERROR_CCP_CREST_URL = 'Invalid "PATHFINDER.API.CCP_CREST" url. %s';
const ERROR_RESOURCE_DEPRECATED = 'Resource: %s has been marked deprecated. %s';
const ERROR_ACCESS_TOKEN = 'Unable to get a valid "access_token. %s';
const ERROR_VERIFY_CHARACTER = 'Unable to verify character data. %s';
const ERROR_GET_ENDPOINTS = 'Unable to get endpoints data. $s';
const ERROR_GET_ENDPOINT = 'Unable to get endpoint data. $s';
const ERROR_FIND_ENDPOINT = 'Unable to find endpoint: %s';
/**
* "Scopes" that are used by pathfinder
* -> Enable scopes: https://developers.eveonline.com
* @var array
*/
private $requestScopes = [
'characterLocationRead',
'characterNavigationWrite'
];
/**
* timeout for API calls
* @var int
*/
private $apiTimeout = 3;
/**
* redirect user to CCP SSO page and request authorization
* @param $f3
*/
public function requestAuthorization($f3){
// used for state check between request and callback
$state = bin2hex(mcrypt_create_iv(12, MCRYPT_DEV_URANDOM));
$f3->set('SESSION.sso.state', $state);
$urlParams = [
'response_type' => 'code',
'redirect_uri' => Controller::getEnvironmentData('URL') . $f3->build('/sso/callbackAuthorization'),
'client_id' => Controller::getEnvironmentData('SSO_CCP_CLIENT_ID'),
'scope' => implode(' ', $this->requestScopes),
'state' => $state
];
$ssoAuthUrl = self::getAuthorizationEndpoint() . '?' . http_build_query($urlParams, '', '&', PHP_QUERY_RFC3986 );
$f3->status(302);
$f3->reroute($ssoAuthUrl);
}
/**
* callback handler for CCP SSO user Auth
* -> see requestAuthorization()
* @param $f3
*/
public function callbackAuthorization($f3){
$getParams = (array)$f3->get('GET');
if($f3->exists('SESSION.sso.state')){
// check response and validate 'state'
if(
isset($getParams['code']) &&
isset($getParams['state']) &&
!empty($getParams['code']) &&
!empty($getParams['state']) &&
$f3->get('SESSION.sso.state') === $getParams['state']
){
// clear 'state' for new next request
$f3->clear('SESSION.sso.state');
$accessToken = $this->getAccessToken($getParams['code']);
if($accessToken){
$data = $this->verifyCharacterData($accessToken);
$characterData = $this->getCharacterData($accessToken);
$characterModel = $this->updateCharacter($characterData);
if( !is_null($characterModel) ){
// everything OK -> login succeeded
}
}
}
}
// on error -> route back to login form
$this->getF3()->reroute('@login');
}
/**
* get a valid "access_token" for oAuth 2.0 verification
* -> if $authCode is set -> request NEW "access_token"
* -> else check for existing (not expired) "access_token"
* -> else try to refresh auth and get fresh "access_token"
* @param bool $authCode
* @return bool|mixed
*/
private function getAccessToken($authCode = false){
$accessToken = false;
if( !empty($authCode) ){
// Authentication Code is set -> request new Access Token -------------------------------------------------
// clear "old" token (if exist and still valid)
$this->getF3()->clear(self::SESSION_KEY_ACCESS_TOKEN);
$accessToken = $this->verifyAuthorizationCode($authCode);
}elseif($this->getF3()->exists(self::SESSION_KEY_ACCESS_TOKEN)){
// Access Token exists and not expired --------------------------------------------------------------------
$accessToken = $this->getF3()->get(self::SESSION_KEY_ACCESS_TOKEN);
}elseif($this->getF3()->exists(self::SESSION_KEY_REFRESH_TOKEN)){
// Refresh Token exists -> refresh Access Token -----------------------------------------------------------
$accessToken = $this->refreshAccessToken($this->getF3()->get(self::SESSION_KEY_REFRESH_TOKEN));
}else{
// Unable to get Token -> trigger error -------------------------------------------------------------------
$this->getLogger('error')->write(sprintf(self::ERROR_ACCESS_TOKEN, $authCode));
}
return $accessToken;
}
/**
* verify authorization code, and get an "access_token" data
* @param $authCode
* @return bool|mixed
*/
private function verifyAuthorizationCode($authCode){
$requestParams = [
'grant_type' => 'authorization_code',
'code' => $authCode
];
return $this->requestAccessToken($requestParams);
}
/**
* get new "access_token" by an existing "refresh_token"
* -> if "access_token" is expired, this function gets a fresh one
* @param $refreshToken
* @return bool|mixed
*/
private function refreshAccessToken($refreshToken){
$requestParams = [
'grant_type' => 'refresh_token',
'refresh_token' => $refreshToken
];
return $this->requestAccessToken($requestParams);
}
/**
* request an "access_token" AND "refresh_token" data
* -> this can either be done by sending a valid "authorization code"
* OR by providing a valid "refresh_token"
* @param $requestParams
* @return bool|mixed
*/
private function requestAccessToken($requestParams){
$verifyAuthCodeUrl = self::getVerifyAuthorizationCodeEndpoint();
$verifyAuthCodeUrlParts = parse_url($verifyAuthCodeUrl);
$accessToken = false;
if($verifyAuthCodeUrlParts){
$contentType = 'application/x-www-form-urlencoded';
$requestOptions = [
'timeout' => $this->apiTimeout,
'method' => 'POST',
'user_agent' => $this->getUserAgent(),
'header' => [
'Authorization: Basic ' . $this->getAuthorizationHeader(),
'Content-Type: ' . $contentType,
'Host: ' . $verifyAuthCodeUrlParts['host']
]
];
// content (parameters to send with)
$requestOptions['content'] = http_build_query($requestParams);
$apiResponse = \Web::instance()->request($verifyAuthCodeUrl, $requestOptions);
if($apiResponse['body']){
$authCodeRequestData = json_decode($apiResponse['body']);
if(property_exists($authCodeRequestData, 'refresh_token')){
// this token is used to refresh/get a new access_token when expires
$this->getF3()->set(self::SESSION_KEY_REFRESH_TOKEN, $authCodeRequestData->refresh_token);
}
if(property_exists($authCodeRequestData, 'access_token')){
// this token is required for endpoints that require Auth
$accessToken = $this->getF3()->set(self::SESSION_KEY_ACCESS_TOKEN, $authCodeRequestData->access_token);
}
}else{
$this->getLogger('error')->write(sprintf(self::ERROR_ACCESS_TOKEN, print_r($requestParams, true)));
}
}else{
$this->getLogger('error')->write(sprintf(self::ERROR_CCP_SSO_URL, __METHOD__));
}
return $accessToken;
}
/**
* verify character data by "access_token"
* -> get some basic information (like character id)
* -> if more character information is required, use CREST endpoints request instead
* @param $accessToken
* @return bool|mixed
*/
private function verifyCharacterData($accessToken){
$verifyUserUrl = self::getVerifyUserEndpoint();
$verifyUrlParts = parse_url($verifyUserUrl);
$characterData = false;
if($verifyUrlParts){
$requestOptions = [
'timeout' => $this->apiTimeout,
'method' => 'GET',
'user_agent' => $this->getUserAgent(),
'header' => [
'Authorization: Bearer ' . $accessToken,
'Host: ' . $verifyUrlParts['host']
]
];
$apiResponse = \Web::instance()->request($verifyUserUrl, $requestOptions);
if($apiResponse['body']){
$characterData = json_decode($apiResponse['body']);
}else{
$this->getLogger('error')->write(sprintf(self::ERROR_VERIFY_CHARACTER, __METHOD__));
}
}else{
$this->getLogger('error')->write(sprintf(self::ERROR_CCP_SSO_URL, __METHOD__));
}
return $characterData;
}
/**
* get all available Endpoints
* @param $accessToken
* @return bool|mixed
*/
private function getEndpoints($accessToken){
$crestUrl = self::getCrestEndpoint();
$endpointsData = false;
$crestUrlParts = parse_url($crestUrl);
if($crestUrlParts){
// represents API version
$contentType = 'application/vnd.ccp.eve.Api-v3+json';
$requestOptions = [
'timeout' => $this->apiTimeout,
'method' => 'GET',
'user_agent' => $this->getUserAgent(),
'header' => [
'Authorization: Bearer ' . $accessToken,
'Accept: ' . $contentType,
'Host: ' . $crestUrlParts['host']
]
];
$apiResponse = \Web::instance()->request($crestUrl, $requestOptions);
if($apiResponse['headers']){
// check headers for error
$this->checkResponseHeaders($apiResponse['headers'], $crestUrl, $contentType);
if($apiResponse['body']){
$endpointsData = json_decode($apiResponse['body'], true);
}else{
$this->getLogger('error')->write(sprintf(self::ERROR_GET_ENDPOINTS, __METHOD__));
}
}
}else{
$this->getLogger('error')->write(sprintf(self::ERROR_CCP_CREST_URL, __METHOD__));
}
return $endpointsData;
}
private function walkEndpoint($accessToken, $endpoint, $path = []){
$targetEndpoint = null;
if( !empty($path) ){
$newNode = array_shift($path);
if(isset($endpoint[$newNode])){
$currentEndpoint = $endpoint[$newNode];
if(isset($currentEndpoint['href'])){
$newEndpoint = $this->getEndpoint($accessToken, $currentEndpoint['href']);
$targetEndpoint = $this->walkEndpoint($accessToken, $newEndpoint, $path);
}else{
// TODO leaf
$targetEndpoint = ' target:) ';
}
}else{
// endpoint not found
$this->getLogger('error')->write(sprintf(self::ERROR_FIND_ENDPOINT, $newNode));
}
}else{
$targetEndpoint = $endpoint;
}
return $targetEndpoint;
}
/**
* get a specific endpoint by its $resourceUrl
* @param $accessToken
* @param $resourceUrl
* @return mixed|null
*/
private function getEndpoint($accessToken, $resourceUrl){
$resourceUrlParts = parse_url($resourceUrl);
$endpoint = null;
if($resourceUrlParts){
$requestOptions = [
'timeout' => $this->apiTimeout,
'method' => 'GET',
'user_agent' => $this->getUserAgent(),
'header' => [
'Authorization: Bearer ' . $accessToken,
'Host: login.eveonline.com',
'Host: ' . $resourceUrlParts['host']
]
];
$apiResponse = \Web::instance()->request($resourceUrl, $requestOptions);
if($apiResponse['headers']){
// check headers for error
$this->checkResponseHeaders($apiResponse['headers'], $requestOptions);
if($apiResponse['body']){
$endpoint = json_decode($apiResponse['body'], true);
}else{
$this->getLogger('error')->write(sprintf(self::ERROR_GET_ENDPOINT, __METHOD__));
}
}
}else{
$this->getLogger('error')->write(sprintf(self::ERROR_CCP_CREST_URL, __METHOD__));
}
return $endpoint;
}
/**
* get character data
* @param $accessToken
* @return array
*/
private function getCharacterData($accessToken){
$endpoints = $this->getEndpoints($accessToken);
$characterData = [];
$endpoint = $this->walkEndpoint($accessToken, $endpoints, [
'decode',
'character'
]);
if( !empty($endpoint) ){
$characterData['character'] = (new Mapper\CrestCharacter($endpoint))->getData();
if(isset($endpoint['corporation'])){
$characterData['corporation'] = (new Mapper\CrestCorporation($endpoint['corporation']))->getData();
}
}
return $characterData;
}
/*
private function getCharacterLocation($accessToken){
$endpoints = $this->getEndpoints($accessToken);
$endpoint = $this->walkEndpoint($accessToken, $endpoints, [
'decode',
'character',
'location'
]);
var_dump($endpoint);
die(' END getCharacterLocation() ');
$characterData = [];
return $characterData;
} */
/**
* update character
* @param $characterData
* @return \Model\CharacterModel
* @throws \Exception
*/
private function updateCharacter($characterData){
$characterModel = null;
$corporationModel = null;
$allianceModel = null;
if( !empty($characterData['corporation']) ){
$corporationModel = Model\BasicModel::getNew('CorporationModel');
$corporationModel->getById($characterData['corporation']['id'], 0);
$corporationModel->copyfrom($characterData['corporation']);
$corporationModel->save();
}
if( !empty($characterData['alliance']) ){
$allianceModel = Model\BasicModel::getNew('AllianceModel');
$allianceModel->getById($characterData['alliance']['id'], 0);
$allianceModel->copyfrom($characterData['alliance']);
$allianceModel->save();
}
if( !empty($characterData['character']) ){
$characterModel = Model\BasicModel::getNew('CharacterModel');
$characterModel->getById($characterData['character']['id'], 0);
$characterModel->copyfrom($characterData['character']);
$characterModel->corporationId = $corporationModel;
$characterModel->allianceId = $allianceModel;
$characterModel->save();
}
return $characterModel;
}
/**
* check response "Header" data for errors
* @param $headers
* @param string $requestUrl
* @param string $contentType
*/
private function checkResponseHeaders($headers, $requestUrl = '', $contentType = ''){
$headers = (array)$headers;
if(preg_grep ('/^X-Deprecated/i', $headers)){
$this->getLogger('error')->write(sprintf(self::ERROR_RESOURCE_DEPRECATED, $requestUrl, $contentType));
}
}
/**
* get "Authorization:" Header data
* -> This header is required for any Auth-required endpoints!
* @return string
*/
private function getAuthorizationHeader(){
return base64_encode(
Controller::getEnvironmentData('SSO_CCP_CLIENT_ID') . ':'
. Controller::getEnvironmentData('SSO_CCP_SECRET_KEY')
);
}
static function getAuthorizationEndpoint(){
return \Base::instance()->get('PATHFINDER.API.CCP_SSO') . '/oauth/authorize';
}
static function getVerifyAuthorizationCodeEndpoint(){
return \Base::instance()->get('PATHFINDER.API.CCP_SSO') . '/oauth/token';
}
static function getVerifyUserEndpoint(){
return \Base::instance()->get('PATHFINDER.API.CCP_SSO') . '/oauth/verify';
}
static function getCrestEndpoint(){
return \Base::instance()->get('PATHFINDER.API.CCP_CREST');
}
}

View File

@@ -7,50 +7,69 @@
*/
namespace Controller;
use Controller\Api as Api;
use Controller\Ccp\Sso as Sso;
use lib\Config;
use Model;
use DB;
class Controller {
// cookie specific keys (names)
const COOKIE_NAME_STATE = 'cookie';
const COOKIE_PREFIX_CHARACTER = 'char';
const ERROR_SESSION_SUSPECT = 'Suspect id: [%30s], ip: [%40s], new ip: [%40s], User-Agent: %s ';
/**
* @var \Base
*/
protected $f3;
private $template;
/**
* @param mixed $template
* @var string template for render
*/
public function setTemplate($template){
protected $template;
/**
* @param string $template
*/
protected function setTemplate($template){
$this->template = $template;
}
/**
* @return mixed
* @return string
*/
public function getTemplate(){
protected function getTemplate(){
return $this->template;
}
/**
* set global f3 instance
* @param null $f3
* @return null|static
* set $f3 base object
* @param \Base $f3
*/
protected function getF3($f3 = null){
if(is_object($f3)){
$this->f3 = $f3;
}else{
$this->f3 = \Base::instance();
}
protected function setF3(\Base $f3){
$this->f3 = $f3;
}
/**
* get $f3 base object
* @return \Base
*/
protected function getF3(){
if( !($this->f3 instanceof \Base) ){
$this->setF3( \Base::instance() );
}
return $this->f3;
}
/**
* event handler for all "views"
* some global template variables are set in here
* @param $f3
* @param \Base $f3
*/
function beforeroute($f3) {
$this->getF3($f3);
function beforeroute(\Base $f3) {
$this->setF3($f3);
// initiate DB connection
DB\Database::instance('PF');
@@ -73,8 +92,9 @@ class Controller {
/**
* event handler after routing
* -> render view
* @param \Base $f3
*/
public function afterroute($f3){
public function afterroute(\Base $f3){
if($this->getTemplate()){
// Ajax calls don´t need a page render..
// this happens on client side
@@ -85,7 +105,7 @@ class Controller {
/**
* set change the DB connection
* @param string $database
* @return mixed|void
* @return DB\SQL
*/
protected function getDB($database = 'PF'){
return DB\Database::instance()->getDB($database);
@@ -95,51 +115,294 @@ class Controller {
* init new Session handler
*/
protected function initSession(){
// init DB Session (not file based)
if( $this->getDB('PF') instanceof \DB\SQL){
new \DB\SQL\Session($this->getDB('PF'));
// init DB based Session (not file based)
if( $this->getDB('PF') instanceof DB\SQL){
// init session with custom "onsuspect()" handler
new DB\SQL\Session($this->getDB('PF'), 'sessions', true, function($session, $sid){
$f3 = $this->getF3();
if( ($ip = $session->ip() )!= $f3->get('IP') ){
// IP address changed -> not critical
$sessionSuspectLogFile = 'PATHFINDER.LOGFILES.SESSION_SUSPECT';
if( !$f3->devoid($sessionSuspectLogFile) ){
$this->getLogger(
$f3->get($sessionSuspectLogFile)
)->write( sprintf(
self::ERROR_SESSION_SUSPECT,
$sid,
$session->ip(),
$f3->get('IP'),
$f3->get('AGENT')
));
}
// no more error handling here
return true;
}elseif($session->agent() != $f3->get('AGENT') ){
// The default behaviour destroys the suspicious session.
return false;
}
return true;
});
}
}
/**
* get current user model
* @param int $ttl
* @return bool|null
* get cookies "state" information
* -> whether user accepts cookies
* @return bool
*/
protected function getCookieState(){
return (bool)count( $this->getCookieByName(self::COOKIE_NAME_STATE) );
}
/**
* search for existing cookies
* -> either a specific cookie by its name
* -> or get multiple cookies by their name (search by prefix)
* @param $cookieName
* @param bool $prefix
* @return array
*/
protected function getCookieByName($cookieName, $prefix = false){
$data = [];
if(!empty($cookieName)){
$cookieData = (array)$this->getF3()->get('COOKIE');
if($prefix === true){
// look for multiple cookies with same prefix
foreach($cookieData as $name => $value){
if(strpos($name, $cookieName) === 0){
$data[$name] = $value;
}
}
}elseif( isset($cookieData[$cookieName]) ){
// look for a single cookie
$data[$cookieName] = $cookieData[$cookieName];
}
}
return $data;
}
/**
* set/update logged in cookie by character model
* -> store validation data in DB
* @param Model\CharacterModel $character
*/
protected function setLoginCookie(Model\CharacterModel $character){
if( $this->getCookieState() ){
$expireSeconds = (int) $this->getF3()->get('PATHFINDER.LOGIN.COOKIE_EXPIRE');
$expireSeconds *= 24 * 60 * 60;
$timezone = new \DateTimeZone( $this->getF3()->get('TZ') );
$expireTime = new \DateTime('now', $timezone);
// add cookie expire time
$expireTime->add(new \DateInterval('PT' . $expireSeconds . 'S'));
// unique "selector" -> to facilitate database look-ups (small size)
// -> This is preferable to simply using the database id field,
// which leaks the number of active users on the application
$selector = bin2hex(mcrypt_create_iv(12, MCRYPT_DEV_URANDOM));
// generate unique "validator" (strong encryption)
// -> plaintext set to user (cookie), hashed version of this in DB
$size = mcrypt_get_iv_size(MCRYPT_CAST_256, MCRYPT_MODE_CFB);
$validator = bin2hex(mcrypt_create_iv($size, MCRYPT_DEV_URANDOM));
// generate unique cookie token
$token = hash('sha256', $validator);
// get unique cookie name for this character
$name = $character->getCookieName();
$authData = [
'characterId' => $character,
'selector' => $selector,
'token' => $token,
'expires' => $expireTime->format('Y-m-d H:i:s')
];
$authenticationModel = $character->rel('characterAuthentications');
$authenticationModel->copyfrom($authData);
$authenticationModel->save();
$cookieValue = implode(':', [$selector, $validator]);
// get cookie name -> save new one OR update existing cookie
$cookieName = 'COOKIE.' . self::COOKIE_PREFIX_CHARACTER . '_' . $name;
$this->getF3()->set($cookieName, $cookieValue, $expireSeconds);
}
}
/**
* get characters from given cookie data
* -> validate cookie data
* -> validate characters
* -> cf. Sso->requestAuthorization() ( equivalent DB based login)
* @param array $cookieData
* @return array
* @throws \Exception
*/
protected function _getUser($ttl = 5){
$user = false;
protected function getCookieCharacters($cookieData = []){
$characters = [];
if( $this->f3->exists('SESSION.user.id') ){
$userId = (int)$this->f3->get('SESSION.user.id');
if(
$this->getCookieState() &&
!empty($cookieData)
){
/**
* @var $characterAuth Model\CharacterAuthenticationModel
*/
$characterAuth = Model\BasicModel::getNew('CharacterAuthenticationModel');
if($userId > 0){
$userModel = Model\BasicModel::getNew('UserModel', $ttl);
$userModel->getById($userId, $ttl);
$timezone = new \DateTimeZone( $this->getF3()->get('TZ') );
$currentTime = new \DateTime('now', $timezone);
if( !$userModel->dry() ){
$user = $userModel;
foreach($cookieData as $name => $value){
// remove invalid cookies
$invalidCookie = false;
$data = explode(':', $value);
if(count($data) === 2){
// cookie data is well formatted
$characterAuth->getByForeignKey('selector', $data[0], ['limit' => 1]);
// validate expire data
// validate token
if( !$characterAuth->dry() ){
if(
strtotime($characterAuth->expires) >= $currentTime->getTimestamp() &&
hash_equals($characterAuth->token, hash('sha256', $data[1]))
){
// cookie information is valid
// -> try to update character information from CREST
// e.g. Corp has changed, this also ensures valid "access_token"
/**
* @var $character Model\CharacterModel
*/
$updateStatus = $characterAuth->characterId->updateFromCrest();
if( empty($updateStatus) ){
// make sure character data is up2date!
// -> this is not the case if e.g. userCharacters was removed "ownerHash" changed...
$character = $characterAuth->rel('characterId');
$character->getById($characterAuth->characterId->_id);
// check if character still has user (is not the case of "ownerHash" changed
// check if character is still authorized to log in (e.g. corp/ally or config has changed
// -> do NOT remove cookie on failure. This can be a temporary problem (e.g. CREST is down,..)
if(
$character->hasUserCharacter() &&
$character->isAuthorized()
){
$characters[$name] = $character;
}
}
}else{
// clear existing authentication data from DB
$characterAuth->erase();
$invalidCookie = true;
}
}else{
$invalidCookie = true;
}
$characterAuth->reset();
}else{
$invalidCookie = true;
}
// remove invalid cookie
if($invalidCookie){
$this->getF3()->clear('COOKIE.' . $name);
}
}
}
return $user;
return $characters;
}
/**
* log the current user out
* @param $f3
* checks whether a user is currently logged in
* @param \Base $f3
* @return bool
*/
public function logOut($f3){
protected function checkLogTimer($f3){
$loginCheck = false;
// destroy session
if($f3->get(Api\User::SESSION_KEY_CHARACTER_TIME) > 0){
// check logIn time
$logInTime = new \DateTime();
$logInTime->setTimestamp( $f3->get(Api\User::SESSION_KEY_CHARACTER_TIME) );
$now = new \DateTime();
$timeDiff = $now->diff($logInTime);
$minutes = $timeDiff->days * 60 * 24 * 60;
$minutes += $timeDiff->h * 60;
$minutes += $timeDiff->i;
if($minutes <= $f3->get('PATHFINDER.TIMER.LOGGED')){
$loginCheck = true;
}
}
return $loginCheck;
}
/**
* get current character model
* @param int $ttl
* @return Model\CharacterModel|null
* @throws \Exception
*/
public function getCharacter($ttl = 0){
$character = null;
if( $this->getF3()->exists(Api\User::SESSION_KEY_CHARACTER_ID) ){
$characterId = (int)$this->getF3()->get(Api\User::SESSION_KEY_CHARACTER_ID);
if($characterId){
/**
* @var $characterModel Model\CharacterModel
*/
$characterModel = Model\BasicModel::getNew('CharacterModel');
$characterModel->getById($characterId, $ttl);
if(
!$characterModel->dry() &&
$characterModel->hasUserCharacter()
){
$character = &$characterModel;
}
}
}
return $character;
}
/**
* log out current character
* @param \Base $f3
*/
public function logout(\Base $f3){
$params = (array)$f3->get('POST');
// ----------------------------------------------------------
// delete server side cookie validation data
// for the current character as well
if(
$params['clearCookies'] === '1' &&
( $activeCharacter = $this->getCharacter())
){
$activeCharacter->logout();
}
// destroy session login data -------------------------------
$f3->clear('SESSION');
if( !$f3->get('AJAX') ){
// redirect to landing page
$f3->reroute('@login');
}else{
$params = $f3->get('POST');
if( $f3->get('AJAX') ){
$return = (object) [];
if(
isset($params['reroute']) &&
@@ -148,272 +411,59 @@ class Controller {
$return->reroute = rtrim(self::getEnvironmentData('URL'), '/') . $f3->alias('login');
}else{
// no reroute -> errors can be shown
$return->error[] = $this->getUserLoggedOffError();
$return->error[] = $this->getLogoutError();
}
echo json_encode($return);
die();
}
}
/**
* verifies weather a given username and password is valid
* @param $userName
* @param $password
* @return Model\UserModel|null
*/
protected function _verifyUser($userName, $password) {
$validUser = null;
$user = Model\BasicModel::getNew('UserModel', 0);
$user->getByName($userName);
// check userName is valid
if( !$user->dry() ){
// check if password is valid
$isValid = $user->verify($password);
if($isValid === true){
$validUser = $user;
}
}
return $validUser;
}
/**
* check weather the page is IGB trusted or not
* @return mixed
*/
static function isIGBTrusted(){
$igbHeaderData = self::getIGBHeaderData();
return $igbHeaderData->trusted;
}
/**
* get all eve IGB specific header data
* @return object
*/
static function getIGBHeaderData(){
$data = (object) [];
$data->trusted = false;
$data->values = [];
$headerData = self::getRequestHeaders();
foreach($headerData as $key => $value){
$key = strtolower($key);
$key = str_replace('eve_', 'eve-', $key);
if (strpos($key, 'eve-') === 0) {
$key = str_replace('eve-', '', $key);
if (
$key === 'trusted' &&
$value === 'Yes'
) {
$data->trusted = true;
}
$data->values[$key] = $value;
}
}
return $data;
}
/**
* Helper function to return all headers because
* getallheaders() is not available under nginx
*
* @return array (string $key -> string $value)
*/
static function getRequestHeaders(){
$headers = [];
$serverData = self::getServerData();
if(
function_exists('apache_request_headers') &&
$serverData->type === 'apache'
){
// Apache Webserver
$headers = apache_request_headers();
}else{
// Other webserver, e.g. Nginx
// Unfortunately this "fallback" does not work for me (Apache)
// Therefore we can´t use this for all servers
// https://github.com/exodus4d/pathfinder/issues/58
foreach($_SERVER as $name => $value){
if(substr($name, 0, 5) == 'HTTP_'){
$headers[str_replace(' ', '-', ucwords(strtolower(str_replace('_', ' ', substr($name, 5)))))] = $value;
}
}
// redirect to landing page
$f3->reroute('@login');
}
return $headers;
}
/**
* get some server information
* @param int $ttl cache time (default: 1h)
* @return object
* get EVE server status from CREST
* @param \Base $f3
*/
static function getServerData($ttl = 3600){
$f3 = \Base::instance();
$cacheKey = 'PF_SERVER_INFO';
public function getEveServerStatus(\Base $f3){
$return = (object) [];
$return->error = [];
// server status can be cached for some seconds
$cacheKey = 'eve_server_status';
if( !$f3->exists($cacheKey) ){
$serverData = (object) [];
$serverData->type = 'unknown';
$serverData->version = 'unknown';
$serverData->requiredVersion = 'unknown';
$serverData->phpInterfaceType = php_sapi_name();
$sso = new Sso();
$return->status = $sso->getCrestServerStatus();
if(strpos(strtolower($_SERVER['SERVER_SOFTWARE']), 'nginx' ) !== false){
// Nginx server
$serverSoftwareArgs = explode('/', strtolower( $_SERVER['SERVER_SOFTWARE']) );
$serverData->type = reset($serverSoftwareArgs);
$serverData->version = end($serverSoftwareArgs);
$serverData->requiredVersion = $f3->get('REQUIREMENTS.SERVER.NGINX.VERSION');
}elseif(strpos(strtolower($_SERVER['SERVER_SOFTWARE']), 'apache' ) !== false){
// Apache server
$serverData->type = 'apache';
$serverData->requiredVersion = $f3->get('REQUIREMENTS.SERVER.APACHE.VERSION');
// try to get the apache version...
if(function_exists('apache_get_version')){
// function does not exists if PHP is running as CGI/FPM module!
$matches = preg_split('/[\s,\/ ]+/', strtolower( apache_get_version() ) );
if(count($matches) > 1){
$serverData->version = $matches[1];
}
}
if( !$return->status->crestOffline ){
$f3->set($cacheKey, $return, 60);
}
// cache data for one day
$f3->set($cacheKey, $serverData, $ttl);
}else{
// get from cache
$return = $f3->get($cacheKey);
}
return $f3->get($cacheKey);
echo json_encode($return);
}
/**
* check if the current request was send from inGame
* @return bool
*/
static function isIGB(){
$isIGB = false;
$igbHeaderData = self::getIGBHeaderData();
if(count($igbHeaderData->values) > 0){
$isIGB = true;
}
return $isIGB;
}
/**
* get error object is a user is not found/logged of
* @return object
* @return \stdClass
*/
protected function getUserLoggedOffError(){
protected function getLogoutError(){
$userError = (object) [];
$userError->type = 'error';
$userError->message = 'User not found';
return $userError;
}
/**
* get the current registration status
* 0=registration stop |1=new registration allowed
* @return int
*/
static function getRegistrationStatus(){
return (int)\Base::instance()->get('PATHFINDER.REGISTRATION.STATUS');
}
/**
* get a log controller e.g. "debug"
* @param $loggerType
* @return mixed
*/
static function getLogger($loggerType){
return LogController::getLogger($loggerType);
}
/**
* removes illegal characters from a Hive-key that are not allowed
* @param $key
* @return mixed
*/
static function formatHiveKey($key){
$illegalCharacters = ['-', ' '];
return strtolower( str_replace($illegalCharacters, '', $key) );
}
/**
* get environment specific configuration data
* @param $key
* @return mixed|null
*/
static function getEnvironmentData($key){
$f3 = \Base::instance();
$environment = self::getEnvironment();
$environmentKey = 'ENVIRONMENT[' . $environment . '][' . $key . ']';
$data = null;
if( $f3->exists($environmentKey) ){
$data = $f3->get($environmentKey);
}
return $data;
}
/**
* get current server environment status
* -> "DEVELOP" or "PRODUCTION"
* @return mixed
*/
static function getEnvironment(){
$f3 = \Base::instance();
return $f3->get('ENVIRONMENT.SERVER');
}
/**
* check if current server is "PRODUCTION"
* @return bool
*/
static function isProduction(){
return self::getEnvironment() == 'PRODUCTION';
}
/**
* get required MySQL variable value
* @param $key
* @return mixed|null
*/
static function getRequiredMySqlVariables($key){
$f3 = \Base::instance();
$requiredMySqlVarKey = 'REQUIREMENTS[MYSQL][VARS][' . $key . ']';
$data = null;
if( $f3->exists($requiredMySqlVarKey) ){
$data = $f3->get($requiredMySqlVarKey);
}
return $data;
}
/**
* get a program URL by alias
* -> if no $alias given -> get "default" route (index.php)
* @param null $alias
* @return bool
* @return bool|string
*/
protected function getRouteUrl($alias = null){
$url = false;
@@ -452,9 +502,9 @@ class Controller {
* onError() callback function
* -> on AJAX request -> return JSON with error information
* -> on HTTP request -> render error page
* @param $f3
* @param \Base $f3
*/
public function showError($f3){
public function showError(\Base $f3){
// set HTTP status
$errorCode = $f3->get('ERROR.code');
if(!empty($errorCode)){
@@ -510,9 +560,224 @@ class Controller {
/**
* Callback for framework "unload"
* check -> config.ini
* @param \Base $f3
* @return bool
*/
public function unload($f3){
public function unload(\Base $f3){
return true;
}
/**
* get controller by class name
* -> controller class is searched within all controller directories
* @param $className
* @return null|\Controller\
* @throws \Exception
*/
static function getController($className){
$controller = null;
// add subNamespaces for controller classes
$subNamespaces = ['Api', 'Ccp'];
for($i = 0; $i <= count($subNamespaces); $i++){
$path = [__NAMESPACE__];
$path[] = ( isset($subNamespaces[$i - 1]) ) ? $subNamespaces[$i - 1] : '';
$path[] = $className;
$classPath = implode('\\', array_filter($path));
if(class_exists($classPath)){
$controller = new $classPath();
break;
}
}
if( is_null($controller) ){
throw new \Exception( sprintf('Controller class "%s" not found!', $className) );
}
return $controller;
}
/**
* check weather the page is IGB trusted or not
* @return boolean
*/
static function isIGBTrusted(){
$igbHeaderData = self::getIGBHeaderData();
return $igbHeaderData->trusted;
}
/**
* get all eve IGB specific header data
* @return \stdClass
*/
static function getIGBHeaderData(){
$data = (object) [];
$data->trusted = false;
$data->values = [];
$headerData = self::getRequestHeaders();
foreach($headerData as $key => $value){
$key = strtolower($key);
$key = str_replace('eve_', 'eve-', $key);
if (strpos($key, 'eve-') === 0) {
$key = str_replace('eve-', '', $key);
if (
$key === 'trusted' &&
$value === 'Yes'
) {
$data->trusted = true;
}
$data->values[$key] = $value;
}
}
return $data;
}
/**
* Helper function to return all headers because
* getallheaders() is not available under nginx
* @return array (string $key -> string $value)
*/
static function getRequestHeaders(){
$headers = [];
$serverData = self::getServerData();
if(
function_exists('apache_request_headers') &&
$serverData->type === 'apache'
){
// Apache Webserver
$headers = apache_request_headers();
}else{
// Other webserver, e.g. Nginx
// Unfortunately this "fallback" does not work for me (Apache)
// Therefore we can´t use this for all servers
// https://github.com/exodus4d/pathfinder/issues/58
foreach($_SERVER as $name => $value){
if(substr($name, 0, 5) == 'HTTP_'){
$headers[str_replace(' ', '-', ucwords(strtolower(str_replace('_', ' ', substr($name, 5)))))] = $value;
}
}
}
return $headers;
}
/**
* get some server information
* @param int $ttl cache time (default: 1h)
* @return \stdClass
*/
static function getServerData($ttl = 3600){
$f3 = \Base::instance();
$cacheKey = 'PF_SERVER_INFO';
if( !$f3->exists($cacheKey) ){
$serverData = (object) [];
$serverData->type = 'unknown';
$serverData->version = 'unknown';
$serverData->requiredVersion = 'unknown';
$serverData->phpInterfaceType = php_sapi_name();
if(strpos(strtolower($_SERVER['SERVER_SOFTWARE']), 'nginx' ) !== false){
// Nginx server
$serverSoftwareArgs = explode('/', strtolower( $_SERVER['SERVER_SOFTWARE']) );
$serverData->type = reset($serverSoftwareArgs);
$serverData->version = end($serverSoftwareArgs);
$serverData->requiredVersion = $f3->get('REQUIREMENTS.SERVER.NGINX.VERSION');
}elseif(strpos(strtolower($_SERVER['SERVER_SOFTWARE']), 'apache' ) !== false){
// Apache server
$serverData->type = 'apache';
$serverData->requiredVersion = $f3->get('REQUIREMENTS.SERVER.APACHE.VERSION');
// try to get the apache version...
if(function_exists('apache_get_version')){
// function does not exists if PHP is running as CGI/FPM module!
$matches = preg_split('/[\s,\/ ]+/', strtolower( apache_get_version() ) );
if(count($matches) > 1){
$serverData->version = $matches[1];
}
}
}
// cache data for one day
$f3->set($cacheKey, $serverData, $ttl);
}
return $f3->get($cacheKey);
}
/**
* check if the current request was send from inGame
* @return bool
*/
static function isIGB(){
$isIGB = false;
$igbHeaderData = self::getIGBHeaderData();
if(count($igbHeaderData->values) > 0){
$isIGB = true;
}
return $isIGB;
}
/**
* get the current registration status
* 0=registration stop |1=new registration allowed
* @return int
*/
static function getRegistrationStatus(){
return (int)\Base::instance()->get('PATHFINDER.REGISTRATION.STATUS');
}
/**
* get a log controller e.g. "debug"
* @param string $loggerType
* @return \Log
*/
static function getLogger($loggerType){
return LogController::getLogger($loggerType);
}
/**
* removes illegal characters from a Hive-key that are not allowed
* @param $key
* @return string
*/
static function formatHiveKey($key){
$illegalCharacters = ['-', ' '];
return strtolower( str_replace($illegalCharacters, '', $key) );
}
/**
* get environment specific configuration data
* @param string $key
* @return string|null
*/
static function getEnvironmentData($key){
return Config::getEnvironmentData($key);
}
/**
* get required MySQL variable value
* @param $key
* @return string|null
*/
static function getRequiredMySqlVariables($key){
$f3 = \Base::instance();
$requiredMySqlVarKey = 'REQUIREMENTS[MYSQL][VARS][' . $key . ']';
$data = null;
if( $f3->exists($requiredMySqlVarKey) ){
$data = $f3->get($requiredMySqlVarKey);
}
return $data;
}
}

View File

@@ -31,7 +31,6 @@ class LogController extends Controller {
$f3->set($hiveKey, new \Log($logFile));
}
return $f3->get($hiveKey);
}

View File

@@ -8,8 +8,11 @@
namespace Controller;
class MapController extends \Controller\AccessController {
class MapController extends AccessController {
/**
* @param \Base $f3
*/
public function init($f3) {
// page title

View File

@@ -11,11 +11,46 @@ namespace Controller;
use DB;
use DB\SQL;
use DB\SQL\MySQL as MySQL;
use lib\Config;
use Model;
class Setup extends Controller {
/**
* required environment variables
* @var array
*/
protected $environmentVars = [
'TYPE',
'BASE',
'URL',
'DEBUG',
'DB_DNS',
'DB_NAME',
'DB_USER',
'DB_PASS',
'DB_CCP_DNS',
'DB_CCP_NAME',
'DB_CCP_USER',
'DB_CCP_PASS',
'CCP_CREST_URL',
'SSO_CCP_URL',
'SSO_CCP_CLIENT_ID',
'SSO_CCP_SECRET_KEY',
'CCP_XML',
'SMTP_HOST',
'SMTP_PORT',
'SMTP_SCHEME',
'SMTP_USER',
'SMTP_PASS',
'SMTP_FROM',
'SMTP_ERROR'
];
/**
* required database setup
* @var array
*/
protected $databases = [
'PF' => [
'info' => [],
@@ -30,18 +65,17 @@ class Setup extends Controller {
'Model\SystemStatusModel',
'Model\SystemNeighbourModel',
'Model\WormholeModel',
'Model\RegistrationKeyModel',
'Model\CharacterStatusModel',
'Model\ConnectionScopeModel',
'Model\UserMapModel',
'Model\CharacterMapModel',
'Model\AllianceMapModel',
'Model\CorporationMapModel',
'Model\UserApiModel',
'Model\UserCharacterModel',
'Model\CharacterModel',
'Model\CharacterAuthenticationModel',
'Model\CharacterLogModel',
'Model\SystemModel',
@@ -76,9 +110,9 @@ class Setup extends Controller {
/**
* event handler for all "views"
* some global template variables are set in here
* @param $f3
* @param \Base $f3
*/
function beforeroute($f3) {
function beforeroute(\Base $f3) {
// page title
$f3->set('pageTitle', 'Setup');
@@ -92,7 +126,7 @@ class Setup extends Controller {
$f3->set('pathJs', 'public/js/' . $f3->get('PATHFINDER.VERSION') );
}
public function afterroute($f3) {
public function afterroute(\Base $f3) {
// js view (file)
$f3->set('jsView', 'setup');
@@ -103,9 +137,9 @@ class Setup extends Controller {
/**
* main setup route handler
* works as dispatcher for setup functions
* @param $f3
* @param \Base $f3
*/
public function init($f3){
public function init(\Base $f3){
$params = $f3->get('GET');
// enables automatic column fix
@@ -129,22 +163,69 @@ class Setup extends Controller {
$fixColumns = true;
}
// set server information for page render
// set template data ----------------------------------------------------------------
// set environment information
$f3->set('environmentInformation', $this->getEnvironmentInformation($f3));
// set server information
$f3->set('serverInformation', $this->getServerInformation($f3));
// set requirement check information for page render
// set requirement check information
$f3->set('checkRequirements', $this->checkRequirements($f3));
// set database connection information for page render
// set database connection information
$f3->set('checkDatabase', $this->checkDatabase($f3, $fixColumns));
}
/**
* get server information
* @param $f3
* set environment information
* @param \Base $f3
* @return array
*/
protected function getServerInformation($f3){
protected function getEnvironmentInformation(\Base $f3){
$environmentData = [];
// exclude some sensitive data (e.g. database, passwords)
$excludeVars = ['DB_DNS', 'DB_NAME', 'DB_USER',
'DB_PASS', 'DB_CCP_DNS', 'DB_CCP_NAME',
'DB_CCP_USER', 'DB_CCP_PASS'
];
// obscure some values
$obscureVars = ['SSO_CCP_CLIENT_ID', 'SSO_CCP_SECRET_KEY', 'SMTP_PASS'];
foreach($this->environmentVars as $var){
if( !in_array($var, $excludeVars) ){
$value = Config::getEnvironmentData($var);
$check = true;
if(is_null($value)){
// variable missing
$check = false;
$value = '[missing]';
}elseif( in_array($var, $obscureVars)){
$length = strlen($value);
$hideChars = ($length < 10) ? $length : 10;
$value = substr_replace($value, str_repeat('.', 3), -$hideChars);
$value .= ' [' . $length . ']';
}
$environmentData[$var] = [
'label' => $var,
'value' => ((empty($value) && !is_int($value)) ? '&nbsp;' : $value),
'check' => $check
];
}
}
return $environmentData;
}
/**
* get server information
* @param \Base $f3
* @return array
*/
protected function getServerInformation(\Base $f3){
$serverInfo = [
'time' => [
'label' => 'Time',
@@ -178,10 +259,10 @@ class Setup extends Controller {
/**
* check all required backend requirements
* (Fat Free Framework)
* @param $f3
* @param \Base $f3
* @return array
*/
protected function checkRequirements($f3){
protected function checkRequirements(\Base $f3){
// server type ------------------------------------------------------------------
@@ -288,11 +369,11 @@ class Setup extends Controller {
/**
* get database connection information
* @param $f3
* @param \Base $f3
* @param bool|false $exec
* @return array
*/
protected function checkDatabase($f3, $exec = false){
protected function checkDatabase(\Base $f3, $exec = false){
foreach($this->databases as $dbKey => $dbData){
@@ -413,6 +494,7 @@ class Setup extends Controller {
$changedType = false;
$changedUnique = false;
$changedIndex = false;
$addConstraints = [];
// set (new) column information -------------------------------------------------------
$requiredTables[$requiredTableName]['fieldConf'][$columnName]['exists'] = true;
@@ -427,17 +509,22 @@ class Setup extends Controller {
$constraint = $col->newConstraint($constraintData);
$foreignKeyExists = $col->constraintExists($constraint);
// constraint information -> show in template
$requiredTables[$requiredTableName]['foreignKeys'][] = [
'exists' => $foreignKeyExists,
'keyName' => $constraint->getConstraintName()
];
$col->addConstraint($constraint);
if(!$foreignKeyExists){
if($foreignKeyExists){
// drop constraint and re-add again at the and, in case something has changed
$col->dropConstraint($constraint);
}else{
$tableStatusCheckCount++;
$foreignKeyStatusCheck = false;
}
$addConstraints[] = $constraint;
}
}
@@ -452,11 +539,21 @@ class Setup extends Controller {
$tableStatusCheckCount++;
}
// check if column unique changed -----------------------------------------------------
// check if column index changed ------------------------------------------------------
$indexUpdate = false;
$indexKey = (bool)$hasIndex;
$indexUnique = (bool)$hasUnique;
if($currentColIndex != $fieldConf['index']){
$changedIndex = true;
$columnStatusCheck = false;
$tableStatusCheckCount++;
$indexUpdate = true;
$indexKey = (bool) $fieldConf['index'];
}
// check if column unique changed -----------------------------------------------------
if($currentColIndexData['unique'] != $fieldConf['unique']){
$changedUnique = true;
$columnStatusCheck = false;
@@ -466,15 +563,6 @@ class Setup extends Controller {
$indexUnique =(bool)$fieldConf['unique'];
}
// check if column index changed ------------------------------------------------------
if($currentColIndex != $fieldConf['index']){
$changedIndex = true;
$columnStatusCheck = false;
$tableStatusCheckCount++;
$indexUpdate = true;
$indexKey = (bool) $fieldConf['index'];
}
// build table with changed columns ---------------------------------------------------
if(!$columnStatusCheck || !$foreignKeyStatusCheck){
@@ -495,6 +583,12 @@ class Setup extends Controller {
$tableModifier->updateColumn($columnName, $col);
}
// (re-)add constraints !after! index update is done
// otherwise index update will fail if there are existing constraints
foreach($addConstraints as $constraint){
$col->addConstraint($constraint);
}
$buildStatus = $tableModifier->build($exec);
if(
@@ -559,11 +653,11 @@ class Setup extends Controller {
}
/** check MySQL params
* @param $f3
* @param \Base $f3
* @param $db
* @return array
*/
protected function checkDBConfig($f3, $db){
protected function checkDBConfig(\Base $f3, $db){
// some db like "Maria DB" have some strange version strings....
$dbVersionString = $db->version();

View File

@@ -9,6 +9,7 @@
namespace Cron;
use Controller;
use DB;
use lib\Config;
class CcpSystemsUpdate {
@@ -69,7 +70,7 @@ class CcpSystemsUpdate {
/**
* imports all relevant map stats from CCPs API
* >> php index.php "/cron/importSystemData"
* @param $f3
* @param \Base $f3
*/
function importSystemData($f3){
@@ -83,7 +84,7 @@ class CcpSystemsUpdate {
// get current jump Data -------------------------------------------------------
$time_start = microtime(true);
$apiPath = $f3->get('PATHFINDER.API.CCP_XML') . '/map/Jumps.xml.aspx';
$apiPath = Config::getEnvironmentData('CCP_XML') . '/map/Jumps.xml.aspx';
$apiResponse = \Web::instance()->request($apiPath, $this->apiRequestOptions );
@@ -108,7 +109,7 @@ class CcpSystemsUpdate {
// get current kill Data -------------------------------------------------------
$time_start = microtime(true);
$apiPath = $f3->get('PATHFINDER.API.CCP_XML') . '/map/Kills.xml.aspx';
$apiPath = Config::getEnvironmentData('CCP_XML') . '/map/Kills.xml.aspx';
$apiResponse = \Web::instance()->request($apiPath, $this->apiRequestOptions );
$killData = [];

View File

@@ -17,15 +17,17 @@ class CharacterUpdate {
/**
* delete all character log data
* >> php index.php "/cron/deleteLogData"
* @param $f3
* @param \Base $f3
*/
function deleteLogData($f3){
DB\Database::instance()->getDB('PF');
/**
* @var $characterLogModel Model\CharacterLogModel
*/
$characterLogModel = Model\BasicModel::getNew('CharacterLogModel', 0);
// find "old" character logs
// find expired character logs
$characterLogs = $characterLogModel->find([
'TIMESTAMPDIFF(SECOND, updated, NOW() ) > :lifetime',
':lifetime' => (int)$f3->get('PATHFINDER.CACHE.CHARACTER_LOG')
@@ -33,10 +35,35 @@ class CharacterUpdate {
if(is_object($characterLogs)){
foreach($characterLogs as $characterLog){
// delete log and all cached values
$characterLog->erase();
}
}
}
/**
* delete expired character authentication data
* authentication data is used for cookie based login
* >> php index.php "/cron/deleteAuthenticationData"
* @param $f3
*/
function deleteAuthenticationData($f3){
DB\Database::instance()->getDB('PF');
/**
* @var $authenticationModel Model\CharacterAuthenticationModel
*/
$authenticationModel = Model\BasicModel::getNew('CharacterAuthenticationModel', 0);
// find expired authentication data
$authentications = $authenticationModel->find([
'(expires - NOW()) <= 0'
]);
if(is_object($authentications)){
foreach($authentications as $authentication){
$authentication->erase();
}
}
}
}

View File

@@ -20,7 +20,7 @@ class MapUpdate {
/**
* deactivate all "private" maps whose lifetime is over
* >> php index.php "/cron/deactivateMapData"
* @param $f3
* @param \Base $f3
*/
function deactivateMapData($f3){
@@ -46,7 +46,7 @@ class MapUpdate {
/**
* delete all deactivated maps
* >> php index.php "/cron/deleteMapData"
* @param $f3
* @param \Base $f3
*/
function deleteMapData($f3){

View File

@@ -47,43 +47,48 @@ class AbstractIterator extends \RecursiveArrayIterator {
*/
static function recursiveIterator($iterator){
$keyWhitelist = array_keys(static::$map);
while($iterator->valid()){
if(array_key_exists($iterator->key(), static::$map)){
if( isset(static::$map[$iterator->key()]) ){
$mapValue = static::$map[$iterator->key()];
// check for mapping key
if($iterator->hasChildren()){
// recursive call for child elements
$iterator->offsetSet($iterator->key(), forward_static_call(array('self', __METHOD__), $iterator->getChildren())->getArrayCopy());
$iterator->next();
}elseif(is_array(static::$map[$iterator->key()])){
}elseif(is_array($mapValue)){
// a -> array mapping
$parentKey = array_keys(static::$map[$iterator->key()])[0];
$entryKey = array_values(static::$map[$iterator->key()])[0];
$parentKey = array_keys($mapValue)[0];
$entryKey = array_values($mapValue)[0];
// check if key already exists
if($iterator->offsetExists($parentKey)){
$currentValue = $iterator->offsetGet($parentKey);
// add new array entry
$currentValue[$entryKey] = $iterator->current();
$iterator->offsetSet($parentKey, $currentValue);
}else{
$iterator->offsetSet($parentKey, [$entryKey => $iterator->current()]);
$keyWhitelist[] = $parentKey;
}
}elseif(is_object(static::$map[$iterator->key()])){
$iterator->offsetUnset($iterator->key());
}elseif(is_object($mapValue)){
// a -> a (format by function)
$formatFunction = static::$map[$iterator->key()];
$formatFunction = $mapValue;
$iterator->offsetSet($iterator->key(), call_user_func($formatFunction, $iterator));
// just value change no key change
$iterator->next();
}elseif(static::$map[$iterator->key()] !== $iterator->key()){
}elseif($mapValue !== $iterator->key()){
// a -> b mapping (key changed)
$iterator->offsetSet(static::$map[$iterator->key()], $iterator->current());
$iterator->offsetSet($mapValue, $iterator->current());
$iterator->offsetUnset($iterator->key());
$keyWhitelist[] = $mapValue;
}else{
// a -> a (no changes)
$iterator->next();
@@ -91,13 +96,13 @@ class AbstractIterator extends \RecursiveArrayIterator {
}elseif(
static::$removeUnmapped &&
!in_array($iterator->key(), static::$map)
!in_array($iterator->key(), $keyWhitelist)
){
$iterator->offsetUnset($iterator->key());
}else{
$iterator->next();
}
}
return $iterator;

View File

@@ -0,0 +1,37 @@
<?php
/**
* Created by PhpStorm.
* User: Exodus
* Date: 23.05.2016
* Time: 20:32
*/
namespace data\mapper;
class CcpCharacterMapper extends AbstractIterator {
protected static $map = [
'characterID' => ['character' => 'id'],
'characterName' => ['character' => 'name'],
'race' => 'race',
'bloodlineID' => ['blood' => 'id'],
'bloodline' => ['blood' => 'name'],
'ancestryID' => ['origin' => 'id'],
'ancestry' => ['origin' => 'name'],
'corporationID' => ['corp' => 'id'],
'corporation' => ['corp' => 'name'],
'corporationDate' => ['corp' => 'date'],
'allianceID' => ['alli' => 'id'],
'alliance' => ['alli' => 'name'],
'allianceDate' => ['alli' => 'date'],
'securityStatus' => 'security'
];
}

View File

@@ -13,7 +13,6 @@ class CcpSystemsMapper extends AbstractIterator {
protected static $map = [
'system_id' => 'systemId',
'system_name' => 'name',
'system_security' => 'trueSec',
'connstallation_id' => ['constellation' => 'id'],
'constallation_name' => ['constellation' => 'name'],
'region_id' => ['region' => 'id'],
@@ -21,14 +20,19 @@ class CcpSystemsMapper extends AbstractIterator {
];
/**
* get formatted data
* map iterator
* @return array
*/
public function getData(){
// format functions
self::$map['effect'] = function($iterator){
// "system trueSec" mapping -------------------------------------------
self::$map['trueSec'] = function($iterator){
$trueSec = round((float)$iterator['system_security'], 1);
return $trueSec;
};
// "system effect" mapping --------------------------------------------
self::$map['effect'] = function($iterator){
$effect = $iterator['effect'];
switch($iterator['effect']){
@@ -55,8 +59,9 @@ class CcpSystemsMapper extends AbstractIterator {
return $effect;
};
// format functions
// "system security" mapping ------------------------------------------
self::$map['security'] = function($iterator){
$security = '';
if(
$iterator['security'] == 7 ||
@@ -64,7 +69,7 @@ class CcpSystemsMapper extends AbstractIterator {
$iterator['security'] == 9
){
// k-space system
$trueSec = round($iterator['trueSec'], 3);
$trueSec = round($iterator['system_security'], 3);
if($trueSec <= 0){
$security = '0.0';
@@ -98,7 +103,7 @@ class CcpSystemsMapper extends AbstractIterator {
return $security;
};
// format functions
// "system type" mapping ----------------------------------------------
self::$map['type'] = function($iterator){
// TODO refactor
@@ -122,7 +127,6 @@ class CcpSystemsMapper extends AbstractIterator {
iterator_apply($this, 'self::recursiveIterator', [$this]);
return iterator_to_array($this, false);
}
@@ -166,7 +170,7 @@ class CcpSystemsMapper extends AbstractIterator {
// just value change no key change
$removeOldEntry = false;
$iterator -> next();
$iterator->next();
}else{
// a -> b mapping
$iterator->offsetSet( self::$map[$iterator->key()], $iterator->current() );

View File

@@ -0,0 +1,18 @@
<?php
/**
* Created by PhpStorm.
* User: Exodus
* Date: 15.05.2016
* Time: 22:04
*/
namespace Data\Mapper;
class CrestAlliance extends AbstractIterator {
protected static $map = [
'id' => 'id',
'name' => 'name'
];
}

View File

@@ -0,0 +1,19 @@
<?php
/**
* Created by PhpStorm.
* User: Exodus
* Date: 01.05.2016
* Time: 19:17
*/
namespace Data\Mapper;
class CrestServiceStatus extends AbstractIterator {
protected static $map = [
'dust' => 'dust',
'eve' => 'eve',
'server' => 'server'
];
}

View File

@@ -0,0 +1,18 @@
<?php
/**
* Created by PhpStorm.
* User: Exodus
* Date: 09.04.2016
* Time: 21:11
*/
namespace Data\Mapper;
class CrestStation extends AbstractIterator {
protected static $map = [
'id' => 'id',
'name' => 'name'
];
}

View File

@@ -0,0 +1,18 @@
<?php
/**
* Created by PhpStorm.
* User: Exodus
* Date: 07.02.2016
* Time: 14:34
*/
namespace Data\Mapper;
class CrestSystem extends AbstractIterator {
protected static $map = [
'id' => 'id',
'name' => 'name'
];
}

View File

@@ -0,0 +1,18 @@
<?php
/**
* Created by PhpStorm.
* User: Exodus
* Date: 01.05.2016
* Time: 19:42
*/
namespace Data\Mapper;
class CrestUserCounts extends AbstractIterator {
protected static $map = [
'dust' => 'dust',
'eve' => 'eve'
];
}

View File

@@ -0,0 +1,47 @@
<?php
/**
* Created by PhpStorm.
* User: Exodus
* Date: 01.04.2016
* Time: 19:48
*/
namespace Data\Mapper;
class IgbHeader extends AbstractIterator {
protected static $map = [
'charid' => ['character' => 'id'],
'charname' => ['character' => 'name'],
// --------------------------------------------------------------------
'solarsystemid' => ['system' => 'id'],
'solarsystemname' => ['system' => 'name'],
'constellationid' => ['constellation' => 'id'],
'constellationname' => ['constellation' => 'name'],
'regionid' => ['region' => 'id'],
'regionname' => ['region' => 'name'],
// --------------------------------------------------------------------
'shiptypeid' => ['ship' => 'typeId'],
'shiptypename' => ['ship' => 'typeName'],
'shipid' => ['ship' => 'id'],
'shipname' => ['ship' => 'name'],
'stationid' => ['station' => 'id'],
'stationname' => ['station' => 'name'],
// --------------------------------------------------------------------
'corpid' => ['corporation' => 'id'],
'corpname' => ['corporation' => 'name'],
'allianceid' => ['alliance' => 'id'],
'alliancename' => ['alliance' => 'name']
];
}

View File

@@ -80,7 +80,7 @@ class TableModifier extends SQL\TableModifier {
public function dropConstraint($constraint){
if($constraint->isValid()){
$this->queries[] = "ALTER TABLE " . $this->db->quotekey($this->name) . "
DROP FOREIGN KEY " . $this->db->quotekey($constraint->getConstraintName());
DROP FOREIGN KEY " . $this->db->quotekey($constraint->getConstraintName()) . ";";
}else{
trigger_error(sprintf(self::TEXT_ConstraintNotValid, 'table: ' . $this->name . ' constraintName: ' . $constraint->getConstraintName()));
}
@@ -93,18 +93,13 @@ class TableModifier extends SQL\TableModifier {
public function addConstraint($constraint){
if($constraint->isValid()){
if($this->constraintExists($constraint)){
// drop constraint and re-add in case something has changed
$this->dropConstraint($constraint);
}
$this->queries[] = "
ALTER TABLE " . $this->db->quotekey($this->name) . "
ADD CONSTRAINT " . $this->db->quotekey($constraint->getConstraintName()) . "
FOREIGN KEY (" . implode(', ', $constraint->getKeys()) . ")
REFERENCES " . $this->db->quotekey($constraint->getReferencedTable()) . " (" . implode(', ', $constraint->getReferencedCols()) . ")
ON DELETE " . $constraint->getOnDelete() . "
ON UPDATE " . $constraint->getOnUpdate();
ON UPDATE " . $constraint->getOnUpdate() . ";";
}else{
trigger_error(sprintf(self::TEXT_ConstraintNotValid, 'table: ' . $this->name . ' constraintName: ' . $constraint->getConstraintName()));
}
@@ -120,7 +115,15 @@ class Column extends SQL\Column {
const TEXT_TableNameMissing = 'Table name missing for FOREIGN KEY in `%s`';
/**
* ass constraint to this column
* drop constraint from this column
* @param Constraint $constraint
*/
public function dropConstraint(Constraint $constraint){
$this->table->dropConstraint($constraint);
}
/**
* add constraint to this column
* @param Constraint $constraint
*/
public function addConstraint(Constraint $constraint){

150
app/main/lib/config.php Normal file
View File

@@ -0,0 +1,150 @@
<?php
/**
* Created by PhpStorm.
* User: Exodus
* Date: 28.05.2016
* Time: 16:05
*/
namespace lib;
class Config extends \Prefab {
const PREFIX_KEY = 'PF';
const ARRAY_DELIMITER = '-';
const HIVE_KEY_ENVIRONMENT = 'ENVIRONMENT';
/**
* all environment data
* @var array
*/
private $serverConfigData = [];
public function __construct(){
// set server data
// -> CGI params (Nginx)
// -> .htaccess (Apache)
$this->setServerData();
// set environment data
$this->setAllEnvironmentData();
// set hive configuration variables
// -> overwrites default configuration
$this->setHiveVariables();
}
/**
* get environment configuration data
* @return array|null
*/
protected function getAllEnvironmentData(){
$f3 = \Base::instance();
$environmentData = null;
if( $f3->exists(self::HIVE_KEY_ENVIRONMENT) ){
$environmentData = $f3->get(self::HIVE_KEY_ENVIRONMENT);
}else{
$environmentData = $this->setAllEnvironmentData();
}
return $environmentData;
}
/**
* set some global framework variables
* that depend on environment settings
*/
protected function setHiveVariables(){
$f3 = \Base::instance();
// hive keys that should be overwritten by environment config
$hiveKeys = ['BASE', 'URL', 'DEBUG'];
foreach($hiveKeys as $key){
$f3->set($key, self::getEnvironmentData($key));
}
}
/**
* set all environment configuration data
* @return array|null
*/
protected function setAllEnvironmentData(){
$environmentData = null;
$f3 = \Base::instance();
if( !empty($this->serverConfigData['ENV']) ){
// get environment config from $_SERVER data
$environmentData = (array)$this->serverConfigData['ENV'];
$environmentData['TYPE'] = 'PHP: environment variables';
}else{
// get environment data from *.ini file config
$f3->config('app/environment.ini');
if(
$f3->exists(self::HIVE_KEY_ENVIRONMENT) &&
($environment = $f3->get(self::HIVE_KEY_ENVIRONMENT . '.SERVER')) &&
($environmentData = $f3->get(self::HIVE_KEY_ENVIRONMENT . '.' . $environment))
){
$environmentData['TYPE'] = 'Config: environment.ini';
}
}
if( !is_null($environmentData) ){
ksort($environmentData);
$f3->set(self::HIVE_KEY_ENVIRONMENT, $environmentData);
}
return $environmentData;
}
/**
* get/extract all server data passed to PHP
* this can be done by either:
* OS Environment variables:
* -> add to /etc/environment
* OR:
* Nginx (server config):
* -> FastCGI syntax
* fastcgi_param PF-ENV-DEBUG 3;
*
* @return array
*/
protected function setServerData(){
$data = [];
foreach($_SERVER as $key => $value){
if( strpos($key, self::PREFIX_KEY . self::ARRAY_DELIMITER) === 0 ){
$path = explode( self::ARRAY_DELIMITER, $key);
// remove prefix
array_shift($path);
$tmp = &$data;
foreach ($path as $segment) {
$tmp[$segment] = (array)$tmp[$segment];
$tmp = &$tmp[$segment];
}
// type cast values
// (e.g. '1.2' => (float); '4' => (int),...)
$tmp = is_numeric($value) ? $value + 0 : $value;
}
}
$this->serverConfigData = $data;
}
/**
* get a environment variable by hive key
* @param $key
* @return string|null
*/
static function getEnvironmentData($key){
$f3 = \Base::instance();
$hiveKey = self::HIVE_KEY_ENVIRONMENT . '.' . $key;
$data = null;
if( $f3->exists($hiveKey) ){
$data = $f3->get($hiveKey);
}
return $data;
}
}

233
app/main/lib/web.php Normal file
View File

@@ -0,0 +1,233 @@
<?php
/**
* Created by PhpStorm.
* User: Exodus
* Date: 12.03.2016
* Time: 12:28
*/
namespace Lib;
use controller\LogController;
class Web extends \Web {
const ERROR_STATUS_LOG = 'HTTP %s: \'%s\' | url: %s \'%s\'%s';
/**
* max number of CREST curls for a single endpoint until giving up...
* this is because CREST is not very stable
*/
const RETRY_COUNT_MAX = 3;
/**
* end of line
* @var string
*/
private $eol = "\r\n";
/**
* get status code from Header data array
* @param array $headers
* @return int
*/
protected function getStatusCodeFromHeaders($headers = []){
$statusCode = 0;
if(
preg_match(
'/HTTP\/1\.\d (\d{3}?)/',
implode($this->eol, $headers),
$matches
)
){
$statusCode = (int)$matches[1];
}
return $statusCode;
}
/**
* get cache time in seconds from Header data array
* @param array $headers
* @return int
*/
protected function getCacheTimeFromHeaders($headers = []){
$cacheTime = 0;
if(
preg_match(
'/Cache-Control:(.*?)max-age=([0-9]+)/',
implode($this->eol, $headers),
$matches
)
){
$cacheTime = (int)$matches[2];
}elseif(
preg_match(
'/Access-Control-Max-Age: ([0-9]+)/',
implode($this->eol, $headers),
$matches
)
){
$cacheTime = (int)$matches[1];
}
return $cacheTime;
}
/**
* get a unique cache kay for a request
* @param $url
* @param null $options
* @return string
*/
protected function getCacheKey($url, $options = null){
$f3 = \Base::instance();
$headers = isset($options['header']) ? implode($this->eol, (array) $options['header']) : '';
return $f3->hash(
$options['method'] . ' '
. $url . ' '
. $headers
) . 'url';
}
/**
* perform curl() request
* -> caches response by returned HTTP Cache header data
* @param string $url
* @param array|null $options
* @param array $additionalOptions
* @param int $retryCount request counter for failed crest call
* @return array|FALSE|mixed
*/
public function request($url,array $options = null, $additionalOptions = [], $retryCount = 0 ) {
$f3 = \Base::instance();
if( !$f3->exists( $hash = $this->getCacheKey($url, $options) ) ){
// retry same request until request limit is reached
$retry = false;
$result = parent::request($url, $options);
$result['timeout'] = false;
$statusCode = $this->getStatusCodeFromHeaders( $result['headers'] );
switch($statusCode){
case 100:
case 200:
// request succeeded -> check if response should be cached
$ttl = $this->getCacheTimeFromHeaders( $result['headers'] );
if(
$ttl > 0 &&
!empty( json_decode( $result['body'], true ) )
){
$f3->set($hash, $result, $ttl);
}
break;
case 401:
case 415:
// unauthorized
$errorMsg = $this->getErrorMessageFromJsonResponse(
$statusCode,
$options['method'],
$url,
json_decode($result['body'])
);
LogController::getLogger('error')->write($errorMsg);
break;
case 500:
case 501:
case 502:
case 503:
case 505:
$retry = true;
if( $retryCount == self::RETRY_COUNT_MAX ){
$errorMsg = $this->getErrorMessageFromJsonResponse(
$statusCode,
$options['method'],
$url,
json_decode($result['body'])
);
LogController::getLogger('error')->write($errorMsg);
// trigger error
$f3->error($statusCode, $errorMsg);
}
break;
case 504:
case 0:
$retry = true;
if( $retryCount == self::RETRY_COUNT_MAX ){
// timeout -> response should not be cached
$result['timeout'] = true;
$errorMsg = $this->getErrorMessageFromJsonResponse(
504,
$options['method'],
$url,
json_decode($result['body'])
);
// log error
LogController::getLogger('error')->write($errorMsg);
if($additionalOptions['suppressTimeoutErrors'] !== true){
// trigger error
$f3->error(504, $errorMsg);
}
}
break;
default:
// unknown status
$errorMsg = $this->getErrorMessageFromJsonResponse(
$statusCode,
$options['method'],
$url
);
LogController::getLogger('error')->write($errorMsg);
break;
}
if(
$retry &&
$retryCount < self::RETRY_COUNT_MAX
){
$retryCount++;
$this->request($url, $options, $additionalOptions, $retryCount);
}
}else{
$result = $f3->get($hash);
}
return $result;
}
/**
* get error message from response object
* @param int $code
* @param string $method
* @param string $url
* @param null|\stdClass $responseBody
* @return string
*/
protected function getErrorMessageFromJsonResponse($code, $method, $url, $responseBody = null){
if( empty($responseBody->message) ){
$message = @constant('Base::HTTP_' . $code);
}else{
$message = $responseBody->message;
}
$body = '';
if( !is_null($responseBody) ){
$body = ' | body: ' . print_r($responseBody, true);
}
return sprintf(self::ERROR_STATUS_LOG, $code, $message, $method, $url, $body);
}
}

View File

@@ -13,7 +13,7 @@ use Exception;
use Controller;
use DB;
class BasicModel extends \DB\Cortex {
abstract class BasicModel extends \DB\Cortex {
/**
* Hive key with DB object
@@ -81,15 +81,21 @@ class BasicModel extends \DB\Cortex {
$self->clearCacheData();
});
// model updated
$this->afterupdate( function($self){
$self->clearCacheData();
});
// model updated
$this->beforeinsert( function($self){
$self->beforeInsertEvent($self);
});
$this->beforeerase( function($self){
$self->beforeeraseEvent($self);
});
$this->aftererase( function($self){
$self->aftereraseEvent($self);
});
}
@@ -106,18 +112,27 @@ class BasicModel extends \DB\Cortex {
return;
}
if($key != 'updated'){
if(
!$this->dry() &&
$key != 'updated'
){
if( $this->exists($key) ){
$currentVal = $this->get($key);
// if current value is not a relational object
// and value has changed -> update table col
if(
!is_object($currentVal) &&
$currentVal != $val
){
if(is_object($currentVal)){
if(
is_numeric($val) &&
is_subclass_of($currentVal, 'Model\BasicModel') &&
$currentVal->_id !== (int)$val
){
$this->touch('updated');
}
}elseif($currentVal != $val){
$this->touch('updated');
}
}
}
@@ -226,7 +241,7 @@ class BasicModel extends \DB\Cortex {
$cacheKey = null;
// set a model unique cache key if the model is saved
if( $this->_id > 0){
if( $this->id > 0){
// check if there is a given key prefix
// -> if not, use the standard key.
// this is useful for caching multiple data sets according to one row entry
@@ -245,6 +260,67 @@ class BasicModel extends \DB\Cortex {
return $cacheKey;
}
/**
* get cached data from this model
* @param string $dataCacheKeyPrefix - optional key prefix
* @return \stdClass|null
*/
protected function getCacheData($dataCacheKeyPrefix = ''){
$cacheKey = $this->getCacheKey($dataCacheKeyPrefix);
$cacheData = null;
if( !is_null($cacheKey) ){
$f3 = self::getF3();
if( $f3->exists($cacheKey) ){
$cacheData = $f3->get( $cacheKey );
}
}
return $cacheData;
}
/**
* update/set the getData() cache for this object
* @param $cacheData
* @param string $dataCacheKeyPrefix
* @param int $data_ttl
*/
public function updateCacheData($cacheData, $dataCacheKeyPrefix = '', $data_ttl = 300){
$cacheDataTmp = (array)$cacheData;
// check if data should be cached
// and cacheData is not empty
if(
$data_ttl > 0 &&
!empty( $cacheDataTmp )
){
$cacheKey = $this->getCacheKey($dataCacheKeyPrefix);
if( !is_null($cacheKey) ){
self::getF3()->set($cacheKey, $cacheData, $data_ttl);
}
}
}
/**
* unset the getData() cache for this object
*/
public function clearCacheData(){
$cacheKey = $this->getCacheKey();
if( !is_null($cacheKey) ){
$f3 = self::getF3();
if( $f3->exists($cacheKey) ){
$f3->clear($cacheKey);
}
}
}
/**
* Throws a validation error for a giben column
* @param $col
@@ -303,18 +379,18 @@ class BasicModel extends \DB\Cortex {
/**
* get dataSet by foreign column (single result)
* @param $key
* @param $id
* @param $value
* @param array $options
* @param int $ttl
* @return \DB\Cortex
*/
public function getByForeignKey($key, $id, $options = [], $ttl = 60){
public function getByForeignKey($key, $value, $options = [], $ttl = 60){
$querySet = [];
$query = [];
if($this->exists($key)){
$query[] = $key . " = :" . $key;
$querySet[':' . $key] = $id;
$querySet[':' . $key] = $value;
}
// check active column
@@ -338,11 +414,29 @@ class BasicModel extends \DB\Cortex {
}
/**
* function should be overwritten in child classes with access restriction
* @param $accessObject
* Event "Hook" function
* can be overwritten
* @return bool
*/
public function hasAccess($accessObject){
public function beforeeraseEvent($self){
return true;
}
/**
* Event "Hook" function
* can be overwritten
* @return bool
*/
public function aftereraseEvent($self){
return true;
}
/**
* function should be overwritten in child classes with access restriction
* @param CharacterModel $characterModel
* @return bool
*/
public function hasAccess(CharacterModel $characterModel){
return true;
}
@@ -354,113 +448,6 @@ class BasicModel extends \DB\Cortex {
return true;
}
/**
* get cached data from this model
* @param string $dataCacheKeyPrefix - optional key prefix
* @return mixed|null
*/
protected function getCacheData($dataCacheKeyPrefix = ''){
$cacheKey = $this->getCacheKey($dataCacheKeyPrefix);
$cacheData = null;
if( !is_null($cacheKey) ){
$f3 = self::getF3();
if( $f3->exists($cacheKey) ){
$cacheData = $f3->get( $cacheKey );
}
}
return $cacheData;
}
/**
* update/set the getData() cache for this object
* @param $cacheData
* @param string $dataCacheKeyPrefix
* @param int $data_ttl
*/
public function updateCacheData($cacheData, $dataCacheKeyPrefix = '', $data_ttl = 300){
$cacheDataTmp = (array)$cacheData;
// check if data should be cached
// and cacheData is not empty
if(
$data_ttl > 0 &&
!empty( $cacheDataTmp )
){
$cacheKey = $this->getCacheKey($dataCacheKeyPrefix);
if( !is_null($cacheKey) ){
self::getF3()->set($cacheKey, $cacheData, $data_ttl);
}
}
}
/**
* unset the getData() cache for this object
*/
public function clearCacheData(){
$cacheKey = $this->getCacheKey();
if( !is_null($cacheKey) ){
$f3 = self::getF3();
if( $f3->exists($cacheKey) ){
$f3->clear($cacheKey);
}
}
}
/**
* get the current class name
* -> namespace not included
* @return string
*/
public static function getClassName(){
$parts = explode('\\', static::class);
return end($parts);
}
/**
* factory for all Models
* @param $model
* @param int $ttl
* @return null
* @throws \Exception
*/
public static function getNew($model, $ttl = 86400){
$class = null;
$model = '\\' . __NAMESPACE__ . '\\' . $model;
if(class_exists($model)){
$class = new $model( null, null, null, $ttl );
}else{
throw new \Exception('No model class found');
}
return $class;
}
/**
* get the framework instance (singleton)
* @return static
*/
public static function getF3(){
return \Base::instance();
}
/**
* debug log function
* @param $text
*/
public static function log($text){
Controller\LogController::getLogger('debug')->write($text);
}
/**
* export and download table data as *.csv
* this is primarily used for static tables
@@ -573,6 +560,54 @@ class BasicModel extends \DB\Cortex {
return ['added' => $addedCount, 'updated' => $updatedCount, 'deleted' => $deletedCount];
}
/**
* get the current class name
* -> namespace not included
* @return string
*/
public static function getClassName(){
$parts = explode('\\', static::class);
return end($parts);
}
/**
* factory for all Models
* @param string $model
* @param int $ttl
* @return BasicModel
* @throws \Exception
*/
public static function getNew($model, $ttl = 86400){
$class = null;
$model = '\\' . __NAMESPACE__ . '\\' . $model;
if(class_exists($model)){
$class = new $model( null, null, null, $ttl );
}else{
throw new \Exception('No model class found');
}
return $class;
}
/**
* get the framework instance (singleton)
* @return \Base
*/
public static function getF3(){
return \Base::instance();
}
/**
* debug log function
* @param $text
* @param string $logFile
*/
public static function log($text, $logFile = null){
$logFile = isset($logFile) ? $logFile : self::getF3()->get('PATHFINDER.LOGFILES.DEBUG');
Controller\LogController::getLogger($logFile)->write($text);
}
/**
* get tableModifier class for this table
* @return bool|DB\SQL\TableModifier

View File

@@ -0,0 +1,74 @@
<?php
/**
* Created by PhpStorm.
* User: Exodus
* Date: 25.04.2016
* Time: 19:33
*/
namespace Model;
use DB\SQL\Schema;
use Controller;
class CharacterAuthenticationModel extends BasicModel{
protected $table = 'character_authentication';
protected $fieldConf = [
'active' => [
'type' => Schema::DT_BOOL,
'nullable' => false,
'default' => 1,
'index' => true
],
'characterId' => [
'type' => Schema::DT_INT,
'index' => true,
'belongs-to-one' => 'Model\CharacterModel',
'constraint' => [
[
'table' => 'character',
'on-delete' => 'CASCADE'
]
]
],
'selector' => [
'type' => Schema::DT_VARCHAR128,
'nullable' => false,
'default' => '',
'index' => true,
'unique' => true
],
'token' => [
'type' => Schema::DT_VARCHAR128,
'nullable' => false,
'default' => '',
'index' => true
],
'expires' => [
'type' => Schema::DT_TIMESTAMP,
'default' => Schema::DF_CURRENT_TIMESTAMP,
'index' => true
]
];
/**
* Event "Hook" function
* can be overwritten
* @param $self CharacterAuthenticationModel
* @return bool
*/
public function beforeeraseEvent($self){
// clear existing client Cookies as well
$cookieName = Controller\Controller::COOKIE_PREFIX_CHARACTER;
$cookieName .= '_' . $this->characterId->getCookieName();
$self::getF3()->clear('COOKIE.' . $cookieName);
return true;
}
}

View File

@@ -14,6 +14,14 @@ class CharacterLogModel extends BasicModel {
protected $table = 'character_log';
/**
* caching for relational data
* -> 10s matches REST API - Expire: Header-Data
* for "Location" calls
* @var int
*/
protected $rel_ttl = 10;
protected $fieldConf = [
'active' => [
'type' => Schema::DT_BOOL,
@@ -33,6 +41,9 @@ class CharacterLogModel extends BasicModel {
]
]
],
// --------------------------------------------------------------------
'systemId' => [
'type' => Schema::DT_INT,
'index' => true
@@ -42,30 +53,107 @@ class CharacterLogModel extends BasicModel {
'nullable' => false,
'default' => ''
],
'shipId' => [
'constellationId' => [
'type' => Schema::DT_INT,
'index' => true
],
'constellationName' => [
'type' => Schema::DT_VARCHAR128,
'nullable' => false,
'default' => ''
],
'regionId' => [
'type' => Schema::DT_INT,
'index' => true
],
'regionName' => [
'type' => Schema::DT_VARCHAR128,
'nullable' => false,
'default' => ''
],
// --------------------------------------------------------------------
'shipTypeId' => [
'type' => Schema::DT_INT,
'index' => true
],
'shipTypeName' => [
'type' => Schema::DT_VARCHAR128,
'nullable' => false,
'default' => ''
],
'shipId' => [
'type' => Schema::DT_BIGINT,
'index' => true
],
'shipName' => [
'type' => Schema::DT_VARCHAR128,
'nullable' => false,
'default' => ''
],
'shipTypeName' => [
'stationId' => [
'type' => Schema::DT_INT,
'index' => true
],
'stationName' => [
'type' => Schema::DT_VARCHAR128,
'nullable' => false,
'default' => ''
]
];
public function __construct($db = NULL, $table = NULL, $fluid = NULL, $ttl = 0){
/**
* set log data from array
* @param array $logData
*/
public function setData($logData){
parent::__construct($db, $table, $fluid, $ttl);
if( isset($logData['system']) ){
$this->systemId = (int)$logData['system']['id'];
$this->systemName = $logData['system']['name'];
}else{
$this->systemId = null;
$this->systemName = '';
}
if( isset($logData['constellation']) ){
$this->constellationId = (int)$logData['constellation']['id'];
$this->constellationName = $logData['constellation']['name'];
}else{
$this->constellationId = null;
$this->constellationName = '';
}
if( isset($logData['region']) ){
$this->regionId = (int)$logData['region']['id'];
$this->regionName = $logData['region']['name'];
}else{
$this->regionId = null;
$this->regionName = '';
}
// --------------------------------------------------------------------
if( isset($logData['ship']) ){
$this->shipTypeId = (int)$logData['ship']['typeId'];
$this->shipTypeName = $logData['ship']['typeName'];
$this->shipId = (int)$logData['ship']['id'];
$this->shipName = $logData['ship']['name'];
}else{
$this->shipTypeId = null;
$this->shipTypeName = '';
$this->shipId = null;
$this->shipName = '';
}
if( isset($logData['station']) ){
$this->stationId = (int)$logData['station']['id'];
$this->stationName = $logData['station']['name'];
}else{
$this->stationId = null;
$this->stationName = '';
}
// events -----------------------------------------
$this->beforeerase(function($self){
$self->clearCacheData();
});
}
/**
@@ -76,30 +164,30 @@ class CharacterLogModel extends BasicModel {
$logData = (object) [];
$logData->system = (object) [];
$logData->system->id = $this->systemId;
$logData->system->id = (int)$this->systemId;
$logData->system->name = $this->systemName;
$logData->constellation = (object) [];
$logData->constellation->id = (int)$this->constellationId;
$logData->constellation->name = $this->constellationName;
$logData->region = (object) [];
$logData->region->id = (int)$this->regionId;
$logData->region->name = $this->regionName;
// --------------------------------------------------------------------
$logData->ship = (object) [];
$logData->ship->typeId = (int)$this->shipTypeId;
$logData->ship->typeName = $this->shipTypeName;
$logData->ship->id = $this->shipId;
$logData->ship->name = $this->shipName;
$logData->ship->typeName = $this->shipTypeName;
$logData->station = (object) [];
$logData->station->id = (int)$this->stationId;
$logData->station->name = $this->stationName;
return $logData;
}
/**
* see parent
*/
public function clearCacheData(){
parent::clearCacheData();
// delete log cache key as well
$f3 = self::getF3();
$character = $this->characterId;
$character->clearCacheData();
$f3->clear('LOGGED.user.character.id_' . $character->id);
return true;
}
}

View File

@@ -1,18 +1,18 @@
<?php
/**
* Created by PhpStorm.
* User: exodus4d
* Date: 28.02.15
* Time: 11:57
* User: Exodus
* Date: 07.02.2016
* Time: 12:31
*/
namespace Model;
use DB\SQL\Schema;
class UserMapModel extends BasicModel {
class CharacterMapModel extends BasicModel {
protected $table = 'user_map';
protected $table = 'character_map';
protected $fieldConf = [
'active' => [
@@ -21,13 +21,13 @@ class UserMapModel extends BasicModel {
'default' => 1,
'index' => true
],
'userId' => [
'characterId' => [
'type' => Schema::DT_INT,
'index' => true,
'belongs-to-one' => 'Model\UserModel',
'belongs-to-one' => 'Model\CharacterModel',
'constraint' => [
[
'table' => 'user',
'table' => 'character',
'on-delete' => 'CASCADE'
]
]
@@ -66,10 +66,10 @@ class UserMapModel extends BasicModel {
$status = parent::setup($db,$table,$fields);
if($status === true){
$status = parent::setMultiColumnIndex(['userId', 'mapId'], true);
$status = parent::setMultiColumnIndex(['characterId', 'mapId'], true);
}
return $status;
}
}
}

View File

@@ -8,13 +8,20 @@
namespace Model;
use Controller;
use Controller\Ccp\Sso as Sso;
use DB\SQL\Schema;
use Data\Mapper as Mapper;
class CharacterModel extends BasicModel {
protected $table = 'character';
protected $fieldConf = [
'lastLogin' => [
'type' => Schema::DT_TIMESTAMP,
'index' => true
],
'active' => [
'type' => Schema::DT_BOOL,
'nullable' => false,
@@ -26,6 +33,22 @@ class CharacterModel extends BasicModel {
'nullable' => false,
'default' => ''
],
'ownerHash' => [
'type' => Schema::DT_VARCHAR128,
'nullable' => false,
'default' => ''
],
'crestAccessToken' => [
'type' => Schema::DT_VARCHAR256
],
'crestAccessTokenUpdated' => [
'type' => Schema::DT_TIMESTAMP,
'default' => Schema::DF_CURRENT_TIMESTAMP,
'index' => true
],
'crestRefreshToken' => [
'type' => Schema::DT_VARCHAR256
],
'corporationId' => [
'type' => Schema::DT_INT,
'index' => true,
@@ -57,29 +80,47 @@ class CharacterModel extends BasicModel {
'nullable' => false,
'default' => ''
],
'shared' => [
'type' => Schema::DT_BOOL,
'nullable' => false,
'default' => 0
],
'userCharacter' => [
'has-one' => ['Model\UserCharacterModel', 'characterId']
],
'characterLog' => [
'has-one' => ['Model\CharacterLogModel', 'characterId']
],
'characterMaps' => [
'has-many' => ['Model\CharacterMapModel', 'characterId']
],
'characterAuthentications' => [
'has-many' => ['Model\CharacterAuthenticationModel', 'characterId']
]
];
/**
* get character data
* @param bool|false $addCharacterLogData
* @return object
* @return \stdClass
*/
public function getData($addCharacterLogData = false){
$cacheKeyModifier = '';
// check if there is cached data
// temporary disabled (performance test)
$characterData = null; //$this->getCacheData();
if($addCharacterLogData){
$cacheKeyModifier = strtoupper($this->table) . '_LOG';
}
$characterData = $this->getCacheData($cacheKeyModifier);
if(is_null($characterData)){
// no cached character data found
$characterData = (object) [];
$characterData->id = $this->id;
$characterData->name = $this->name;
$characterData->shared = $this->shared;
if($addCharacterLogData){
if($logModel = $this->getLog()){
@@ -100,24 +141,60 @@ class CharacterModel extends BasicModel {
// max caching time for a system
// the cached date has to be cleared manually on any change
// this includes system, connection,... changes (all dependencies)
$this->updateCacheData($characterData, '', 300);
$this->updateCacheData($characterData, $cacheKeyModifier, 10);
}
return $characterData;
}
/**
* set unique "ownerHash" for this character
* -> Hash will change when character is transferred (sold)
* @param string $ownerHash
* @return string
*/
public function set_ownerHash($ownerHash){
if( $this->ownerHash !== $ownerHash ){
if( $this->hasUserCharacter() ){
$this->userCharacter->erase();
}
// delete all existing login-cookie data
$this->logout();
}
return $ownerHash;
}
/**
* set CREST accessToken for current session
* -> update "tokenUpdated" column on change
* -> this is required for expire checking!
* @param string $accessToken
* @return string
*/
public function set_crestAccessToken($accessToken){
if($this->crestAccessToken !== $accessToken){
$this->touch('crestAccessTokenUpdated');
}
return $accessToken;
}
/**
* check whether this character has already a user assigned to it
* @return bool
*/
public function hasUserCharacter(){
return is_object($this->userCharacter);
}
/**
* check whether this character has a corporation
* @return bool
*/
public function hasCorporation(){
$hasCorporation = false;
if($this->corporationId){
$hasCorporation = true;
}
return $hasCorporation;
return is_object($this->corporationId);
}
/**
@@ -125,46 +202,280 @@ class CharacterModel extends BasicModel {
* @return bool
*/
public function hasAlliance(){
$hasAlliance = false;
return is_object($this->allianceId);
}
if($this->allianceId){
$hasAlliance = true;
/**
* @return UserModel|null
*/
public function getUser(){
$user = null;
if($this->hasUserCharacter()){
/**
* @var $user UserModel
*/
$user = $this->userCharacter->userId;
}
return $hasAlliance;
return $user;
}
/**
* get the corporation for this user
* @return mixed|null
* @return \Model\CorporationModel|null
*/
public function getCorporation(){
$corporation = null;
if($this->hasCorporation()){
$corporation = $this->corporationId;
}
return $corporation;
return $this->corporationId;
}
/**
* get the alliance of this user
* @return mixed|null
* @return \Model\AllianceModel|null
*/
public function getAlliance(){
$alliance = null;
return $this->allianceId;
}
if($this->hasAlliance()){
$alliance = $this->allianceId;
/**
* get CREST API "access_token" from OAuth
* @return bool|string
*/
public function getAccessToken(){
$accessToken = false;
// check if there is already an "accessToken" for this user
// check expire timer for stored "accessToken"
if(
!empty($this->crestAccessToken) &&
!empty($this->crestAccessTokenUpdated)
){
$timezone = new \DateTimeZone( $this->getF3()->get('TZ') );
$tokenTime = \DateTime::createFromFormat(
'Y-m-d H:i:s',
$this->crestAccessTokenUpdated,
$timezone
);
// add expire time buffer for this "accessToken"
// token should be marked as "deprecated" BEFORE it actually expires.
$timeBuffer = 2 * 60;
$tokenTime->add(new \DateInterval('PT' . (Sso::ACCESS_KEY_EXPIRE_TIME - $timeBuffer) . 'S'));
$now = new \DateTime('now', $timezone);
if($tokenTime->getTimestamp() > $now->getTimestamp()){
$accessToken = $this->crestAccessToken;
}
}
return $alliance;
// if no "accessToken" was found -> get a fresh one by an existing "refreshToken"
if(
!$accessToken &&
!empty($this->crestRefreshToken)
){
// no accessToken found OR token is deprecated
$ssoController = new Sso();
$accessData = $ssoController->refreshAccessToken($this->crestRefreshToken);
if(
isset($accessData->accessToken) &&
isset($accessData->refreshToken)
){
$this->crestAccessToken = $accessData->accessToken;
$this->save();
$accessToken = $this->crestAccessToken;
}
}
return $accessToken;
}
/**
* checks whether this character is authorized to log in
* -> check corp/ally whitelist config (pathfinder.ini)
* @return bool
*/
public function isAuthorized(){
$isAuthorized = false;
$f3 = self::getF3();
$whitelistCorporations = $whitelistAlliance = [];
if( !empty($f3->get('PATHFINDER.LOGIN.CORPORATION')) ){
$whitelistCorporations = array_map('trim',(array) $f3->get('PATHFINDER.LOGIN.CORPORATION') );
}
if( !empty($f3->get('PATHFINDER.LOGIN.ALLIANCE')) ){
$whitelistAlliance = array_map('trim',(array) $f3->get('PATHFINDER.LOGIN.ALLIANCE') );
}
if(
empty($whitelistCorporations) &&
empty($whitelistAlliance)
){
// no corp/ally restrictions set -> any character is allowed to login
$isAuthorized = true;
}else{
// check if character corporation is set in whitelist
if(
!empty($whitelistCorporations) &&
$this->hasCorporation() &&
in_array($this->getCorporation()->_id, $whitelistCorporations)
){
$isAuthorized = true;
}
// check if character alliance is set in whitelist
if(
!$isAuthorized &&
!empty($whitelistAlliance) &&
$this->hasAlliance() &&
in_array($this->getAlliance()->_id, $whitelistAlliance)
){
$isAuthorized = true;
}
}
return $isAuthorized;
}
/**
* update character log (active system, ...)
* -> HTTP Header Data (if IGB)
* -> CREST API request for character log data (if not IGB)
* @param array $additionalOptions (optional) request options for cURL request
* @return $this
*/
public function updateLog($additionalOptions = []){
// check if everything is OK
// -> do not update log in case of temporary CREST timeouts
$updateLogData = false;
$logData = [];
$headerData = Controller\Controller::getIGBHeaderData();
// check if IGB Data is available
if(
$headerData->trusted === true &&
!empty($headerData->values)
){
// format header data
$formattedHeaderData = (new Mapper\IgbHeader($headerData->values))->getData();
// just for security -> check if Header Data matches THIS character
// in case current IGB-Character is NOT the one logged on -> don´t update log
if(
isset($formattedHeaderData['character']) &&
$formattedHeaderData['character']['id'] == $this->_id
){
$updateLogData = true;
$logData = $formattedHeaderData;
}
}
if($updateLogData == false){
// ... IGB Header data not found OR character does not match current active character
// -> try to pull data from CREST
$ssoController = new Sso();
$logData = $ssoController->getCharacterLocationData($this->getAccessToken(), 10, $additionalOptions);
if($logData['timeout'] === false){
$updateLogData = true;
}
}
if($updateLogData == true){
if( empty($logData['system']) ){
// character is not in-game
if(is_object($this->characterLog)){
// delete existing log
$this->characterLog->erase();
$this->save();
}
}else{
// character is currently in-game
if( !$characterLog = $this->getLog() ){
// create new log
$characterLog = $this->rel('characterLog');
$characterLog->characterId = $this->_id;
}
$characterLog->setData($logData);
$characterLog->save();
$this->characterLog = $characterLog;
}
}
return $this;
}
/**
* update character data from CCPs CREST API
* @return array (some status messages)
*/
public function updateFromCrest(){
$status = [];
if( $accessToken = $this->getAccessToken() ){
// et basic character data
// -> this is required for "ownerHash" hash check (e.g. character was sold,..)
// -> the "id" check is just for security and should NEVER fail!
$ssoController = new Sso();
if(
!is_null( $verificationCharacterData = $ssoController->verifyCharacterData($accessToken) ) &&
$verificationCharacterData->CharacterID === $this->_id
){
// get character data from CREST
$characterData = $ssoController->getCharacterData($accessToken);
if( isset($characterData->character) ){
$characterData->character['ownerHash'] = $verificationCharacterData->CharacterOwnerHash;
$corporation = null;
$alliance = null;
if( isset($characterData->corporation) ){
/**
* @var $corporation CorporationModel
*/
$corporation = $this->rel('corporationId');
$corporation->getById($characterData->corporation['id'], 0);
$corporation->copyfrom($characterData->corporation, ['id', 'name', 'isNPC']);
$corporation->save();
}
if( isset($characterData->alliance) ){
/**
* @var $alliance AllianceModel
*/
$alliance = $this->rel('allianceId');
$alliance->getById($characterData->alliance['id'], 0);
$alliance->copyfrom($characterData->alliance, ['id', 'name']);
$alliance->save();
}
$this->copyfrom($characterData->character, ['name', 'ownerHash']);
$this->set('corporationId', is_object($corporation) ? $corporation->get('id') : null);
$this->set('allianceId', is_object($alliance) ? $alliance->get('id') : null);
$this->save();
}
}else{
$status[] = sprintf(Sso::ERROR_VERIFY_CHARACTER, $this->name);
}
}else{
$status[] = sprintf(Sso::ERROR_ACCESS_TOKEN, $this->name);
}
return $status;
}
/**
* get a unique cookie name for this character
* -> cookie name does not have to be "secure"
* -> but is should be unique
* @return string
*/
public function getCookieName(){
return md5($this->name);
}
/**
* get the character log entry for this character
* @return bool|null
* @return bool|CharacterLogModel
*/
public function getLog(){
@@ -173,10 +484,78 @@ class CharacterModel extends BasicModel {
is_object($this->characterLog) &&
!$this->characterLog->dry()
){
$characterLog = $this->characterLog;
$characterLog = &$this->characterLog;
}
return $characterLog;
}
/**
* get mapModel by id and check if user has access
* @param $mapId
* @return MapModel|null
*/
public function getMap($mapId){
/**
* @var $map MapModel
*/
$map = self::getNew('MapModel');
$map->getById( (int)$mapId );
$returnMap = null;
if($map->hasAccess($this)){
$returnMap = $map;
}
return $returnMap;
}
/**
* get all accessible map models for this character
* @return MapModel[]
*/
public function getMaps(){
$this->filter(
'characterMaps',
['active = ?', 1],
['order' => 'created']
);
$maps = [];
if($alliance = $this->getAlliance()){
$maps = array_merge($maps, $alliance->getMaps());
}
if($corporation = $this->getCorporation()){
$maps = array_merge($maps, $corporation->getMaps());
}
if($this->characterMaps){
$mapCountPrivate = 0;
foreach($this->characterMaps as &$characterMap){
if(
$mapCountPrivate < self::getF3()->get('PATHFINDER.MAX_MAPS_PRIVATE') &&
$characterMap->mapId->isActive()
){
$maps[] = &$characterMap->mapId;
$mapCountPrivate++;
}
}
}
return $maps;
}
public function logout(){
if($this->characterAuthentications){
foreach($this->characterAuthentications as $characterAuthentication){
/**
* @var $characterAuthentication CharacterAuthenticationModel
*/
$characterAuthentication->erase();
}
}
}
}

View File

@@ -103,11 +103,11 @@ class ConnectionModel extends BasicModel{
/**
* check object for model access
* @param $accessObject
* @return bool
* @param CharacterModel $characterModel
* @return mixed
*/
public function hasAccess($accessObject){
return $this->mapId->hasAccess($accessObject);
public function hasAccess(CharacterModel $characterModel){
return $this->mapId->hasAccess($characterModel);
}
/**
@@ -117,8 +117,12 @@ class ConnectionModel extends BasicModel{
public function isValid(){
$isValid = true;
// check if source/target system are not equal
// check if source/target belong to same map
if( $this->source->mapId->id !== $this->target->mapId->id ){
if(
$this->source->_id === $this->target->_id ||
$this->source->mapId->_id !== $this->target->mapId->_id
){
$isValid = false;
}
@@ -127,13 +131,12 @@ class ConnectionModel extends BasicModel{
/**
* delete a connection
* @param $accessObject
* @param CharacterModel $characterModel
*/
public function delete($accessObject){
if(!$this->dry()){
// check if editor has access
if($this->hasAccess($accessObject)){
public function delete(CharacterModel $characterModel){
if( !$this->dry() ){
// check if character has access
if($this->hasAccess($characterModel)){
$this->erase();
}
}

View File

@@ -8,6 +8,7 @@
namespace Model;
use Controller\Api\User;
use DB\SQL\Schema;
class MapModel extends BasicModel {
@@ -60,8 +61,8 @@ class MapModel extends BasicModel {
'connections' => [
'has-many' => ['Model\ConnectionModel', 'mapId']
],
'mapUsers' => [
'has-many' => ['Model\UserMapModel', 'mapId']
'mapCharacters' => [
'has-many' => ['Model\CharacterMapModel', 'mapId']
],
'mapCorporations' => [
'has-many' => ['Model\CorporationMapModel', 'mapId']
@@ -117,16 +118,14 @@ class MapModel extends BasicModel {
}
}
/**
* get map data
* -> this includes system and connection data as well!
* @return array
* @return \stdClass
*/
public function getData(){
// check if there is cached data
$mapDataAll = $this->getCacheData();
// check if there is cached data
$mapDataAll = $this->getCacheData();
if(is_null($mapDataAll)){
// no cached map data found
@@ -158,17 +157,23 @@ class MapModel extends BasicModel {
// get access object data -------------------------------------
if($this->isPrivate()){
$users = $this->getUsers();
$userData = [];
foreach($users as $user){
$userData[] = $user->getSimpleData();
$characters = $this->getCharacters();
$characterData = [];
foreach($characters as $character){
/**
* @var $character CharacterModel
*/
$characterData[] = $character->getData();
}
$mapData->access->user = $userData;
$mapData->access->character = $characterData;
} elseif($this->isCorporation()){
$corporations = $this->getCorporations();
$corporationData = [];
foreach($corporations as $corporation){
/**
* @var $corporation CorporationModel
*/
$corporationData[] = $corporation->getData();
}
$mapData->access->corporation = $corporationData;
@@ -177,6 +182,9 @@ class MapModel extends BasicModel {
$allianceData = [];
foreach($alliances as $alliance){
/**
* @var $alliance AllianceModel
*/
$allianceData[] = $alliance->getData();
}
$mapData->access->alliance = $allianceData;
@@ -202,39 +210,61 @@ class MapModel extends BasicModel {
}
/**
* search for a system by id
* @param $systemId
* @return null
* search for a system by CCPs systemId
* @param int $systemId
* @return null|SystemModel
*/
public function getSystem($systemId){
$searchSystem = null;
if($systemId > 0){
$systems = $this->getSystems();
foreach($systems as $system){
if($system->id == $systemId){
$searchSystem = $system;
break;
}
}
public function getSystemByCCPId($systemId){
$system = null;
if( !empty($systems = $this->getSystems('systemId', (int)$systemId) ) ){
$system = $systems[0];
}
return $searchSystem;
return $system;
}
/**
* get all system models in this map
* search for a system by id
* @param int $systemId
* @return null|SystemModel
*/
public function getSystemById($systemId){
$system = null;
if( !empty($systems = $this->getSystems('id', (int)$systemId) ) ){
$system = $systems[0];
}
return $system;
}
/**
* get either all system models in this map
* -> or get a specific system by column filter
* @param string $column
* @param string $value
* @return array|mixed
*/
public function getSystems(){
// orderBy x-Coordinate for cleaner frontend animation (left to right)
$this->filter('systems',
['active = :active AND id > 0',
':active' => 1
],
['order' => 'posX']);
public function getSystems($column = '', $value = ''){
$systems = [];
$filterQuery = ['active = :active AND id > 0',
':active' => 1
];
// add more filter options....
if(
!empty($column) &&
!empty($value)
){
$filterQuery[0] .= ' AND ' . $column . ' = :value';
$filterQuery[':value'] = $value;
}
// orderBy x-Coordinate for smoother frontend animation (left to right)
$this->filter('systems', $filterQuery,
['order' => 'posX']
);
if($this->systems){
$systems = $this->systems;
}
@@ -252,6 +282,9 @@ class MapModel extends BasicModel {
$systemData = [];
foreach($systems as $system){
/**
* @var $system SystemModel
*/
$systemData[] = $system->getData();
}
@@ -285,6 +318,9 @@ class MapModel extends BasicModel {
$connectionData = [];
foreach($connections as $connection){
/**
* @var $connection ConnectionModel
*/
$connectionData[] = $connection->getData();
}
@@ -292,26 +328,26 @@ class MapModel extends BasicModel {
}
/**
* set map access for an object (user, corporation or alliance)
* set map access for an object (character, corporation or alliance)
* @param $obj
*/
public function setAccess($obj){
$newAccessGranted = false;
if($obj instanceof UserModel){
if($obj instanceof CharacterModel){
// private map
// check whether the user has already map access
$this->has('mapUsers', ['active = 1 AND userId = :userId', ':userId' => $obj->id]);
$this->has('mapCharacters', ['active = 1 AND characterId = :characterId', ':characterId' => $obj->id]);
$result = $this->findone(['id = :id', ':id' => $this->id]);
if($result === false){
// grant access for the user
$userMap = self::getNew('UserMapModel');
$userMap->userId = $obj;
$userMap->mapId = $this;
$userMap->save();
// grant access for the character
$characterMap = self::getNew('CharacterMapModel');
$characterMap-> characterId = $obj;
$characterMap->mapId = $this;
$characterMap->save();
$newAccessGranted = true;
}
@@ -355,25 +391,34 @@ class MapModel extends BasicModel {
/**
* clear access for a given type of objects
* @param $clearKeys
* @param array $clearKeys
*/
public function clearAccess($clearKeys = ['user', 'corporation', 'alliance']){
public function clearAccess($clearKeys = ['character', 'corporation', 'alliance']){
foreach($clearKeys as $key){
switch($key){
case 'user':
foreach((array)$this->mapUsers as $obj){
$obj->erase();
case 'character':
foreach((array)$this->mapCharacters as $characterMapModel){
/**
* @var CharacterMapModel $characterMapModel
*/
$characterMapModel->erase();
};
break;
case 'corporation':
foreach((array)$this->mapCorporations as $obj){
$obj->erase();
foreach((array)$this->mapCorporations as $corporationMapModel){
/**
* @var CorporationMapModel $corporationMapModel
*/
$corporationMapModel->erase();
};
break;
case 'alliance':
foreach((array)$this->mapAlliances as $obj){
$obj->erase();
foreach((array)$this->mapAlliances as $allianceMapModel){
/**
* @var AllianceMapModel $allianceMapModel
*/
$allianceMapModel->erase();
};
break;
}
@@ -381,22 +426,17 @@ class MapModel extends BasicModel {
}
/**
* checks weather a user has access to this map or not
* @param $user
* checks weather a character has access to this map or not
* @param CharacterModel $characterModel
* @return bool
*/
public function hasAccess($user){
public function hasAccess(CharacterModel $characterModel){
$hasAccess = false;
if(
!$this->dry() &&
$user instanceof UserModel
){
if( !$this->dry() ){
// get all maps the user has access to
// this includes corporation and alliance maps
$maps = $user->getMaps();
$maps = $characterModel->getMaps();
foreach($maps as $map){
if($map->id === $this->id){
$hasAccess = true;
@@ -409,54 +449,56 @@ class MapModel extends BasicModel {
}
/**
* get all user models that have access to this map
* note: This function is just for "private" maps
* @return array
*/
public function getUsers(){
$users = [];
if($this->isPrivate()){
$this->filter('mapUsers', ['active = ?', 1]);
if($this->mapUsers){
foreach($this->mapUsers as $mapUser){
$users[] = $mapUser->userId;
}
}
}
return $users;
}
/**
* get all character models that are currently online "viewing" this map
* @return array
* get all (private) characters for this map
* @return CharacterModel array
*/
private function getCharacters(){
$characters = [];
$this->filter('mapCharacters', ['active = ?', 1]);
if($this->mapCharacters){
foreach($this->mapCharacters as $characterMapModel){
$characters[] = $characterMapModel->characterId;
}
}
return $characters;
}
/**
* get all character models that are currently online "viewing" this map
* @return CharacterModel[]
*/
private function getActiveCharacters(){
$characters = [];
if($this->isPrivate()){
$users = $this->getUsers();
$activeCharacters = $this->getCharacters();
foreach($users as $user){
// get all active character logs for a user
$tempActiveUserCharacters = $user->getActiveUserCharacters();
foreach($tempActiveUserCharacters as $tempActiveUserCharacter){
$characters[] = $tempActiveUserCharacter;
}
// add active character for each user
foreach($activeCharacters as $activeCharacter){
/**
* @var UserModel $user
*/
$characters[] = $activeCharacter;
}
}elseif($this->isCorporation()){
$corporations = $this->getCorporations();
foreach($corporations as $corporation){
/**
* @var CorporationModel $corporation
*/
$characters = array_merge($characters, $corporation->getCharacters());
}
}elseif($this->isAlliance()){
$alliances = $this->getAlliances();
foreach($alliances as $alliance){
/**
* @var AllianceModel $alliance
*/
$characters = array_merge($characters, $alliance->getCharacters());
}
}
@@ -476,7 +518,7 @@ class MapModel extends BasicModel {
if(is_null($charactersData)){
$charactersData = [];
$characters = $this->getCharacters();
$characters = $this->getActiveCharacters();
foreach($characters as $character){
$charactersData[] = $character->getData(true);
@@ -484,7 +526,7 @@ class MapModel extends BasicModel {
// cache active characters (if found)
if(!empty($charactersData)){
$this->updateCacheData($charactersData, 'CHARACTERS', 10);
$this->updateCacheData($charactersData, 'CHARACTERS', 5);
}
}
@@ -531,19 +573,15 @@ class MapModel extends BasicModel {
return $alliances;
}
/**
* delete this map and all dependencies
* @param $accessObject
* @param CharacterModel $characterModel
*/
public function delete($accessObject){
if(!$this->dry()){
// check if editor has access
if($this->hasAccess($accessObject)){
public function delete(CharacterModel $characterModel){
if( !$this->dry() ){
// check if character has access
if($this->hasAccess($characterModel)){
// all map related tables will be deleted on cascade
// delete map
$this->erase();
}
}
@@ -656,9 +694,9 @@ class MapModel extends BasicModel {
if( $mapModel->isPrivate() ){
$mapModel->clearAccess(['corporation', 'alliance']);
}elseif( $mapModel->isCorporation() ){
$mapModel->clearAccess(['user', 'alliance']);
$mapModel->clearAccess(['character', 'alliance']);
}elseif( $mapModel->isAlliance() ){
$mapModel->clearAccess(['user', 'corporation']);
$mapModel->clearAccess(['character', 'corporation']);
}
}

View File

@@ -1,49 +0,0 @@
<?php
/**
* Created by PhpStorm.
* User: exodus4d
* Date: 29.08.15
* Time: 11:57
*/
namespace Model;
use DB\SQL\Schema;
class RegistrationKeyModel extends BasicModel {
protected $table = 'registration_key';
protected $fieldConf = [
'active' => [
'type' => Schema::DT_BOOL,
'nullable' => false,
'default' => 1,
'index' => true
],
'ip' => [
'type' => Schema::DT_VARCHAR128,
'nullable' => false,
'default' => ''
],
'used' => [
'type' => Schema::DT_BOOL,
'nullable' => false,
'default' => 0
],
'email' => [
'type' => Schema::DT_VARCHAR128,
'nullable' => false,
'default' => '',
'index' => true
],
'registrationKey' => [
'type' => Schema::DT_VARCHAR128,
'nullable' => false,
'default' => '',
'index' => true,
'unique' => true
]
];
}

View File

@@ -177,18 +177,18 @@ class SystemModel extends BasicModel {
}else{
// special array data
if($key == 'constellation'){
$this->constellationId = $value['id'];
$this->constellationId = (int)$value['id'];
$this->constellation = $value['name'];
}elseif($key == 'region'){
$this->regionId = $value['id'];
$this->regionId = (int)$value['id'];
$this->region = $value['name'];
}elseif($key == 'type'){
$this->typeId = $value['id'];
$this->typeId = (int)$value['id'];
}elseif($key == 'status'){
$this->statusId = $value['id'];
$this->statusId = (int)$value['id'];
}elseif($key == 'position'){
$this->posX = $value['x'];
$this->posY = $value['y'];
$this->posX = (int)$value['x'];
$this->posY = (int)$value['y'];
}
}
}
@@ -242,16 +242,17 @@ class SystemModel extends BasicModel {
$systemData->position->x = $this->posX;
$systemData->position->y = $this->posY;
if($this->createdCharacterId){
$systemData->created = (object) [];
$systemData->created = (object) [];
$systemData->created->created = strtotime($this->created);
if( is_object($this->createdCharacterId) ){
$systemData->created->character = $this->createdCharacterId->getData();
$systemData->created->created = strtotime($this->created);
}
if($this->updatedCharacterId){
$systemData->updated = (object) [];
$systemData->updated = (object) [];
$systemData->updated->updated = strtotime($this->updated);
if( is_object($this->updatedCharacterId) ){
$systemData->updated->character = $this->updatedCharacterId->getData();
$systemData->updated->updated = strtotime($this->updated);
}
// max caching time for a system
@@ -313,23 +314,22 @@ class SystemModel extends BasicModel {
/**
* check object for model access
* @param $accessObject
* @return bool
* @param CharacterModel $characterModel
* @return mixed
*/
public function hasAccess($accessObject){
return $this->mapId->hasAccess($accessObject);
public function hasAccess(CharacterModel $characterModel){
return $this->mapId->hasAccess($characterModel);
}
/**
* delete a system from a map
* hint: signatures and connections will be deleted on cascade
* @param $accessObject
* @param CharacterModel $characterModel
*/
public function delete($accessObject){
if(! $this->dry()){
// check if user has access
if($this->hasAccess($accessObject)){
public function delete(CharacterModel $characterModel){
if( !$this->dry() ){
// check if character has access
if($this->hasAccess($characterModel)){
$this->erase();
}
}
@@ -367,14 +367,14 @@ class SystemModel extends BasicModel {
/**
* get Signature by id and check for access
* @param $accessObject
* @param CharacterModel $characterModel
* @param $id
* @return bool|null
*/
public function getSignatureById($accessObject, $id){
public function getSignatureById(CharacterModel $characterModel, $id){
$signature = null;
if($this->hasAccess($accessObject)){
if($this->hasAccess($characterModel)){
$this->filter('signatures', ['active = ? AND id = ?', 1, $id]);
if($this->signatures){
$signature = reset( $this->signatures );
@@ -386,14 +386,14 @@ class SystemModel extends BasicModel {
/**
* get a signature by its "unique" 3-digit name
* @param $accessObject
* @param CharacterModel $characterModel
* @param $name
* @return mixed|null
*/
public function getSignatureByName($accessObject, $name){
public function getSignatureByName(CharacterModel $characterModel, $name){
$signature = null;
if($this->hasAccess($accessObject)){
if($this->hasAccess($characterModel)){
$this->filter('signatures', ['active = ? AND name = ?', 1, $name]);
if($this->signatures){
$signature = reset( $this->signatures );

View File

@@ -35,13 +35,13 @@ class SystemSignatureModel extends BasicModel {
'groupId' => [
'type' => Schema::DT_INT,
'nullable' => false,
'default' => 1,
'default' => 0,
'index' => true,
],
'typeId' => [
'type' => Schema::DT_INT,
'nullable' => false,
'default' => 1,
'default' => 0,
'index' => true,
],
'name' => [
@@ -103,26 +103,29 @@ class SystemSignatureModel extends BasicModel {
}
/**
* get signature data as array
* @return array
* get signature data
* @return \stdClass
*/
public function getData(){
$signatureData = [
'id' => $this->id,
'groupId' => $this->groupId,
'typeId' => $this->typeId,
'name' => $this->name,
'description' => $this->description,
'created' => [
'character' => $this->createdCharacterId->getData(),
'created' => strtotime($this->created)
],
'updated' => [
'character' => $this->updatedCharacterId->getData(),
'updated' => strtotime($this->updated)
]
];
$signatureData = (object) [];
$signatureData->id = $this->id;
$signatureData->groupId = $this->groupId;
$signatureData->typeId = $this->typeId;
$signatureData->name = $this->name;
$signatureData->description = $this->description;
$signatureData->created = (object) [];
$signatureData->created->created = strtotime($this->created);
if( is_object($this->createdCharacterId) ){
$signatureData->created->character = $this->createdCharacterId->getData();
}
$signatureData->updated = (object) [];
$signatureData->updated->updated = strtotime($this->updated);
if( is_object($this->updatedCharacterId) ){
$signatureData->updated->character = $this->updatedCharacterId->getData();
}
return $signatureData;
}
@@ -151,18 +154,21 @@ class SystemSignatureModel extends BasicModel {
/**
* check object for model access
* @param $accessObject
* @param CharacterModel $characterModel
* @return bool
*/
public function hasAccess($accessObject){
return $this->systemId->hasAccess($accessObject);
public function hasAccess(CharacterModel $characterModel){
return $this->systemId->hasAccess($characterModel);
}
public function delete($accessObject){
if(!$this->dry()){
// check if editor has access
if($this->hasAccess($accessObject)){
/**
* delete signature
* @param CharacterModel $characterModel
*/
public function delete(CharacterModel $characterModel){
if( !$this->dry() ){
// check if character has access
if($this->hasAccess($characterModel)){
$this->erase();
}
}

View File

@@ -1,152 +0,0 @@
<?php
/**
* Created by PhpStorm.
* User: exodus4d
* Date: 28.03.15
* Time: 16:41
*/
namespace Model;
use DB\SQL\Schema;
use Controller;
class UserApiModel extends BasicModel {
protected $table = 'user_api';
protected $fieldConf = [
'active' => [
'type' => Schema::DT_BOOL,
'nullable' => false,
'default' => 1,
'index' => true
],
'userId' => [
'type' => Schema::DT_INT,
'index' => true,
'belongs-to-one' => 'Model\UserModel',
'constraint' => [
[
'table' => 'user',
'on-delete' => 'CASCADE'
]
]
],
'keyId' => [
'type' => Schema::DT_INT,
'index' => true,
'unique' => true
],
'vCode' => [
'type' => Schema::DT_VARCHAR128,
'nullable' => false,
'default' => '',
],
'userCharacters' => [
'has-many' => ['Model\UserCharacterModel', 'apiId']
],
];
/**
* get all data for this api
* @return object
*/
public function getData(){
$apiData = (object) [];
$apiData->keyId = $this->keyId;
$apiData->vCode = $this->vCode;
return $apiData;
}
/**
* @return int
*/
public function updateCharacters(){
$apiController = new Controller\CcpApiController();
return $apiController->updateCharacters($this);
}
/**
* get all characters for this API
* @return array|mixed
*/
public function getUserCharacters(){
$this->filter('userCharacters', ['active = ?', 1]);
$userCharacters = [];
if($this->userCharacters){
$userCharacters = $this->userCharacters;
}
return $userCharacters;
}
/**
* search for a user character model by a characterId
* @param $characterId
* @return null
*/
public function getUserCharacterById($characterId){
$userCharacters = $this->getUserCharacters();
$returnUserCharacter = null;
foreach($userCharacters as $userCharacter){
if($userCharacter->characterId->id == $characterId){
$returnUserCharacter = $userCharacter;
break;
}
}
return $returnUserCharacter;
}
/**
* check if this api model has a main character
* @return bool
*/
public function hasMainCharacter(){
$hasMain = false;
$characters = $this->getUserCharacters();
foreach($characters as $character){
if($character->isMain()){
$hasMain = true;
break;
}
}
return $hasMain;
}
/**
* get the user object for this model
* @return mixed
*/
public function getUser(){
return $this->userId;
}
/**
* delete this api model
*/
public function delete(){
// check if this api model had a main character
$user = $this->userId;
$setNewMain = false;
if($this->hasMainCharacter()){
$setNewMain = true;
}
$this->erase();
if($setNewMain){
$user->setMainCharacterId();
}
}
}

View File

@@ -32,20 +32,10 @@ class UserCharacterModel extends BasicModel {
]
]
],
'apiId' => [
'type' => Schema::DT_INT,
'index' => true,
'belongs-to-one' => 'Model\UserApiModel',
'constraint' => [
[
'table' => 'user_api',
'on-delete' => 'CASCADE'
]
]
],
'characterId' => [
'type' => Schema::DT_INT,
'index' => true,
'unique' => true,
'belongs-to-one' => 'Model\CharacterModel',
'constraint' => [
[
@@ -53,12 +43,6 @@ class UserCharacterModel extends BasicModel {
'on-delete' => 'CASCADE'
]
]
],
'isMain' => [
'type' => Schema::DT_BOOLEAN,
'nullable' => false,
'default' => 0,
'index' => true
]
];
@@ -67,9 +51,7 @@ class UserCharacterModel extends BasicModel {
* @param $characterData
*/
public function setData($characterData){
foreach((array)$characterData as $key => $value){
if(!is_array($value)){
if($this->exists($key)){
$this->$key = $value;
@@ -79,52 +61,19 @@ class UserCharacterModel extends BasicModel {
}
/**
* get all character data
* @param $addCharacterLogData
* @return array
*/
public function getData($addCharacterLogData = false){
// get characterModel
$characterModel = $this->getCharacter();
// get static character data
$characterData = $characterModel->getData($addCharacterLogData);
// add user specific character data
$characterData->isMain = $this->isMain;
// check for corporation
if( is_object( $characterModel->corporationId ) ){
$characterData->corporation = $characterModel->corporationId->getData();
}
// check for alliance
if( is_object( $characterModel->allianceId ) ){
$characterData->alliance = $characterModel->allianceId->getData();
}
return $characterData;
}
/**
* check if this character is Main character or not
* event "Hook"
* -> remove user if there are no other characters bound to this user
* @param $self
* @return bool
*/
public function isMain(){
$isMain = false;
if($this->isMain == 1){
$isMain = true;
public function aftereraseEvent($self){
if(
is_object($self->userId) &&
is_null($self->userId->userCharacters)
){
$self->userId->erase();
}
return $isMain;
}
/**
* set this character as main character
*/
public function setMain($value = 0){
$this->isMain = $value;
return true;
}
/**
@@ -146,10 +95,7 @@ class UserCharacterModel extends BasicModel {
$status = parent::setup($db,$table,$fields);
if($status === true){
$status = parent::setMultiColumnIndex(['userId', 'apiId', 'characterId'], true);
if($status === true){
$status = parent::setMultiColumnIndex(['userId', 'apiId']);
}
$status = parent::setMultiColumnIndex(['userId', 'characterId'], true);
}
return $status;

View File

@@ -10,6 +10,7 @@ namespace Model;
use DB\SQL\Schema;
use Controller;
use Controller\Api;
use Exception;
class UserModel extends BasicModel {
@@ -17,10 +18,6 @@ class UserModel extends BasicModel {
protected $table = 'user';
protected $fieldConf = [
'lastLogin' => [
'type' => Schema::DT_TIMESTAMP,
'index' => true
],
'active' => [
'type' => Schema::DT_BOOL,
'nullable' => false,
@@ -31,61 +28,32 @@ class UserModel extends BasicModel {
'type' => Schema::DT_VARCHAR128,
'nullable' => false,
'default' => '',
'index' => true,
'unique' => true
'index' => true
],
'email' => [
'type' => Schema::DT_VARCHAR128,
'nullable' => false,
'default' => '',
'index' => true,
'unique' => true
],
'password' => [
'type' => Schema::DT_VARCHAR128,
'nullable' => false,
'default' => ''
],
'shared' => [
'type' => Schema::DT_BOOL,
'nullable' => false,
'default' => 0
],
'apis' => [
'has-many' => ['Model\UserApiModel', 'userId']
],
'userCharacters' => [
'has-many' => ['Model\UserCharacterModel', 'userId']
],
'userMaps' => [
'has-many' => ['Model\UserMapModel', 'userId']
]
];
protected $validate = [
'name' => [
'length' => [
'min' => 5,
'max' => 25
]
],
'email' => [
'length' => [
'min' => 5
]
],
'password' => [
'length' => [
'min' => 6
'min' => 3,
'max' => 50
]
]
];
/**
* get all data for this user
* ! caution ! this function returns sensitive data!
* -> ! caution ! this function returns sensitive data! (e.g. email,..)
* -> user getSimpleData() for faster performance and public user data
* @return object
* @return \stdClass
*/
public function getData(){
@@ -95,27 +63,19 @@ class UserModel extends BasicModel {
// add sensitive user data
$userData->email = $this->email;
// user shared info
$userData->shared = $this->shared;
// api data
$APIs = $this->getAPIs();
foreach($APIs as $api){
$userData->api[] = $api->getData();
}
// all chars
$userData->characters = [];
$userCharacters = $this->getUserCharacters();
foreach($userCharacters as $userCharacter){
$userData->characters[] = $userCharacter->getData();
$characters = $this->getCharacters();
foreach($characters as $character){
/**
* @var $character CharacterModel
*/
$userData->characters[] = $character->getData();
}
// set active character with log data
$activeUserCharacter = $this->getActiveUserCharacter();
if($activeUserCharacter){
$userData->character = $activeUserCharacter->getData(true);
}
// get active character with log data
$activeCharacter = $this->getActiveCharacter();
$userData->character = $activeCharacter->getData(true);
return $userData;
}
@@ -123,7 +83,7 @@ class UserModel extends BasicModel {
/**
* get public user data
* - check out getData() for all user data
* @return object
* @return \stdClass
*/
public function getSimpleData(){
$userData = (object) [];
@@ -135,31 +95,21 @@ class UserModel extends BasicModel {
/**
* validate and set a email address for this user
* @param $email
* @return mixed
* -> empty email is allowed!
* @param string $email
* @return string
*/
public function set_email($email){
if (\Audit::instance()->email($email) == false) {
if (
!empty($email) &&
\Audit::instance()->email($email) == false
) {
// no valid email address
$this->throwValidationError('email');
}
return $email;
}
/**
* set a password hash for this user
* @param $password
* @return FALSE|string
*/
public function set_password($password){
if(strlen($password) < 6){
$this->throwValidationError('password');
}
$salt = uniqid('', true);
return \Bcrypt::instance()->hash($password, $salt);
}
/**
* check if new user registration is allowed
* @return bool
@@ -191,366 +141,89 @@ class UserModel extends BasicModel {
return $this->getByForeignKey('name', $name, [], 0);
}
/**
* verify a user by his password
* @param $password
* @return bool
*/
public function verify($password){
$valid = false;
if(! $this->dry()){
$valid = (bool) \Bcrypt::instance()->verify($password, $this->password);
}
return $valid;
}
/**
* get all accessible map models for this user
* @return array
*/
public function getMaps(){
$f3 = self::getF3();
$this->filter(
'userMaps',
['active = ?', 1],
['order' => 'created']
);
$maps = [];
if($this->userMaps){
$mapCountPrivate = 0;
foreach($this->userMaps as $userMap){
if(
$userMap->mapId->isActive() &&
$mapCountPrivate < $f3->get('PATHFINDER.MAX_MAPS_PRIVATE')
){
$maps[] = $userMap->mapId;
$mapCountPrivate++;
}
}
}
$activeUserCharacter = $this->getActiveUserCharacter();
if($activeUserCharacter){
$character = $activeUserCharacter->getCharacter();
$corporation = $character->getCorporation();
$alliance = $character->getAlliance();
if($alliance){
$allianceMaps = $alliance->getMaps();
$maps = array_merge($maps, $allianceMaps);
}
if($corporation){
$corporationMaps = $corporation->getMaps();
$maps = array_merge($maps, $corporationMaps);
}
}
return $maps;
}
/**
* get mapModel by id and check if user has access
* @param $mapId
* @return null
* @throws \Exception
*/
public function getMap($mapId){
$map = self::getNew('MapModel');
$map->getById( (int)$mapId );
$returnMap = null;
if($map->hasAccess($this)){
$returnMap = $map;
}
return $returnMap;
}
/**
* get all API models for this user
* @return array|mixed
*/
public function getAPIs(){
$this->filter('apis', ['active = ?', 1]);
$apis = [];
if($this->apis){
$apis = $this->apis;
}
return $apis;
}
/**
* set main character ID for this user.
* If id does not match with his API chars -> select "random" main character
* @param int $characterId
*/
public function setMainCharacterId($characterId = 0){
if(is_int($characterId)){
$userCharacters = $this->getUserCharacters();
if(count($userCharacters) > 0){
$mainSet = false;
foreach($userCharacters as $userCharacter){
if($characterId == $userCharacter->getCharacter()->id){
$mainSet = true;
$userCharacter->setMain(1);
}else{
$userCharacter->setMain(0);
}
$userCharacter->save();
}
// set first character as "main"
if( !$mainSet ){
$userCharacter = reset($userCharacters);
$userCharacter->setMain(1);
$userCharacter->save();
}
}
}
}
/**
* get all userCharacters models for a user
* characters will be checked/updated on login by CCP API call
* @return array|mixed
* @return UserCharacterModel[]
*/
public function getUserCharacters(){
$this->filter('userCharacters', ['active = ?', 1]);
$this->filter('apis', ['active = ?', 1]);
// if a user has multiple API keys saved for ONE character,
// skip double characters!
$userCharacters = [];
if($this->apis){
$this->apis->rewind();
while($this->apis->valid()){
$this->apis->current()->filter('userCharacters', ['active = ?', 1]);
if($this->apis->current()->userCharacters){
$this->apis->current()->userCharacters->rewind();
while($this->apis->current()->userCharacters->valid()){
$tempCharacterId = $this->apis->current()->userCharacters->current()->characterId->get('id');
if( !isset($userCharacters[ $tempCharacterId ]) ){
$userCharacters[ $tempCharacterId ] = $this->apis->current()->userCharacters->current();
}
$this->apis->current()->userCharacters->next();
}
}
$this->apis->next();
}
if($this->userCharacters){
$userCharacters = $this->userCharacters;
}
return $userCharacters;
}
/**
* Get the main user character for this user
* @return null
* get the current active character for this user
* -> EITHER - the current active one for the current user
* -> OR - get the first active one
* @return null|CharacterModel
*/
public function getMainUserCharacter(){
$mainUserCharacter = null;
public function getActiveCharacter(){
$activeCharacter = null;
$controller = new Controller\Controller();
$currentActiveCharacter = $controller->getCharacter();
if(
!is_null($currentActiveCharacter) &&
$currentActiveCharacter->getUser()->_id === $this->id
){
$activeCharacter = &$currentActiveCharacter;
}else{
// set "first" found as active for this user
if($activeCharacters = $this->getActiveCharacters()){
$activeCharacter = $activeCharacters[0];
}
}
return $activeCharacter;
}
/**
* get all characters for this user
* @return CharacterModel[]
*/
public function getCharacters(){
$userCharacters = $this->getUserCharacters();
$characters = [];
foreach($userCharacters as $userCharacter){
if($userCharacter->isMain()){
$mainUserCharacter = $userCharacter;
break;
/**
* @var $userCharacter UserCharacterModel
*/
if( $currentCharacter = $userCharacter->getCharacter() ){
// check if userCharacter has a valid character
// -> this should never fail!
$characters[] = $currentCharacter;
}
}
return $mainUserCharacter;
return $characters;
}
/**
* get the active user character for this user
* either there is an active Character (IGB) or the character labeled as "main"
* @return null
*/
public function getActiveUserCharacter(){
$activeUserCharacter = null;
$headerData = Controller\CcpApiController::getIGBHeaderData();
// check if IGB Data is available
if( !empty($headerData->values) ){
// search for the active character by IGB Header Data
$this->filter('userCharacters',
[
'active = :active AND characterId = :characterId',
':active' => 1,
':characterId' => intval($headerData->values['charid'])
],
['limit' => 1]
);
if($this->userCharacters){
// check if userCharacter has active log
$userCharacter = current($this->userCharacters);
if( $userCharacter->getCharacter()->getLog() ){
$activeUserCharacter = $userCharacter;
}
}
}
// if no active character is found
// e.g. not online in IGB
// -> get main Character
if(is_null($activeUserCharacter)){
$activeUserCharacter = $this->getMainUserCharacter();
}
return $activeUserCharacter;
}
/**
* get all active user characters (with log entry)
* get all active characters (with log entry)
* hint: a user can have multiple active characters
* @return array
* @return CharacterModel[]
*/
public function getActiveUserCharacters(){
public function getActiveCharacters(){
$userCharacters = $this->getUserCharacters();
$activeUserCharacters = [];
$activeCharacters = [];
foreach($userCharacters as $userCharacter){
$characterLog = $userCharacter->getCharacter()->getLog();
if($characterLog){
$activeUserCharacters[] = $userCharacter;
/**
* @var $userCharacter UserCharacterModel
*/
$characterModel = $userCharacter->getCharacter();
if($characterLog = $characterModel->getLog()){
$activeCharacters[] = $characterModel;
}
}
return $activeUserCharacters;
return $activeCharacters;
}
/**
* update/check API information.
* request API information from CCP
*/
public function updateApiData(){
$this->filter('apis', ['active = ?', 1]);
if($this->apis){
$this->apis->rewind();
while($this->apis->valid()){
$this->apis->current()->updateCharacters();
$this->apis->next();
}
}
}
/**
* updated the character log entry for a user character by IGB Header data
* @param int $ttl cache time in seconds
* @throws \Exception
*/
public function updateCharacterLog($ttl = 0){
$headerData = Controller\CcpApiController::getIGBHeaderData();
// check if IGB Data is available
if( !empty($headerData->values) ){
$f3 = self::getF3();
// check if system has changed since the last call
// current location is stored (global) to avoid unnecessary DB calls
$sessionCharacterKey = 'LOGGED.user.character.id_' . $headerData->values['charid'];
if($f3->exists($sessionCharacterKey)){
// cache data exists
$cacheData = $f3->get($sessionCharacterKey);
}else{
// new cache data
$cacheData = [
'systemId' => 0,
'shipId' => 0
];
}
if(
$cacheData['systemId'] != $headerData->values['solarsystemid'] ||
$cacheData['shipId'] != $headerData->values['shiptypeid']
){
$cacheData['systemId'] = (int)$headerData->values['solarsystemid'];
$cacheData['shipId'] = (int)$headerData->values['shiptypeid'];
// character has changed system, or character just logged on
$character = self::getNew('CharacterModel');
$character->getById( (int)$headerData->values['charid'] );
if( $character->dry() ){
// this can happen if a valid user plays the game with a not registered character
// whose API is not registered -> save new character or update character data
$corporationId = array_key_exists('corpid', $headerData->values) ? $headerData->values['corpid'] : null;
$allianceId = array_key_exists('allianceid', $headerData->values) ? $headerData->values['allianceid'] : null;
// check if corp exists
if( !is_null($corporationId) ){
$corporation = self::getNew('CorporationModel');
$corporation->getById( (int)$corporationId );
if( $corporation->dry() ){
$corporation->id = $corporationId;
$corporation->name = $headerData->values['corpname'];
$corporation->save();
}
}
// check if ally exists
if( !is_null($allianceId) ){
$alliance = self::getNew('AllianceModel');
$alliance->getById( (int)$allianceId );
if( $alliance->dry() ){
$alliance->id = $allianceId;
$alliance->name = $headerData->values['alliancename'];
$alliance->save();
}
}
$character->id = (int) $headerData->values['charid'];
$character->name = $headerData->values['charname'];
$character->corporationId = $corporationId;
$character->allianceId = $allianceId;
$character->save();
}
// check if this character has an active log
if( !$characterLog = $character->getLog() ){
$characterLog = self::getNew('CharacterLogModel');
}
// set character log values
$characterLog->characterId = $character;
$characterLog->systemId = (int)$headerData->values['solarsystemid'];
$characterLog->systemName = $headerData->values['solarsystemname'];
$characterLog->shipId = (int)$headerData->values['shiptypeid'];
$characterLog->shipName = $headerData->values['shipname'];
$characterLog->shipTypeName = $headerData->values['shiptypename'];
$characterLog->save();
// clear cache for the characterModel as well
$character->clearCacheData();
// cache character log information
$f3->set($sessionCharacterKey, $cacheData, $ttl);
}
}
}
}

View File

@@ -3,7 +3,7 @@
[PATHFINDER]
NAME = PATHFINDER
; installed version (used for CSS/JS cache busting)
VERSION = v1.0.0RC2
VERSION = v1.0.0
; contact information (DO NOT CHANGE)
CONTACT = https://github.com/exodus4d
; source code (DO NOT CHANGE)
@@ -15,7 +15,7 @@ MAX_MAPS_CORPORATION = 3
MAX_MAPS_ALLIANCE = 3
; Max number of shared entities per map
MAX_SHARED_USER = 10
MAX_SHARED_CHARACTER = 10
MAX_SHARED_CORPORATION = 3
MAX_SHARED_ALLIANCE = 2
@@ -34,6 +34,14 @@ INVITE = 0
; the limit of registration keys. Increase it to hand out more keys
INVITE_LIMIT = 50
[PATHFINDER.LOGIN]
; expire time (in days) for login cookies
COOKIE_EXPIRE = 30
; restrict login to specific corporations/alliances by id (e.g. 1000166,1000080)
CORPORATION =
ALLIANCE =
; View ============================================================================================
[PATHFINDER.VIEW]
; static page templates
@@ -78,7 +86,7 @@ EXECUTION_LIMIT = 50
; map user update ping (ajax) (ms)
[PATHFINDER.TIMER.UPDATE_SERVER_USER_DATA]
DELAY = 5000
EXECUTION_LIMIT = 200
EXECUTION_LIMIT = 300
; update client user data (ms)
[PATHFINDER.TIMER.UPDATE_CLIENT_USER_DATA]
@@ -95,19 +103,14 @@ CONSTELLATION_SYSTEMS = 2592000
[PATHFINDER.LOGFILES]
; just for manuel debug during development
DEBUG = debug
; user login information
; login information
LOGIN = login
; session warnings (suspect)
SESSION_SUSPECT = session_suspect
; account deleted
DELETE_ACCOUNT = delete_account
DELETE_ACCOUNT = account_delete
; API =============================================================================================
[PATHFINDER.API]
; CCP SSO OAuth 2.0
CCP_SSO = https://login.eveonline.com
; CCP CREST API
CCP_CREST = https://crest-tq.eveonline.com
; CCP_CREST = https://api-sisi.testeveonline.com
; CCP XML APIv2
CCP_XML = https://api.eveonline.com
; GitHub Developer API
GIT_HUB = https://api.github.com

View File

@@ -3,16 +3,15 @@
[routes]
; DB setup setup
; IMPORTANT: remove/comment this line after setup/update is finished!
GET @setup: /setup = Controller\Setup->init, 0
GET @setup: /setup [sync] = Controller\Setup->init, 0
; login (index) page
GET @login: / = Controller\AppController->init, 0
GET @login: / [sync] = Controller\AppController->init, 0
; CCP SSO redirect
GET @sso: /sso/@action = Controller\CcpSsoController->@action, 0
GET @sso: /sso/@action [sync] = Controller\Ccp\Sso->@action, 0
; map page
GET @map: /map = Controller\MapController->init, 0
GET @map: /map [sync] = Controller\MapController->init, 0
; ajax wildcard APIs (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
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

Binary file not shown.

View File

@@ -6,7 +6,6 @@ var plumber = require('gulp-plumber');
var gzip = require('gulp-gzip');
var gulpif = require('gulp-if');
var clean = require('gulp-clean');
var critical = require('critical');
var runSequence = require('run-sequence');
var exec = require('child_process').exec;
@@ -91,13 +90,7 @@ gulp.task('jshint', function(){
errorHandler: onError
}))
.pipe(jshint())
.pipe(jshint.reporter(stylish))
.pipe(notify({
icon: path.resolve(__dirname, _src.ICON),
title: 'JSHint',
message: 'Task complete',
onLast: true
}));
.pipe(jshint.reporter(stylish));
// .pipe(jshint.reporter('fail')); // uncomment this line to stop build on error
});
@@ -247,25 +240,6 @@ gulp.task('default', function(tag) {
);
});
/*
// This removes all CSS styles "above the fold" from *.css and inlines them
// -> to improve pagespeed. The remaining (main) css file will be lazy loaded afterwards...
// https://github.com/addyosmani/critical
gulp.task('critical', function (cb) {
critical.generate({
inline: true,
base: './',
src: './public/templates/view/index.html',
dest: './public/templates/view/index-critical.html',
extract: true,
minify: true,
width: 2560,
height: 1440
});
});
*/
/**
* clear all backend (fat free framework) cache files
*/

View File

@@ -4,14 +4,8 @@ $f3 = require('app/lib/base.php');
// load main config
$f3->config('app/config.ini');
// set base dir
$f3->set('BASE', \Controller\Controller::getEnvironmentData('BASE'));
// set debug level (stacktrace)
$f3->set('DEBUG', \Controller\Controller::getEnvironmentData('DEBUG'));
// set debug level (stacktrace)
$f3->set('URL', \Controller\Controller::getEnvironmentData('URL'));
// load environment dependent config
lib\Config::instance();
// initiate cron-jobs
Cron::instance();

View File

@@ -39,13 +39,13 @@ requirejs.config({
xEditable: 'lib/bootstrap-editable.min', // v1.5.1 X-editable - in placed editing
morris: 'lib/morris.min', // v0.5.1 Morris.js - graphs and charts
raphael: 'lib/raphael-min', // v2.1.2 Raphaël - required for morris (dependency)
bootbox: 'lib/bootbox.min', // v4.3.0 Bootbox.js - custom dialogs
bootbox: 'lib/bootbox.min', // v4.4.0 Bootbox.js - custom dialogs - http://bootboxjs.com/
easyPieChart: 'lib/jquery.easypiechart.min', // v2.1.6 Easy Pie Chart - HTML 5 pie charts - http://rendro.github.io/easy-pie-chart/
dragToSelect: 'lib/jquery.dragToSelect', // v1.1 Drag to Select - http://andreaslagerkvist.com/jquery/drag-to-select/
hoverIntent: 'lib/jquery.hoverIntent.minified', // v1.8.0 Hover intention - http://cherne.net/brian/resources/jquery.hoverIntent.html
fullScreen: 'lib/jquery.fullscreen.min', // v0.5.0 Full screen mode - https://github.com/private-face/jquery.fullscreen
select2: 'lib/select2.min', // v4.0.0 Drop Down customization - https://select2.github.io/
validator: 'lib/validator.min', // v0.7.2 Validator for Bootstrap 3 - https://github.com/1000hz/bootstrap-validator
validator: 'lib/validator.min', // v0.10.1 Validator for Bootstrap 3 - https://github.com/1000hz/bootstrap-validator
lazylinepainter: 'lib/jquery.lazylinepainter-1.5.1.min', // v1.5.1 SVG line animation plugin - http://lazylinepainter.info/
blueImpGallery: 'lib/blueimp-gallery', // v2.15.2 Image Gallery - https://github.com/blueimp/Gallery/
blueImpGalleryHelper: 'lib/blueimp-helper', // helper function for Blue Imp Gallery

View File

@@ -6,11 +6,70 @@
* proofed, signature names (copy & paste from scanning window)
*/
define([], function() {
define(['jquery'], function($) {
'use strict';
// system effects
// signature sources
// http://de.sistersprobe.wikia.com/wiki/EVE_Sister_Core_Scanner_Probe_Wiki
// NullSec Relic sites, which can also spawn in C1, C2, C3 wormholes
var nullSecRelicSites = {
10: 'Ruined Angel Crystal Quarry',
11: 'Ruined Angel Monument Site',
12: 'Ruined Angel Science Outpost',
13: 'Ruined Angel Temple Site',
14: 'Ruined Blood Raider Crystal Quarry',
15: 'Ruined Blood Raider Monument Site',
16: 'Ruined Blood Raider Science Outpost',
17: 'Ruined Blood Raider Temple Site',
18: 'Ruined Guristas Crystal Quarry',
19: 'Ruined Guristas Monument Site',
20: 'Ruined Guristas Science Outpost',
21: 'Ruined Guristas Temple Site',
22: 'Ruined Sansha Crystal Quarry',
23: 'Ruined Sansha Monument Site',
24: 'Ruined Sansha Science Outpost',
25: 'Ruined Sansha Temple Site',
26: 'Ruined Serpentis Crystal Quarry',
27: 'Ruined Serpentis Monument Site',
28: 'Ruined Serpentis Science Outpost',
29: 'Ruined Serpentis Temple Site'
};
// NulSec Data sites, which can also spawn in C1, C2, C3 wormholes
var nullSecDataSites = {
10: 'Abandoned Research Complex DA005',
11: 'Abandoned Research Complex DA015',
12: 'Abandoned Research Complex DC007',
13: 'Abandoned Research Complex DC021',
14: 'Abandoned Research Complex DC035',
15: 'Abandoned Research Complex DG003',
16: 'Central Angel Command Center',
17: 'Central Angel Data Mining Site',
18: 'Central Angel Sparking Transmitter',
19: 'Central Angel Survey Site',
20: 'Central Blood Raider Command Center',
21: 'Central Blood Raider Data Mining Site',
22: 'Central Blood Raider Sparking Transmitter',
23: 'Central Blood Raider Survey Site',
24: 'Central Guristas Command Center',
25: 'Central Guristas Data Mining Center',
26: 'Central Guristas Sparking Transmitter',
27: 'Central Guristas Survey Site',
28: 'Central Sansha Command Center',
29: 'Central Sansha Data Mining Site',
30: 'Central Sansha Sparking Transmitter',
31: 'Central Sansha Survey Site',
32: 'Central Serpentis Command Center',
33: 'Central Serpentis Data Mining Site',
34: 'Central Serpentis Sparking Transmitter',
35: 'Central Serpentis Survey Site'
};
// signature types
var signatureTypes = {
1: { // system type (wh)
1: { // C1 (area id)
@@ -20,14 +79,14 @@ define([], function() {
3: 'Phase Catalyst Node',
4: 'The Line'
},
2: { // Relic
1: 'Forgotten Perimeter Coronation Platform',
2: 'Forgotten Perimeter Power Array'
},
3: { // Data
1: 'Unsecured Perimeter Amplifier',
2: 'Unsecured Perimeter Information Center '
},
2: $.extend({}, nullSecRelicSites, { // Relic
1: 'Forgotten Perimeter Coronation Platform', //*
2: 'Forgotten Perimeter Power Array' //*
}),
3: $.extend({}, nullSecDataSites, { // Data
1: 'Unsecured Perimeter Amplifier', //*
2: 'Unsecured Perimeter Information Center' //*
}),
4: { // Gas
1: 'Barren Perimeter Reservoir', //*
2: 'Token Perimeter Reservoir', //*
@@ -62,14 +121,14 @@ define([], function() {
3: 'The Ruins of Enclave Cohort 27',
4: 'Sleeper Data Sanctuary'
},
2: { // Relic
1: 'Forgotten Perimeter Gateway',
2: 'Forgotten Perimeter Habitation Coils'
},
3: { // Data
1: 'Unsecured Perimeter Comms Relay',
2: 'Unsecured Perimeter Transponder Farm '
},
2: $.extend({}, nullSecRelicSites, { // Relic
1: 'Forgotten Perimeter Gateway', //*
2: 'Forgotten Perimeter Habitation Coils' //*
}),
3: $.extend({}, nullSecDataSites, { // Data
1: 'Unsecured Perimeter Comms Relay', //*
2: 'Unsecured Perimeter Transponder Farm' //*
}),
4: { // Gas
1: 'Barren Perimeter Reservoir', //*
2: 'Token Perimeter Reservoir', //*
@@ -100,14 +159,14 @@ define([], function() {
3: 'Solar Cell',
4: 'The Oruze Construct'
},
2: { // Relic
1: 'Forgotten Frontier Quarantine Outpost',
2: 'Forgotten Frontier Recursive Depot'
},
3: { // Data
1: 'Unsecured Frontier Database',
2: 'Unsecured Frontier Receiver'
},
2: $.extend({}, nullSecRelicSites, { // Relic
1: 'Forgotten Frontier Quarantine Outpost', //*
2: 'Forgotten Frontier Recursive Depot' //*
}),
3: $.extend({}, nullSecDataSites, { // Data
1: 'Unsecured Frontier Database', //*
2: 'Unsecured Frontier Receiver' //*
}),
4: { // Gas
1: 'Barren Perimeter Reservoir', //*
2: 'Token Perimeter Reservoir', //*
@@ -192,13 +251,14 @@ define([], function() {
},
4: { // Gas
1: 'Barren Perimeter Reservoir', //*
2: 'Token Perimeter Reservoir', //*
3: 'Sizeable Perimeter Reservoir', //*
4: 'Ordinary Perimeter Reservoir', //*
5: 'Bountiful Frontier Reservoir', //*
6: 'Instrumental Core Reservoir', //*
7: 'Vital Core Reservoir', //*
8: 'Minor Perimeter Reservoir' //*
2: 'Minor Perimeter Reservoir', //*
3: 'Ordinary Perimeter Reservoir', //*
4: 'Sizeable Perimeter Reservoir', //*
5: 'Token Perimeter Reservoir', //*
6: 'Bountiful Frontier Reservoir', //*
7: 'Vast Frontier Reservoir', //*
8: 'Instrumental Core Reservoir', //*
9: 'Vital Core Reservoir' //*
},
5: { // Wormhole
1: 'D792 - HS',
@@ -206,9 +266,16 @@ define([], function() {
3: 'Z142 - 0.0'
},
6: { // ORE
1: 'Ordinary Perimeter Deposit', //*
2: 'Common Perimeter Deposit', //*
3: 'Rarified Core Deposit' //*
1: 'Average Frontier Deposit', //*
2: 'Unexceptional Frontier Deposit', //*
3: 'Uncommon Core Deposit', //*
4: 'Ordinary Perimeter Deposit', //*
5: 'Common Perimeter Deposit', //*
6: 'Exceptional Core Deposit', //*
7: 'Infrequent Core Deposit', //*
8: 'Unusual Core Deposit', //*
9: 'Rarified Core Deposit', //*
10: 'Isolated Core Deposit' //*
},
7: { // Ghost
@@ -230,14 +297,15 @@ define([], function() {
2: 'Unsecured Core Emergence' //*
},
4: { // Gas
1: 'Token Perimeter Reservoir', //*
1: 'Barren Perimeter Reservoir', //*
2: 'Minor Perimeter Reservoir', //*
3: 'Sizeable Perimeter Reservoir', //*
4: 'Ordinary Perimeter Reservoir', //*
5: 'Bountiful Frontier Reservoir', //*
6: 'Vast Frontier Reservoir', //*
7: 'Instrumental Core Reservoir', //*
8: 'Vital Core Reservoir' //*
3: 'Ordinary Perimeter Reservoir', //*
4: 'Sizeable Perimeter Reservoir', //*
5: 'Token Perimeter Reservoir', //*
6: 'Bountiful Frontier Reservoir', //*
7: 'Vast Frontier Reservoir', //*
8: 'Instrumental Core Reservoir', //*
9: 'Vital Core Reservoir' //*
},
5: { // Wormhole
1: 'D792 - HS',

View File

@@ -11,12 +11,13 @@ define(['jquery'], function($) {
img: 'public/img/', // path for images
// user API
getCaptcha: 'api/user/getCaptcha', // ajax URL - get captcha image
getServerStatus: 'api/user/getEveServerStatus', // ajax URL - get EVE-Online server status
sendInviteKey: 'api/user/sendInvite', // ajax URL - send registration key
getCookieCharacterData: 'api/user/getCookieCharacter', // ajax URL - get character data from cookie
logIn: 'api/user/logIn', // ajax URL - login
logOut: 'api/user/logOut', // ajax URL - logout
logout: 'api/user/logout', // ajax URL - logout
deleteLog: 'api/user/deleteLog', // ajax URL - delete character log
saveUserConfig: 'api/user/saveAccount', // ajax URL - saves/update user account
saveSharingConfig: 'api/user/saveSharingConfig', // ajax URL - save "sharing settings" dialog
deleteAccount: 'api/user/deleteAccount', // ajax URL - delete Account data
// access API
searchAccess: 'api/access/search', // ajax URL - search user/corporation/ally by name
@@ -34,6 +35,7 @@ define(['jquery'], function($) {
deleteSystem: 'api/system/delete', // ajax URL - delete system from map
getSystemGraphData: 'api/system/graphData', // ajax URL - get all system graph data
getConstellationData: 'api/system/constellationData', // ajax URL - get system constellation data
setDestination: 'api/system/setDestination', // ajax URL - set destination
// connection API
saveConnection: 'api/connection/save', // ajax URL - save new connection to map
@@ -125,7 +127,7 @@ define(['jquery'], function($) {
},
redGiant: {
class: 'pf-system-effect-redgiant',
name: 'red gaint'
name: 'red giant'
},
pulsar: {
class: 'pf-system-effect-pulsar',
@@ -329,34 +331,58 @@ define(['jquery'], function($) {
// frigate wormholes
frigateWormholes: {
1: { // C1
1: 'E004 - C1',
2: 'L005 - C2',
3: 'Z006 - C3',
4: 'M001 - C4',
5: 'C008 - C5',
6: 'G008 - C6',
7: 'Q003 - 0.0'
},
2: { // C2
1: 'L005 - C2',
2: 'C008 - C5',
3: 'Q003 - 0.0'
1: 'E004 - C1',
2: 'L005 - C2',
3: 'Z006 - C3',
4: 'M001 - C4',
5: 'C008 - C5',
6: 'G008 - C6',
7: 'Q003 - 0.0'
},
3: { // C3
1: 'E004 - C1',
2: 'L005 - C2',
3: 'M001 - C4'
3: 'Z006 - C3',
4: 'M001 - C4',
5: 'C008 - C5',
6: 'G008 - C6',
7: 'Q003 - 0.0'
},
4: { // C4
1: 'L005 - C2',
2: 'G008 - C6',
3: 'Q003 - 0.0'
},
1: 'E004 - C1',
2: 'L005 - C2',
3: 'Z006 - C3',
4: 'M001 - C4',
5: 'C008 - C5',
6: 'G008 - C6',
7: 'Q003 - 0.0'
},
5: { // C5
1: 'E004 - C1',
2: 'L005 - C2',
3: 'Z006 - C3',
4: 'C008 - C5',
5: 'Q003 - 0.0'
4: 'M001 - C4',
5: 'C008 - C5',
6: 'G008 - C6',
7: 'Q003 - 0.0'
},
6: { // C6
1: 'E004 - C1',
2: 'Z006 - C3',
5: 'Q003 - 0.0'
2: 'L005 - C2',
3: 'Z006 - C3',
4: 'M001 - C4',
5: 'C008 - C5',
6: 'G008 - C6',
7: 'Q003 - 0.0'
},
13: { // Shattered Wormholes (some of them are static)
1: 'E004 - C1',
@@ -365,8 +391,7 @@ define(['jquery'], function($) {
4: 'M001 - C4',
5: 'C008 - C5',
6: 'G008 - C6',
7: 'Q003 - C7'
7: 'Q003 - 0.0'
}
},
// incoming wormholes
@@ -382,4 +407,4 @@ define(['jquery'], function($) {
};
return Config;
});
});

View File

@@ -83,6 +83,7 @@ define([
autoWidth: false,
hover: false,
pageLength: 15,
lengthMenu: [[10, 15, 25, 50, 50], [10, 15, 25, 50, 50]],
data: logData, // load cached logs (if available)
language: {
emptyTable: 'No entries',

View File

@@ -27,6 +27,7 @@ define([
splashOverlayClass: 'pf-splash', // class for "splash" overlay
// header
headerId: 'pf-landing-top', // id for header
headerContainerId: 'pf-header-container', // id for header container
logoContainerId: 'pf-logo-container', // id for main header logo container
headHeaderMapId: 'pf-header-map', // id for header image (svg animation)
@@ -43,11 +44,15 @@ define([
navigationLinkLicenseClass: 'pf-navbar-license', // class for "license" trigger link
navigationVersionLinkClass: 'pf-navbar-version-info', // class for "version information"
// login form
loginFormId: 'pf-login-form', // id for login form
loginButtonClass: 'pf-login-button', // class for "login" button(s)
registerButtonClass: 'pf-register-button', // class for "register" button(s)
loginMessageContainerId: 'pf-login-message-container', // id for login form message container
// cookie hint
cookieHintId: 'pf-cookie-hint', // id for "cookie hint" element
// character select
characterSelectionClass: 'pf-character-selection', // class for character panel wrapper
characterRowAnimateClass: 'pf-character-row-animate', // class for character panel row during animation
characterImageWrapperClass: 'pf-character-image-wrapper', // class for image wrapper (animated)
characterImageInfoClass: 'pf-character-info', // class for character info layer (visible on hover)
dynamicMessageContainerClass: 'pf-dynamic-message-container', // class for "dynamic" (JS) message container
// gallery
galleryId: 'pf-gallery', // id for gallery container
@@ -55,101 +60,82 @@ define([
galleryThumbContainerId: 'pf-landing-gallery-thumb-container', // id for gallery thumb images
galleryCarouselId: 'pf-landing-gallery-carousel', // id for "carousel" element
// server panel
serverPanelId: 'pf-server-panel', // id for EVE Online server status panel
// animation
animateElementClass: 'pf-animate-on-visible' // class for elements that will be animated to show
};
/**
* set a cookie
* @param cname
* @param cvalue
* @param exdays
*/
var setCookie = function(cname, cvalue, exdays) {
var d = new Date();
d.setTime(d.getTime() + (exdays*24*60*60*1000));
var expires = 'expires=' + d.toUTCString();
document.cookie = cname + '=' + cvalue + '; ' + expires;
};
/**
* get cookie value by name
* @param cname
* @returns {*}
*/
var getCookie = function(cname) {
var name = cname + '=';
var ca = document.cookie.split(';');
for(var i = 0; i <ca.length; i++) {
var c = ca[i];
while (c.charAt(0) === ' ') {
c = c.substring(1);
}
if (c.indexOf(name) === 0) {
return c.substring(name.length,c.length);
}
}
return '';
};
/**
* set page observer
*/
var setPageObserver = function(){
// login form =====================================================================================
// register buttons ---------------------------------------------
$('.' + config.registerButtonClass).on('click', function(e){
e.preventDefault();
// cookie hint --------------------------------------------------------
if(getCookie('cookie') !== '1'){
// hint not excepted
$('#' + config.cookieHintId).collapse('show');
}
// logout current user (if there e.g. to register a second account)
Util.logout({
ajaxData: {
reroute: 0
}
});
// show register/settings dialog
$.fn.showSettingsDialog({
register: 1,
invite : parseInt( $('body').data('invite') )
});
$('#' + config.cookieHintId + ' .btn-success').on('click', function(){
setCookie('cookie', 1, 365);
});
// login buttons ------------------------------------------------
var loginForm = $('#' + config.loginFormId);
loginForm.on('submit', function(e){
e.preventDefault();
var loginFormMessageContainer = $('#' + config.loginMessageContainerId);
// validate form
loginForm.validator('validate');
// check weather the form is valid
var formValid = loginForm.isValidForm();
if(formValid === true){
// show splash overlay
$('.' + config.splashOverlayClass).showSplashOverlay(function(){
var loginData = {loginData: loginForm.getFormValues()};
$.ajax({
type: 'POST',
url: Init.path.logIn,
data: loginData,
dataType: 'json'
}).done(function(data){
// login error
if(data.error !== undefined){
$('.' + config.splashOverlayClass).hideSplashOverlay();
loginFormMessageContainer.showMessage({title: 'Login failed', text: ' Invalid username or password', type: 'error'});
}else if(data.reroute !== undefined){
window.location = data.reroute;
}
}).fail(function( jqXHR, status, error) {
$('.' + config.splashOverlayClass).hideSplashOverlay();
var reason = status + ' ' + error;
Util.showNotify({title: jqXHR.status + ': login', text: reason, type: 'error'});
// show Form message
loginFormMessageContainer.showMessage({title: 'Login failed', text: ' internal server error', type: 'error'});
});
});
}
});
// releases -----------------------------------------------------
// releases -----------------------------------------------------------
$('.' + config.navigationVersionLinkClass).on('click', function(e){
$.fn.releasesDialog();
});
// manual -------------------------------------------------------
// manual -------------------------------------------------------------
$('.' + config.navigationLinkManualClass).on('click', function(e){
e.preventDefault();
$.fn.showMapManual();
});
// license ------------------------------------------------------
// license ------------------------------------------------------------
$('.' + config.navigationLinkLicenseClass).on('click', function(e){
e.preventDefault();
$.fn.showCreditsDialog(false, true);
});
// tooltips -----------------------------------------------------
// tooltips -----------------------------------------------------------
var mapTooltipOptions = {
toggle: 'tooltip',
container: 'body',
@@ -217,8 +203,6 @@ define([
responseData.error.length > 0
){
form.showFormMessage(responseData.error);
}else{
$('.modal').modal('hide');
Util.showNotify({title: 'Registration Key send', text: 'Check your Mails', type: 'success'});
@@ -288,7 +272,7 @@ define([
return newSlideContent[0];
};
// initialize carousel ------------------------------------------
// initialize carousel ------------------------------------------------
var carousel = new Gallery([
{
title: 'IGB',
@@ -411,29 +395,31 @@ define([
var initYoutube = function(){
$(".youtube").each(function() {
$('.youtube').each(function() {
// Based on the YouTube ID, we can easily find the thumbnail image
$(this).css('background-image', 'url(https://i.ytimg.com/vi/' + this.id + '/sddefault.jpg)');
// Overlay the Play icon to make it look like a video player
$(this).append($('<div/>', {'class': 'play'}));
$(document).delegate('#'+this.id, 'click', function() {
$(document).delegate('#' + this.id, 'click', function() {
// Create an iFrame with autoplay set to true
var iframe_url = "https://www.youtube.com/embed/" + this.id + "?autoplay=1&autohide=1";
if ($(this).data('params')) iframe_url+='&'+$(this).data('params');
var iFrameUrl = 'https://www.youtube.com/embed/' + this.id + '?autoplay=1&autohide=1';
if ( $(this).data('params') ){
iFrameUrl += '&'+$(this).data('params');
}
// The height and width of the iFrame should be the same as parent
var iframe = $('<iframe/>', {
var iFrame = $('<iframe/>', {
frameborder: '0',
src: iframe_url,
src: iFrameUrl,
width: $(this).width(),
height: $(this).height(),
class: 'pricing-big'
});
// Replace the YouTube thumbnail with YouTube HTML5 Player
$(this).replaceWith(iframe);
$(this).replaceWith(iFrame);
});
});
};
@@ -484,6 +470,218 @@ define([
});
};
/**
* get current EVE-Online server status
* -> show "server panel"
*/
var initServerStatus = function(){
$.ajax({
type: 'POST',
url: Init.path.getServerStatus,
dataType: 'json'
}).done(function(responseData, textStatus, request){
if(responseData.hasOwnProperty('status')){
var data = responseData.status;
data.serverPanelId = config.serverPanelId;
var statusClass = '';
switch(data.serviceStatus.eve.toLowerCase()){
case 'online': statusClass = 'txt-color-green'; break;
case 'vip': statusClass = 'txt-color-orange'; break;
case 'offline': statusClass = 'txt-color-redDarker'; break;
}
data.serviceStatus.style = statusClass;
requirejs(['text!templates/ui/server_panel.html', 'mustache'], function(template, Mustache) {
var content = Mustache.render(template, data);
$('#' + config.headerId).prepend(content);
$('#' + config.serverPanelId).velocity('transition.slideLeftBigIn', {
duration: 240
});
});
}
}).fail(handleAjaxErrorResponse);
};
/**
* load character data from cookie information
* -> all validation is done server side!
*/
var initCharacterSelect = function(){
/**
* init panel animation for an element
* @param imageWrapperElement
*/
var initCharacterAnimation = function(imageWrapperElement){
imageWrapperElement.velocity('stop').delay(300).velocity('transition.flipBounceXIn', {
display: 'inline-block',
stagger: 60,
drag: true,
duration: 600
});
// Hover effect for character info layer
imageWrapperElement.hoverIntent(function(e){
var characterInfoElement = $(this).find('.' + config.characterImageInfoClass);
characterInfoElement.velocity('finish').velocity({
width: ['100%', [ 400, 15 ] ]
},{
easing: 'easeInSine'
});
}, function(e){
var characterInfoElement = $(this).find('.' + config.characterImageInfoClass);
characterInfoElement.velocity('finish').velocity({
width: 0
},{
duration: 150,
easing: 'easeInOutSine'
});
});
};
// --------------------------------------------------------------------
/**
* update all character panels -> set CSS class (e.g. after some panels were added/removed,..)
*/
var updateCharacterPanels = function(){
var characterRows = $('.' + config.characterSelectionClass + ' .pf-dynamic-area').parent();
var rowClassIdentifier = ((12 / characterRows.length ) <= 3) ? 3 : (12 / characterRows.length);
$(characterRows).removeClass().addClass('col-sm-' + rowClassIdentifier);
};
// --------------------------------------------------------------------
var removeCharacterPanel = function(panelElement){
$(panelElement).velocity('transition.expandOut', {
duration: 250,
complete: function () {
// lock row for CSS animations while removing...
$(this).parent().addClass(config.characterRowAnimateClass);
$(this).parent().velocity({
width: 0
},{
easing: 'ease',
duration: 300,
complete: function(){
$(this).remove();
// reset column CSS classes for all existing panels
updateCharacterPanels();
}
});
}
});
};
// --------------------------------------------------------------------
// request character data for each character panel
$('.' + config.characterSelectionClass + ' .pf-dynamic-area').each(function(){
var characterElement = $(this);
characterElement.showLoadingAnimation();
var requestData = {
cookie: characterElement.data('cookie')
};
$.ajax({
type: 'POST',
url: Init.path.getCookieCharacterData,
data: requestData,
dataType: 'json',
context: {
href: characterElement.data('href'),
cookieName: requestData.cookie,
characterElement: characterElement
}
}).done(function(responseData, textStatus, request){
var characterElement = this.characterElement;
characterElement.hideLoadingAnimation();
if(
responseData.error &&
responseData.error.length > 0
){
$('.' + config.dynamicMessageContainerClass).showMessage({
type: responseData.error[0].type,
title: 'Character verification failed',
text: responseData.error[0].message
});
}
if(responseData.hasOwnProperty('character')){
var data = {
link: this.href,
cookieName: this.cookieName,
character: responseData.character
};
requirejs(['text!templates/ui/character_panel.html', 'mustache'], function(template, Mustache) {
var content = Mustache.render(template, data);
characterElement.html(content);
// show character panel (animation settings)
initCharacterAnimation(characterElement.find('.' + config.characterImageWrapperClass));
});
}else{
// character data not available -> remove panel
removeCharacterPanel(this.characterElement);
}
}).fail(function( jqXHR, status, error) {
var characterElement = this.characterElement;
characterElement.hideLoadingAnimation();
// character data not available -> remove panel
removeCharacterPanel(this.characterElement);
});
});
};
/**
* default ajax error handler
* -> show user notifications
* @param jqXHR
* @param status
* @param error
*/
var handleAjaxErrorResponse = function(jqXHR, status, error){
var type = status;
var title = 'Status ' + jqXHR.status + ': ' + error;
var message = '';
if(jqXHR.responseText){
var errorObj = $.parseJSON(jqXHR.responseText);
if(
errorObj.error &&
errorObj.error.length > 0
){
for(var i = 0; i < errorObj.error.length; i++){
var errorData = errorObj.error[i];
type = errorData.type;
title = 'Status ' + errorData.code + ': ' + errorData.status;
message = errorData.message;
Util.showNotify({title: title, text: message, type: type});
}
}
}else{
Util.showNotify({title: title, text: message, type: type});
}
};
/**
* main init "landing" page
*/
@@ -538,6 +736,11 @@ define([
// hide splash loading animation
$('.' + config.splashOverlayClass).hideSplashOverlay();
// init server status information
initServerStatus();
initCharacterSelect();
// init carousel
initCarousel();

View File

@@ -602,12 +602,12 @@ define([
system.attr('data-mapid', parseInt(mapContainer.data('id')));
// locked system
if( Boolean( system.data( 'locked') ) !== Boolean( parseInt( data.locked ) )){
if( Boolean( system.data( 'locked') ) !== data.locked ){
system.toggleLockSystem(false, {hideNotification: true, hideCounter: true, map: map});
}
// rally system
if( Boolean( system.data( 'rally') ) !== Boolean( parseInt( data.rally ) )){
if( Boolean( system.data( 'rally') ) !== data.rally ){
system.toggleRallyPoint(false, {hideNotification: true, hideCounter: true});
}
@@ -869,6 +869,7 @@ define([
// no visual effects in IGB (glitches)
if(
systemElements.length === 0 ||
systemElements.length > 20 ||
endpointElements.length === 0 ||
CCP.isInGameBrowser() === true
){
@@ -1156,7 +1157,7 @@ define([
placement: 'top',
onblur: 'submit',
container: 'body',
toggle: 'manual', // is triggered manually on dblclick
toggle: 'manual', // is triggered manually on dblClick
showbuttons: false
});
@@ -1223,7 +1224,7 @@ define([
/**
* connect two systems
* @param mapConfig
* @param map
* @param connectionData
* @returns new connection
*/
@@ -1241,10 +1242,12 @@ define([
var connection = map.connect({
source: config.systemIdPrefix + mapId + '-' + connectionData.source,
target: config.systemIdPrefix + mapId + '-' + connectionData.target,
/*
parameters: {
connectionId: connectionId,
updated: connectionData.updated
},
*/
type: null
/* experimental (straight connections)
anchors: [
@@ -1254,20 +1257,33 @@ define([
*/
});
// add connection types -----------------------------------------------------
if(connectionData.type){
for(var i = 0; i < connectionData.type.length; i++){
connection.addType(connectionData.type[i]);
}
}
// add connection scope -----------------------------------------------------
// connection have the default map Scope scope
var scope = map.Defaults.Scope;
if(connectionData.scope){
scope = connectionData.scope;
// check if connection is valid (e.g. source/target exist
if( connection instanceof jsPlumb.Connection ){
// set connection parameters
// they should persist even through connection type change (e.g. wh -> stargate,..)
// therefore they shoule be part of the connection not of the connector
connection.setParameters({
connectionId: connectionId,
updated: connectionData.updated
});
// add connection types -----------------------------------------------------
if(connectionData.type){
for(var i = 0; i < connectionData.type.length; i++){
connection.addType(connectionData.type[i]);
}
}
// add connection scope -----------------------------------------------------
// connection have the default map Scope scope
var scope = map.Defaults.Scope;
if(connectionData.scope){
scope = connectionData.scope;
}
setConnectionScope(connection, scope);
}
setConnectionScope(connection, scope);
// set Observer for new Connection -> is automatically set
@@ -1280,56 +1296,65 @@ define([
*/
var saveConnection = function(connection){
var map = connection._jsPlumb.instance;
var mapContainer = $( map.getContainer() );
mapContainer.getMapOverlay('timer').startMapUpdateCounter();
if( connection instanceof jsPlumb.Connection ){
var mapId = mapContainer.data('id');
var connectionData = getDataByConnection(connection);
var map = connection._jsPlumb.instance;
var mapContainer = $( map.getContainer() );
mapContainer.getMapOverlay('timer').startMapUpdateCounter();
var requestData = {
mapData: {
id: mapId
},
connectionData: connectionData
};
var mapId = mapContainer.data('id');
var connectionData = getDataByConnection(connection);
$.ajax({
type: 'POST',
url: Init.path.saveConnection,
data: requestData,
dataType: 'json',
//context: connection
context: {
connection: connection,
mapId: mapId
}
}).done(function(newConnectionData){
var requestData = {
mapData: {
id: mapId
},
connectionData: connectionData
};
// update connection data e.g. "scope" has auto detected
connection = updateConnection(this.connection, connectionData, newConnectionData);
$.ajax({
type: 'POST',
url: Init.path.saveConnection,
data: requestData,
dataType: 'json',
//context: connection
context: {
connection: connection,
map: map,
mapId: mapId
}
}).done(function(newConnectionData){
// new connection should be cached immediately!
updateConnectionCache(this.mapId, connection);
if( !$.isEmptyObject(newConnectionData) ){
// update connection data e.g. "scope" has auto detected
connection = updateConnection(this.connection, connectionData, newConnectionData);
// connection scope
var scope = Util.getScopeInfoForConnection(newConnectionData.scope, 'label');
// new connection should be cached immediately!
updateConnectionCache(this.mapId, connection);
var title = 'New connection established';
if(connectionData.id > 0){
title = 'Connection switched';
}
// connection scope
var scope = Util.getScopeInfoForConnection(newConnectionData.scope, 'label');
Util.showNotify({title: title, text: 'Scope: ' + scope, type: 'success'});
}).fail(function( jqXHR, status, error) {
var title = 'New connection established';
if(connectionData.id > 0){
title = 'Connection switched';
}
// remove this connection from map
this._jsPlumb.instance.detach(this);
Util.showNotify({title: title, text: 'Scope: ' + scope, type: 'success'});
}else{
// some save errors
this.map.detach(this.connection, {fireEvent: false});
}
var reason = status + ' ' + error;
Util.showNotify({title: jqXHR.status + ': saveConnection', text: reason, type: 'warning'});
$(document).setProgramStatus('problem');
});
}).fail(function( jqXHR, status, error) {
// remove this connection from map
this.map.detach(this.connection, {fireEvent: false});
var reason = status + ' ' + error;
Util.showNotify({title: jqXHR.status + ': saveConnection', text: reason, type: 'warning'});
$(document).setProgramStatus('problem');
});
}
};
/**
@@ -1658,12 +1683,15 @@ define([
{icon: 'fa-lock', action: 'lock_system', text: 'lock system'},
{icon: 'fa-users', action: 'set_rally', text: 'set rally point'},
{icon: 'fa-tags', text: 'set status', subitems: systemStatus},
{icon: 'fa-reply fa-rotate-180', text: 'waypoints', subitems: [
{subIcon: 'fa-flag-checkered', subAction: 'set_destination', subText: 'set destination'},
{subDivider: true, action: ''},
{subIcon: 'fa-step-backward', subAction: 'add_first_waypoint', subText: 'add new [start]'},
{subIcon: 'fa-step-forward', subAction: 'add_last_waypoint', subText: 'add new [end]'}
]},
{divider: true, action: 'ingame'},
{icon: 'fa-reply fa-rotate-180', action: 'ingame', text: 'ingame actions', subitems: [
{subIcon: 'fa-info', subAction: 'ingame_show_info', subText: 'show info'},
{subDivider: true, action: 'ingame'},
{subIcon: 'fa-flag', subAction: 'ingame_add_waypoint', subText: 'add waypoint'},
{subIcon: 'fa-flag-checkered', subAction: 'ingame_set_destination', subText: 'set destination'}
{icon: 'fa-reply fa-rotate-180', action: 'ingame', text: 'ingame', subitems: [
{subIcon: 'fa-info', subAction: 'ingame_show_info', subText: 'show info'}
]},
{divider: true, action: 'delete_system'},
{icon: 'fa-eraser', action: 'delete_system', text: 'delete system'}
@@ -1984,15 +2012,11 @@ define([
CCPEVE.showInfo(5, systemData.systemId );
break;
case 'ingame_set_destination':
case 'set_destination':
case 'add_first_waypoint':
case 'add_last_waypoint':
systemData = system.getSystemData();
CCPEVE.setDestination( systemData.systemId );
break;
case 'ingame_add_waypoint':
systemData = system.getSystemData();
CCPEVE.addWaypoint( systemData.systemId );
setDestination(systemData, action);
break;
}
}
@@ -2044,6 +2068,68 @@ define([
system.singleDoubleClick(single, double);
};
/**
* set new destination for a system
* -> CREST request
* @param systemData
* @param type
*/
var setDestination = function(systemData, type){
var description = '';
switch(type){
case 'set_destination':
description = 'Set destination';
break;
case 'add_first_waypoint':
description = 'Set first waypoint';
break;
case 'add_last_waypoint':
description = 'Set new waypoint';
break;
}
$.ajax({
type: 'POST',
url: Init.path.setDestination,
data: {
clearOtherWaypoints: (type === 'set_destination') ? 1 : 0,
first: (type === 'add_last_waypoint') ? 0 : 1,
systemData: [{
systemId: systemData.systemId,
name: systemData.name
}]
},
context: {
description: description
},
dataType: 'json'
}).done(function(responseData){
if(
responseData.systemData &&
responseData.systemData.length > 0
){
for (var j = 0; j < responseData.systemData.length; j++) {
Util.showNotify({title: this.description, text: 'System: ' + responseData.systemData[j].name, type: 'success'});
}
}
if(
responseData.error &&
responseData.error.length > 0
){
for(var i = 0; i < responseData.error.length; i++){
Util.showNotify({title: this.description + ' error', text: 'System: ' + responseData.error[i].message, type: 'error'});
}
}
}).fail(function( jqXHR, status, error) {
var reason = status + ' ' + error;
Util.showNotify({title: jqXHR.status + ': ' + this.description, text: reason, type: 'warning'});
});
};
/**
* mark a dom element (map, system, connection) as changed
*/
@@ -3099,12 +3185,10 @@ define([
// this is restricted to IGB-usage! CharacterLog data is always set through the IGB
// ->this prevent adding the same system multiple times, if a user is online with IGB AND OOG
if(
CCP.isInGameBrowser() === true &&
currentUserOnMap === false &&
currentCharacterLog &&
mapTracking
){
// add new system to the map
var requestData = {
systemData: {
@@ -3344,6 +3428,15 @@ define([
return data;
};
/**
* removes a map instance from local cache
* @param mapId
*/
var clearMapInstance = function(mapId){
if(typeof activeInstances[mapId] === 'object'){
delete activeInstances[mapId];
}
};
/**
* get a new jsPlumb map instance or or get a cached one for update
@@ -3583,4 +3676,8 @@ define([
});
};
return {
clearMapInstance: clearMapInstance
};
});

View File

@@ -39,14 +39,26 @@ define([
// map init load static data =======================================================
$.getJSON( Init.path.initMap, function( initData ) {
Init.timer = initData.timer;
Init.mapTypes = initData.mapTypes;
Init.mapScopes = initData.mapScopes;
Init.connectionScopes = initData.connectionScopes;
Init.systemStatus = initData.systemStatus;
Init.systemType = initData.systemType;
Init.characterStatus = initData.characterStatus;
Init.maxSharedCount = initData.maxSharedCount;
if( initData.error.length > 0 ){
for(var i = 0; i < initData.error.length; i++){
Util.showNotify({
title: initData.error[i].title,
text: initData.error[i].message,
type: initData.error[i].type
});
}
}
Init.timer = initData.timer;
Init.mapTypes = initData.mapTypes;
Init.mapScopes = initData.mapScopes;
Init.connectionScopes = initData.connectionScopes;
Init.systemStatus = initData.systemStatus;
Init.systemType = initData.systemType;
Init.characterStatus = initData.characterStatus;
Init.maxSharedCount = initData.maxSharedCount;
Init.routes = initData.routes;
// init tab change observer, Once the timers are available
Page.initTabChangeObserver();
@@ -168,14 +180,7 @@ define([
}
}
}).fail(function( jqXHR, status, error) {
// clear both main update request trigger timer
clearUpdateTimeouts();
var reason = status + ' ' + jqXHR.status + ': ' + error;
$(document).trigger('pf:shutdown', {reason: reason});
});
}).fail(handleAjaxErrorResponse);
};
// ping for user data update =======================================================
@@ -258,14 +263,35 @@ define([
}
}
}).fail(function( jqXHR, status, error) {
}).fail(handleAjaxErrorResponse);
// clear both main update request trigger timer
clearUpdateTimeouts();
};
var reason = status + ' ' + jqXHR.status + ': ' + error;
$(document).trigger('pf:shutdown', {reason: reason});
});
/**
* Ajax error response handler function for main-ping functions
* @param jqXHR
* @param status
* @param error
*/
var handleAjaxErrorResponse = function(jqXHR, status, error){
// clear both main update request trigger timer
clearUpdateTimeouts();
var reason = status + ' ' + jqXHR.status + ': ' + error;
var errorData = [];
if(jqXHR.responseText){
var errorObj = $.parseJSON(jqXHR.responseText);
if(
errorObj.error &&
errorObj.error.length > 0
){
errorData = errorObj.error;
}
}
$(document).trigger('pf:shutdown', {reason: reason, error: errorData});
};
@@ -287,7 +313,6 @@ define([
};
});
});

View File

@@ -2,6 +2,7 @@ define([
'jquery',
'app/init',
'app/util',
'app/map/map',
'app/counter',
'app/ui/system_info',
'app/ui/system_graph',
@@ -9,9 +10,8 @@ define([
'app/ui/system_route',
'app/ui/system_killboard',
'datatablesTableTools',
'datatablesResponsive',
'app/map/map'
], function($, Init, Util) {
'datatablesResponsive'
], function($, Init, Util, Map) {
'use strict';
@@ -129,7 +129,7 @@ define([
firstCell.drawSignatureTableModule(currentSystemData.systemData);
// draw system routes module
secondCell.drawSystemRouteModule(currentSystemData.systemData);
secondCell.drawSystemRouteModule(currentSystemData.mapId, currentSystemData.systemData);
// draw system killboard module
secondCell.drawSystemKillboardModule(currentSystemData.systemData);
@@ -153,7 +153,7 @@ define([
var clickY = e.pageY - posY;
// check for top-left click
if(clickX <= 6 && clickY <= 6){
if(clickX <= 8 && clickY <= 8){
// remember height
if(! moduleElement.data('origHeight')){
@@ -175,7 +175,7 @@ define([
});
}else{
moduleElement.velocity('finish').velocity({
height: [ '40px', [ 400, 15 ] ]
height: [ '36px', [ 400, 15 ] ]
},{
duration: 400,
easing: 'easeInSine',
@@ -259,6 +259,11 @@ define([
});
};
/**
* get a fresh tab element
* @param options
* @returns {*|jQuery|HTMLElement}
*/
var getTabElement = function(options){
var tabElement = $('<div>', {
@@ -395,7 +400,7 @@ define([
// update Tab element -> set data
linkElement.updateTabData(options);
// tabs content ====================================
// tabs content =======================================================
var contentElement = $('<div>', {
id: config.mapTabIdPrefix + parseInt( options.id ),
class: [config.mapTabContentClass].join(' ')
@@ -405,8 +410,7 @@ define([
tabContent.append(contentElement);
// init tab =========================================================
// init tab ===========================================================
linkElement.on('click', function(e){
e.preventDefault();
@@ -482,8 +486,11 @@ define([
liElement.remove();
contentElement.remove();
// remove map instance from local cache
Map.clearMapInstance(mapId);
if(findNewActiveTab === true){
tabElement.find('a:first').tab('show');
tabElement.find('.' + config.mapTabClass + ':not(.pull-right):first a').tab('show');
}
}
@@ -540,6 +547,9 @@ define([
// tab element already exists
var tabElements = mapModuleElement.getMapTabElements();
// map ID that is currently active
var activeMapId = 0;
// mapIds that are currently active
var activeMapIds = [];
@@ -582,6 +592,15 @@ define([
var newTabElements = tabMapElement.addTab(data.config);
// check if there is any active map yet (this is not the case
// when ALL maps are removed AND new maps are added in one call
// e.g. character switch)
if(tabMapElement.find('.' + config.mapTabClass + '.active:not(.pull-right)').length === 0){
tabMapElement.find('.' + config.mapTabClass + ':not(.pull-right):first a').tab('show');
activeMapId = data.config.id;
}
// set observer for manually triggered map events
newTabElements.contentElement.setTabContentObserver();
@@ -596,7 +615,9 @@ define([
});
// get current active map
var activeMapId = Util.getMapModule().getActiveMap().data('id');
if(activeMapId === 0){
activeMapId = Util.getMapModule().getActiveMap().data('id');
}
var activeMapData = Util.getCurrentMapData(activeMapId);
if(activeMapData !== false){

View File

@@ -14,7 +14,6 @@ define([
'text!templates/modules/footer.html',
'dialog/notification',
'dialog/trust',
'dialog/sharing_settings',
'dialog/map_info',
'dialog/account_settings',
'dialog/manual',
@@ -143,13 +142,13 @@ define([
$('<a>', {
class: 'list-group-item',
href: '#'
}).html('&nbsp;&nbsp;Sharing settings').prepend(
}).html('&nbsp;&nbsp;Settings').prepend(
$('<i>',{
class: 'fa fa-share-alt fa-fw'
class: 'fa fa-gears fa-fw'
})
).on('click', function(){
$(document).triggerMenuEvent('ShowSharingSettings');
})
$(document).triggerMenuEvent('ShowSettingsDialog');
})
).append(
$('<a>', {
class: 'list-group-item',
@@ -231,7 +230,7 @@ define([
class: 'fa fa-sign-in fa-fw'
})
).on('click', function(){
$(document).triggerMenuEvent('Logout');
$(document).triggerMenuEvent('Logout', {clearCookies: 1});
})
)
);
@@ -307,12 +306,7 @@ define([
onEnable: 'initMagnetizer', // jQuery extension function
onDisable: 'destroyMagnetizer' // jQuery extension function
});
}).append(
$('<span>',{
class: ['badge', 'bg-color', 'bg-color-orange', 'pull-right'].join(' '),
html: '&beta;'
})
)
})
).append(
$('<a>', {
class: 'list-group-item',
@@ -400,11 +394,6 @@ define([
slideMenu.slidebars.toggle('right');
});
// settings
$('.' + config.headUserCharacterClass).find('a').on('click', function(){
$(document).triggerMenuEvent('ShowSettingsDialog');
});
// active pilots
$('.' + config.headActiveUserClass).find('a').on('click', function(){
$(document).triggerMenuEvent('ShowMapInfo');
@@ -439,11 +428,8 @@ define([
});
// set default values for map tracking checkbox
if(CCP.isInGameBrowser() === false){
mapTrackingCheckbox.bootstrapToggle('disable');
}else{
mapTrackingCheckbox.bootstrapToggle('on');
}
// -> always "enable"
mapTrackingCheckbox.bootstrapToggle('on');
mapTrackingCheckbox.on('change', function(e) {
var value = $(this).is(':checked');
@@ -502,13 +488,6 @@ define([
*/
var setDocumentObserver = function(){
// tab close/reload detected
window.addEventListener('beforeunload', function (e) {
// logout
deleteLog();
});
// on "full-screen" change event
$(document).on('fscreenchange', function(e, state, elem){
@@ -526,12 +505,6 @@ define([
}
});
$(document).on('pf:menuShowSharingSettings', function(e){
// show sharing settings dialog
$.fn.showSharingSettingsDialog();
return false;
});
$(document).on('pf:menuShowSystemEffectInfo', function(e){
// show system effects info box
$.fn.showSystemEffectInfoDialog();
@@ -612,10 +585,19 @@ define([
});
$(document).on('pf:menuLogout', function(e, data){
var clearCookies = false;
if( typeof data === 'object' ){
if( data.hasOwnProperty('clearCookies') ){
clearCookies = data.clearCookies;
}
}
// logout
Util.logout({
ajaxData: {
reroute: 1
reroute: 1,
clearCookies: clearCookies
}
});
return false;
@@ -670,12 +652,22 @@ define([
title: 'Shutdown',
headline: 'Emergency shutdown',
text: [
'Sorry! Under normal circumstances that should not happen',
data.reason
]
],
textSmaller: []
}
};
// add error information (if available)
if(
data.error &&
data.error.length
){
for(var i = 0; i < data.error.length; i++){
options.content.textSmaller.push(data.error[i].message);
}
}
$.fn.showNotificationDialog(options);
$(document).setProgramStatus('offline');
@@ -752,7 +744,7 @@ define([
newCharacterName = userData.character.name;
if(userData.character.log){
newShipId = userData.character.log.ship.id;
newShipId = userData.character.log.ship.typeId;
newShipName = userData.character.log.ship.typeName;
}
}
@@ -769,6 +761,9 @@ define([
animateHeaderElement(userInfoElement, function(){
userInfoElement.find('span').text( newCharacterName );
userInfoElement.find('img').attr('src', Init.url.ccpImageServer + 'Character/' + newCharacterId + '_32.jpg' );
// init "character switch" popover
userInfoElement.initCharacterSwitchPopover(userData);
}, showCharacterElement);
// set new id for next check

View File

@@ -1,5 +1,5 @@
/**
* user register/settings dialog
* user settings/share dialog
*/
define([
@@ -12,22 +12,13 @@ define([
'use strict';
var config = {
dialogWizardNavigationClass: 'pf-wizard-navigation', // class for wizard navigation bar
// select character dialog
settingsDialogId: 'pf-settings-dialog', // id for "settings" dialog
settingsImageWrapperClass: 'pf-dialog-image-wrapper', // class for image wrapper (animated)
settingsImageInfoClass: 'pf-dialog-character-info', // class for character info layer (visible on hover)
settingsMainClass: 'pf-dialog-character-main', // class for main character highlighting
settingsNavigationButtonClass: 'pf-dialog-navigation-button', // class for all navigation buttons
settingsFinishButtonClass: 'pf-dialog-finish-button', // class for "finish" button
settingsPrevButtonClass: 'pf-dialog-prev-button', // class for "prev" button
settingsNextButtonClass: 'pf-dialog-next-button', // class for "next" button
settingsCloneApiRowClass: 'pf-dialog-api-row', // class for form row with API data (will be cloned)
settingsCloneRowButtonClass: 'pf-dialog-clone-button', // class for clone button (api row)
settingsDeleteRowButtonClass: 'pf-dialog-delete-button', // class for delete button (api row)
settingsAccountContainerId: 'pf-settings-dialog-account', // id for the "account" container
settingsShareContainerId: 'pf-settings-dialog-share', // id for the "share" container
// captcha
captchaKeyUpdateAccount: 'SESSION.CAPTCHA.ACCOUNT.UPDATE', // key for captcha reason
captchaImageWrapperId: 'pf-dialog-captcha-wrapper', // id for "captcha image" wrapper
captchaImageId: 'pf-dialog-captcha-image', // id for "captcha image"
@@ -35,80 +26,9 @@ define([
icon: {
size: 'fa-2x'
}
},
// character status
settingsCharacterStatusOwn : { // "own" -> my characters
name: 'own',
class: 'pf-user-status-own'
}
};
/**
* get active Tab link element for a dialog
* @param dialog
* @returns {JQuery|*}
*/
var getActiveTabElement = function(dialog){
var navigationBarElement = $(dialog).find('.' + config.dialogWizardNavigationClass);
var currentActiveTab = navigationBarElement.find('li.active');
return currentActiveTab;
};
/**
* init popovers in dialog
* @param dialogElement
*/
var initPopover = function(dialogElement){
var apiCloneButtons = dialogElement.find('.' + config.settingsCloneRowButtonClass);
var apiDeleteButtons = dialogElement.find('.' + config.settingsDeleteRowButtonClass);
var confirmationSettings = {
container: 'body',
placement: 'left',
btnCancelClass: 'btn btn-sm btn-default',
btnCancelLabel: 'cancel',
btnCancelIcon: 'fa fa-fw fa-ban'
};
// add API key row
var cloneConfirmationSettings = $.extend({
title: 'Add additional key',
btnOkClass: 'btn btn-sm btn-success',
btnOkLabel: 'confirm',
btnOkIcon: 'fa fa-fw fa-check',
onConfirm: function(e) {
var cloneRow = dialogElement.find('.' + config.settingsCloneApiRowClass).last();
var newApiRow = cloneRow.clone();
newApiRow.find('.form-group').removeClass('has-success has-error');
newApiRow.find('input').val('');
cloneRow.after(newApiRow);
// init new row with popups
initPopover(dialogElement);
}
}, confirmationSettings);
// delete API key row
var deleteConfirmationSettings = $.extend({
title: 'Delete key',
btnOkClass: 'btn btn-sm btn-danger',
btnOkLabel: 'delete',
btnOkIcon: 'fa fa-fw fa-close',
onConfirm: function(e, target) {
$(target).parents('.row').remove();
}
}, confirmationSettings);
$(apiCloneButtons).confirmation(cloneConfirmationSettings);
$(apiDeleteButtons).confirmation(deleteConfirmationSettings);
};
/**
* show "register/settings" dialog
* @param options
@@ -122,213 +42,124 @@ define([
return false;
}
var reroutePath = '';
// check navigation buttons and show/hide them
var checkNavigationButton = function(dialog){
var navigationBarElement = $(dialog).find('.' + config.dialogWizardNavigationClass);
var currentActiveTab = navigationBarElement.find('li.active');
var hidePrevButton = currentActiveTab.prevAll().length > 0 ? false : true;
var hideNextButton = currentActiveTab.nextAll().length > 0 ? false : true;
if(hidePrevButton){
$('.' + config.settingsPrevButtonClass).hide();
}else{
$('.' + config.settingsPrevButtonClass).show();
}
if(hideNextButton){
$('.' + config.settingsNextButtonClass).hide();
}else{
$('.' + config.settingsNextButtonClass).show();
}
};
requirejs(['text!templates/dialog/settings.html', 'mustache'], function(template, Mustache) {
// if this is a new registration there is no API key -> fake an empty API to make fields visible
if(options.register === 1){
Init.currentUserData = {};
Init.currentUserData.api = [{
keyId: '',
vCode: ''
}];
}else if(Init.currentUserData.api === undefined){
Init.currentUserData.api = [{
keyId: '',
vCode: ''
}];
}
var data = {
id: config.settingsDialogId,
register: options.register === 1 ? 1 : 0,
invite : options.invite === 1 ? 1 : 0,
navigationClass: config.dialogWizardNavigationClass,
settingsAccountContainerId: config.settingsAccountContainerId,
settingsShareContainerId: config.settingsShareContainerId,
userData: Init.currentUserData,
cloneApiRowClass: config.settingsCloneApiRowClass,
cloneRowButtonClass: config.settingsCloneRowButtonClass,
deleteRowButtonClass: config.settingsDeleteRowButtonClass,
captchaImageWrapperId: config.captchaImageWrapperId,
captchaImageId: config.captchaImageId,
formErrorContainerClass: Util.config.formErrorContainerClass,
formWarningContainerClass: Util.config.formWarningContainerClass
ccpImageServer: Init.url.ccpImageServer
};
var content = Mustache.render(template, data);
var selectCharacterDialog = bootbox.dialog({
title: options.register === 1 ? 'Registration' : 'Account settings',
var accountSettingsDialog = bootbox.dialog({
title: 'Account settings',
message: content,
buttons: {
close: {
label: 'finish',
className: ['btn-success', 'pull-right', config.settingsFinishButtonClass].join(' '),
callback: function(e){
if(options.register === 1){
if(reroutePath !== undefined){
// root user to main app
window.location = reroutePath;
}
}else{
// close dialog
return true;
}
}
label: 'cancel',
className: 'btn-default'
},
prev: {
label: '<i class="fa fa-fw fa-angle-left"></i>back',
className: ['btn-default', 'pull-left', config.settingsNavigationButtonClass, config.settingsPrevButtonClass].join(' '),
callback: function (e) {
var currentActiveTab = getActiveTabElement(this);
currentActiveTab.removeClass('finished');
currentActiveTab.prev('li').find('a').tab('show');
success: {
label: '<i class="fa fa-check fa-fw"></i>&nbsp;save',
className: 'btn-success',
callback: function() {
return false;
}
},
next: {
label: 'next<i class="fa fa-fw fa-angle-right"></i>',
className: ['btn-primary', 'pull-right', config.settingsNavigationButtonClass, config.settingsNextButtonClass].join(' '),
callback: function (e) {
var dialogElement = $(this);
var currentActiveTab = getActiveTabElement(dialogElement);
var currentActiveLink = currentActiveTab.find('a');
var tabContentElement = $(currentActiveLink.attr('href'));
var form = tabContentElement.find('form');
var changeTab = function(){
currentActiveTab.addClass('finished');
currentActiveLink.removeClass('btn-danger btn-default');
currentActiveLink.addClass('btn-primary');
currentActiveTab.next('li').find('a').tab('show');
};
// get the current active form
var form = $('#' + config.settingsDialogId).find('form').filter(':visible');
// validate form
form.validator('validate');
// check weather the form is valid
var formValid = form.isValidForm();
if(!formValid){
currentActiveTab.removeClass('disabled');
currentActiveLink.removeClass('btn-default btn-primary');
currentActiveLink.addClass('btn-danger');
}else{
if(formValid === true){
var tabFormValues = form.getFormValues();
if(! $.isEmptyObject(tabFormValues) ){
// send Tab data and store values
var requestData = {
formData: tabFormValues
};
// send Tab data and store values
var requestData = {
settingsData: tabFormValues
};
accountSettingsDialog.find('.modal-content').showLoadingAnimation();
selectCharacterDialog.find('.modal-content').showLoadingAnimation();
$.ajax({
type: 'POST',
url: Init.path.saveUserConfig,
data: requestData,
dataType: 'json'
}).done(function(responseData){
accountSettingsDialog.find('.modal-content').hideLoadingAnimation();
$.ajax({
type: 'POST',
url: Init.path.saveUserConfig,
data: requestData,
dataType: 'json'
}).done(function(responseData){
selectCharacterDialog.find('.modal-content').hideLoadingAnimation();
// set new captcha for any request
// captcha is required for sensitive data (not for all data)
if(
responseData.error &&
responseData.error.length > 0
){
form.showFormMessage(responseData.error);
// set new captcha for any request
// captcha is required for sensitive data (not for all data)
if(
responseData.error &&
responseData.error.length > 0
){
form.showFormMessage(responseData.error);
$('#' + config.captchaImageWrapperId).showCaptchaImage('createAccount', function(){
$('#captcha').resetFormFields();
});
}else{
// store new/updated user data -> update head
if(responseData.userData){
Util.setCurrentUserData(responseData.userData);
}
// store reroute path after registration finished
if(responseData.reroute){
reroutePath = responseData.reroute;
}
dialogElement.find('.alert').velocity('transition.slideDownOut',{
duration: 500,
complete: function(){
// switch tab
changeTab();
$('#' + config.captchaImageWrapperId).showCaptchaImage('createAccount', function(){
$('#captcha').resetFormFields();
});
}
});
Util.showNotify({title: 'Account saved', type: 'success'});
}
}).fail(function( jqXHR, status, error) {
selectCharacterDialog.find('.modal-content').hideLoadingAnimation();
var reason = status + ' ' + error;
Util.showNotify({title: jqXHR.status + ': saveAccount', text: reason, type: 'error'});
// set new captcha for any request
// captcha is required for sensitive data (not for all)
$('#' + config.captchaImageWrapperId).showCaptchaImage('createAccount', function(){
$('#' + config.captchaImageWrapperId).showCaptchaImage(config.captchaKeyUpdateAccount, function(){
$('#captcha').resetFormFields();
});
}else{
// store new/updated user data -> update head
if(responseData.userData){
Util.setCurrentUserData(responseData.userData);
}
// check for DB errors
if(jqXHR.status === 500){
if(jqXHR.responseText){
var errorObj = $.parseJSON(jqXHR.responseText);
if(
errorObj.error &&
errorObj.error.length > 0
){
form.showFormMessage(errorObj.error);
}
form.find('.alert').velocity('transition.slideDownOut',{
duration: 500,
complete: function(){
$('#' + config.captchaImageWrapperId).showCaptchaImage(config.captchaKeyUpdateAccount, function(){
$('#captcha').resetFormFields();
});
}
}
});
if( options.register !== 1 ){
$(document).setProgramStatus('problem');
}
Util.showNotify({title: 'Account saved', type: 'success'});
// close dialog/menu
$(document).trigger('pf:closeMenu', [{}]);
accountSettingsDialog.modal('hide');
}
}).fail(function( jqXHR, status, error) {
accountSettingsDialog.find('.modal-content').hideLoadingAnimation();
var reason = status + ' ' + error;
Util.showNotify({title: jqXHR.status + ': saveAccountSettings', text: reason, type: 'error'});
// set new captcha for any request
// captcha is required for sensitive data (not for all)
$('#' + config.captchaImageWrapperId).showCaptchaImage(config.captchaKeyUpdateAccount, function(){
$('#captcha').resetFormFields();
});
}else{
// no request required -> change tab
changeTab();
}
// check for DB errors
if(jqXHR.status === 500){
if(jqXHR.responseText){
var errorObj = $.parseJSON(jqXHR.responseText);
if(
errorObj.error &&
errorObj.error.length > 0
){
form.showFormMessage(errorObj.error);
}
}
}
$(document).setProgramStatus('problem');
});
}
return false;
@@ -338,163 +169,42 @@ define([
});
// after modal is shown =======================================================================
selectCharacterDialog.on('shown.bs.modal', function(e) {
accountSettingsDialog.on('shown.bs.modal', function(e) {
var dialogElement = $(this);
var tabLinkElements = dialogElement.find('a[data-toggle="tab"]');
var form = dialogElement.find('form');
// request captcha image and show
$('#' + config.captchaImageWrapperId).showCaptchaImage('createAccount');
var captchaImageWrapperContainer = $('#' + config.captchaImageWrapperId);
captchaImageWrapperContainer.showCaptchaImage(config.captchaKeyUpdateAccount);
// init captcha refresh button
captchaImageWrapperContainer.find('i').on('click', function(){
captchaImageWrapperContainer.showCaptchaImage(config.captchaKeyUpdateAccount);
});
// init dialog tooltips
dialogElement.initTooltips();
// init popups
initPopover( dialogElement );
// init form validation
form.initFormValidation();
});
// on Tab switch ======================================================================
tabLinkElements.on('shown.bs.tab', function (e) {
// check navigation buttons (hide/show)
checkNavigationButton(dialogElement);
$(e.target).removeClass('disabled');
// hide finish button
dialogElement.find('.' + config.settingsFinishButtonClass).hide();
if($(e.target).text() < $(e.relatedTarget).text()){
var currentActiveTab = getActiveTabElement(dialogElement);
var nextTabElements = currentActiveTab.nextAll();
// disable all next tabs
currentActiveTab.removeClass('finished');
nextTabElements.removeClass('finished');
nextTabElements.find('a').removeClass('btn-primary btn-danger').addClass('btn-default disabled');
}
if($(e.target).text() === '3'){
// load character tab -----------------------------------------------
requirejs(['text!templates/form/character_panel.html', 'mustache'], function(template, Mustache) {
// all characters for the current user
var characters = Init.currentUserData.characters;
// calculate grid class
var characterCount = characters.length;
var gridClass = ((12 / characterCount) < 4)? 4 : 12 / characterCount ;
// add character status information for each character
var statusInfo = {};
statusInfo.class = config.settingsCharacterStatusOwn.class;
statusInfo.label = config.settingsCharacterStatusOwn.name;
var mainCharacter = 0;
for(var i = 0; i < characters.length; i++){
characters[i].status = statusInfo;
if(characters[i].isMain === 1){
mainCharacter = characters[i].id;
}else if(mainCharacter === 0){
// mark at least one character as "main" if no main char was found
// e.g. first account setup
mainCharacter = characters[i].id;
}
}
var characterTemplateData = {
imageWrapperClass: config.settingsImageWrapperClass,
imageInfoClass: config.settingsImageInfoClass,
imageWrapperMainClass: config.settingsMainClass,
charactersData: characters,
gridClass: 'col-sm-' + gridClass,
mainCharacter: mainCharacter
};
var content = Mustache.render(template, characterTemplateData);
var characterForm = dialogElement.find('#pf-dialog-settings-character form');
// add form HTML
characterForm.html(content);
var imageWrapperElements = dialogElement.find('.' + config.settingsImageWrapperClass);
// special effects :)
imageWrapperElements.velocity('stop').delay(100).velocity('transition.flipBounceXIn', {
display: 'inline-block',
stagger: 60,
drag: true,
duration: 400,
complete: function(){
// init new character tooltips
dialogElement.initTooltips();
}
});
// Hover effect for character info layer
imageWrapperElements.hoverIntent(function(e){
var characterInfoElement = $(this).find('.' + config.settingsImageInfoClass);
characterInfoElement.velocity('finish').velocity({
width: ['100%', [ 400, 15 ] ]
},{
easing: 'easeInSine'
});
}, function(e){
var characterInfoElement = $(this).find('.' + config.settingsImageInfoClass);
characterInfoElement.velocity('finish').velocity({
width: 0
},{
duration: 150,
easing: 'easeInOutSine'
});
});
// click event on character image
imageWrapperElements.on('click', function(e){
var wrapperElement = $(this);
var characterId = wrapperElement.data('id');
// update layout if character is selected
if(characterId > 0){
// update hidden field with new mainCharacterId
dialogElement.find('input[name="mainCharacterId"]').val(characterId);
imageWrapperElements.removeClass( config.settingsMainClass );
wrapperElement.addClass( config.settingsMainClass );
}
});
});
}else if($(e.target).text() === '4'){
// show finish button
dialogElement.find('.' + config.settingsFinishButtonClass).show();
// show success message
dialogElement.find('h1').velocity('stop').delay(200).velocity('transition.flipBounceXIn', {
duration: 500
}).delay(100).velocity('callout.pulse');
}
// events for tab change
accountSettingsDialog.find('.navbar a').on('shown.bs.tab', function(e){
// init "toggle" switches on current active tab
accountSettingsDialog.find( $(this).attr('href') ).find('input[type="checkbox"]').bootstrapToggle({
on: '<i class="fa fa-fw fa-check"></i>&nbsp;Enable',
off: 'Disable&nbsp;<i class="fa fa-fw fa-ban"></i>',
onstyle: 'success',
offstyle: 'warning',
width: 90,
height: 30
});
});
});
};
});

View File

@@ -15,6 +15,7 @@ define([
deleteAccountId: 'pf-dialog-delete-account', // dialog id
// captcha
captchaKeyDeleteAccount: 'SESSION.CAPTCHA.ACCOUNT.DELETE', // key for captcha reason
captchaImageWrapperId: 'pf-dialog-captcha-wrapper' // id for "captcha image" wrapper
};
@@ -82,8 +83,8 @@ define([
){
form.showFormMessage(responseData.error);
$('#' + config.captchaImageWrapperId).showCaptchaImage('deleteAccount', function(){
form.find('[name="captcha"], [name="password"]').resetFormFields();
$('#' + config.captchaImageWrapperId).showCaptchaImage(config.captchaKeyDeleteAccount, function(){
form.find('[name="captcha"]').resetFormFields();
});
}
@@ -106,7 +107,7 @@ define([
// after modal is shown =======================================================================
deleteAccountDialog.on('shown.bs.modal', function(e) {
// request captcha image and show
$('#' + config.captchaImageWrapperId).showCaptchaImage('deleteAccount');
$('#' + config.captchaImageWrapperId).showCaptchaImage(config.captchaKeyDeleteAccount);
});
});

View File

@@ -643,7 +643,6 @@ define([
}
}
var userDataTable = userTable.dataTable( {
pageLength: 20,
paging: true,
@@ -670,7 +669,7 @@ define([
data: 'log.ship',
render: {
_: function(data, type, row, meta){
return '<img src="' + Init.url.ccpImageServer + 'Render/' + data.id + '_32.png" />';
return '<img src="' + Init.url.ccpImageServer + 'Render/' + data.typeId + '_32.png" />';
}
}
},{
@@ -736,6 +735,16 @@ define([
}
},{
targets: 7,
title: 'station',
orderable: true,
searchable: true,
data: 'log.station',
render: {
_: 'name',
sort: 'name'
}
},{
targets: 8,
title: '',
orderable: false,
searchable: false,
@@ -754,7 +763,7 @@ define([
});
}
},{
targets: 8,
targets: 9,
title: '',
orderable: false,
searchable: false,

View File

@@ -20,7 +20,7 @@ define([
dialogMapSettingsContainerId: 'pf-map-dialog-settings', // id for the "settings" container
dialogMapDownloadContainerId: 'pf-map-dialog-download', // id for the "download" container
userSelectId: 'pf-map-dialog-user-select', // id for "user" select
characterSelectId: 'pf-map-dialog-character-select', // id for "character" select
corporationSelectId: 'pf-map-dialog-corporation-select', // id for "corporation" select
allianceSelectId: 'pf-map-dialog-alliance-select', // id for "alliance" select
@@ -100,7 +100,7 @@ define([
contentEditMap = $(contentEditMap);
// current map access info
var accessUser = [];
var accessCharacter = [];
var accessCorporation = [];
var accessAlliance = [];
@@ -112,7 +112,7 @@ define([
contentEditMap.find('select[name="scopeId"]').val( mapData.config.scope.id );
contentEditMap.find('select[name="typeId"]').val( mapData.config.type.id );
accessUser = mapData.config.access.user;
accessCharacter = mapData.config.access.character;
accessCorporation = mapData.config.access.corporation;
accessAlliance = mapData.config.access.alliance;
}
@@ -145,17 +145,17 @@ define([
hideDownloadTab: hideDownloadTab,
// settings tab --------------
userSelectId: config.userSelectId,
characterSelectId: config.characterSelectId,
corporationSelectId: config.corporationSelectId,
allianceSelectId: config.allianceSelectId,
// map access objects --------
accessUser: accessUser,
accessCharacter: accessCharacter,
accessCorporation: accessCorporation,
accessAlliance: accessAlliance,
// access limitations --------
maxUser: Init.maxSharedCount.user,
maxCharacter: Init.maxSharedCount.character,
maxCorporation: Init.maxSharedCount.corporation,
maxAlliance: Init.maxSharedCount.alliance,
@@ -287,7 +287,7 @@ define([
// events for tab change
mapInfoDialog.find('.navbar a').on('shown.bs.tab', function(e){
var selectElementUser = mapInfoDialog.find('#' + config.userSelectId);
var selectElementCharacter = mapInfoDialog.find('#' + config.characterSelectId);
var selectElementCorporation = mapInfoDialog.find('#' + config.corporationSelectId);
var selectElementAlliance = mapInfoDialog.find('#' + config.allianceSelectId);
@@ -295,8 +295,8 @@ define([
// "settings" tab
initSettingsSelectFields(mapInfoDialog);
}else{
if( $(selectElementUser).data('select2') !== undefined ){
$(selectElementUser).select2('destroy');
if( $(selectElementCharacter).data('select2') !== undefined ){
$(selectElementCharacter).select2('destroy');
}
if( $(selectElementCorporation).data('select2') !== undefined ){
@@ -550,14 +550,14 @@ define([
*/
var initSettingsSelectFields = function(mapInfoDialog){
var selectElementUser = mapInfoDialog.find('#' + config.userSelectId);
var selectElementCharacter = mapInfoDialog.find('#' + config.characterSelectId);
var selectElementCorporation = mapInfoDialog.find('#' + config.corporationSelectId);
var selectElementAlliance = mapInfoDialog.find('#' + config.allianceSelectId);
// init corporation select live search
selectElementUser.initAccessSelect({
type: 'user',
maxSelectionLength: Init.maxSharedCount.user
// init character select live search
selectElementCharacter.initAccessSelect({
type: 'character',
maxSelectionLength: Init.maxSharedCount.character
});
// init corporation select live search
@@ -575,7 +575,7 @@ define([
/**
* shows the delete map Dialog
* @param mapElement
* @param mapData
*/
$.fn.showDeleteMapDialog = function(mapData){

View File

@@ -47,8 +47,6 @@ define([
releasesDialog.find('ul.timeline').append(content);
}
// console.log()
$('.timeline > li').velocity('transition.expandIn', {
stagger: 300,
duration: 240,

View File

@@ -1,109 +0,0 @@
/**
* sharing settings dialog
*/
define([
'jquery',
'app/init',
'app/util',
'app/render',
'bootbox'
], function($, Init, Util, Render, bootbox) {
'use strict';
var config = {
// select character dialog
sharingDialogId: 'pf-sharing-dialog', // id for "sharing settings" dialog
};
$.fn.showSharingSettingsDialog = function(){
var sharingDialogElement = $('#' + config.sharingDialogId);
if(!sharingDialogElement.is(':visible')){
var userData = Util.getCurrentUserData();
if(userData){
requirejs([
'text!templates/dialog/sharing_settings.html',
'mustache'
], function(templatSharingDialog, Mustache) {
var data = {
id: config.sharingDialogId,
ccpImageServer: Init.url.ccpImageServer,
userData: userData
};
// render "new map" tab content -------------------------------------------
var contentSharingDialog = Mustache.render(templatSharingDialog, data);
var sharingSettingsDialog = bootbox.dialog({
title: 'Sharing settings',
message: $(contentSharingDialog),
buttons: {
close: {
label: 'cancel',
className: 'btn-default'
},
success: {
label: '<i class="fa fa-check fa-fw"></i>&nbsp;save',
className: 'btn-success',
callback: function() {
var form = $('#' + config.sharingDialogId).find('form');
var sharingSettingsData = {formData: form.getFormValues()};
$.ajax({
type: 'POST',
url: Init.path.saveSharingConfig,
data: sharingSettingsData,
dataType: 'json'
}).done(function(data){
if(data.userData !== undefined){
// store current user data global (cache)
Util.setCurrentUserData(data.userData);
$(document).trigger('pf:closeMenu', [{}]);
$(sharingSettingsDialog).modal('hide');
// success
Util.showNotify({title: 'Sharing settings saved', type: 'success'});
}
}).fail(function( jqXHR, status, error) {
var reason = status + ' ' + error;
Util.showNotify({title: jqXHR.status + ': shareSettings', text: reason, type: 'warning'});
$(document).setProgramStatus('problem');
});
return false;
}
}
}
});
// after modal is shown ---------------------------------------------------
sharingSettingsDialog.on('shown.bs.modal', function(e) {
$(this).find('input[type="checkbox"]').bootstrapToggle({
on: '<i class="fa fa-fw fa-check"></i>&nbsp;Enable',
off: 'Disable&nbsp;<i class="fa fa-fw fa-ban"></i>',
onstyle: 'success',
offstyle: 'warning',
width: 90,
height: 30
});
});
});
}else{
Util.showNotify({title: 'No userData found', type: 'warning'});
}
}
};
});

View File

@@ -10,6 +10,23 @@ define([
'use strict';
/**
* init a select element as "select2" for map selection
*/
$.fn.initMapSelect = function(){
var selectElement = $(this);
$.when(
selectElement.select2({
dropdownParent: 'body',
theme: 'pathfinder',
maximumSelectionLength: 5
})
);
};
/**
* init a select element as an ajax based "select2" object for system search
* @param options
@@ -114,7 +131,7 @@ define([
theme: 'pathfinder',
minimumInputLength: 2,
templateResult: formatResultData,
placeholder: 'Systemname',
placeholder: 'System name',
allowClear: true,
escapeMarkup: function (markup) {
// let our custom formatter work
@@ -131,7 +148,7 @@ define([
/**
* init a select element as an ajax based "select2" object for Access resources
* user (private map), corporation (corp map), alliance (ally map)
* character (private map), corporation (corp map), alliance (ally map)
* @param options
*/
$.fn.initAccessSelect = function(options){
@@ -162,8 +179,9 @@ define([
var previewContent = '';
switch(options.type){
case 'user':
previewContent = '<i class="fa fa-lg fa-user"></i>';
case 'character':
imagePath = Init.url.ccpImageServer + 'Character/' + data.id + '_32.jpg';
previewContent = '<img src="' + imagePath + '" style="max-width: 100%" />';
break;
case 'corporation':
imagePath = Init.url.ccpImageServer + 'Corporation/' + data.id + '_32.png';
@@ -251,26 +269,6 @@ define([
});
});
};
});

View File

@@ -150,57 +150,58 @@ define([
dataType: 'json'
}).done(function(systemGraphsData){
// create new (hidden) module container
var moduleElement = $('<div>', {
class: [config.moduleClass, config.systemGraphModuleClass].join(' '),
css: {opacity: 0}
});
// insert at the correct position
if($(parentElement).children().length === 1){
$(parentElement).append(moduleElement);
}else{
$(parentElement).find('>:first-child').after(moduleElement);
}
// row element
var rowElement = $('<div>', {
class: 'row'
});
moduleElement.append(rowElement);
$.each(systemGraphsData, function(systemId, graphsData){
$.each(graphsData, function(graphKey, graphData){
var colElement = $('<div>', {
class: ['col-xs-12', 'col-sm-6', 'col-md-4'].join(' ')
});
var headlineElement = $('<h5>').text( getInfoForGraph(graphKey, 'headline') );
colElement.append(headlineElement);
var graphElement = $('<div>', {
class: config.systemGraphClass
});
colElement.append(graphElement);
rowElement.append(colElement);
initGraph(graphElement, graphKey, graphData, eventLine);
if( !$.isEmptyObject(systemGraphsData) ){
// create new (hidden) module container
var moduleElement = $('<div>', {
class: [config.moduleClass, config.systemGraphModuleClass].join(' '),
css: {opacity: 0}
});
});
moduleElement.append($('<div>', {
css: {'clear': 'both'}
}));
// insert at the correct position
if($(parentElement).children().length === 1){
$(parentElement).append(moduleElement);
}else{
$(parentElement).find('>:first-child').after(moduleElement);
}
// show module
moduleElement.velocity('transition.slideDownIn', {
duration: Init.animationSpeed.mapModule,
delay: Init.animationSpeed.mapModule
});
// row element
var rowElement = $('<div>', {
class: 'row'
});
moduleElement.append(rowElement);
$.each(systemGraphsData, function(systemId, graphsData){
$.each(graphsData, function(graphKey, graphData){
var colElement = $('<div>', {
class: ['col-xs-12', 'col-sm-6', 'col-md-4'].join(' ')
});
var headlineElement = $('<h5>').text( getInfoForGraph(graphKey, 'headline') );
colElement.append(headlineElement);
var graphElement = $('<div>', {
class: config.systemGraphClass
});
colElement.append(graphElement);
rowElement.append(colElement);
initGraph(graphElement, graphKey, graphData, eventLine);
});
});
moduleElement.append($('<div>', {
css: {'clear': 'both'}
}));
// show module
moduleElement.velocity('transition.slideDownIn', {
duration: Init.animationSpeed.mapModule,
delay: Init.animationSpeed.mapModule
});
}
}).fail(function( jqXHR, status, error) {
var reason = status + ' ' + error;
Util.showNotify({title: jqXHR.status + ': System graph data', text: reason, type: 'warning'});

View File

@@ -333,34 +333,34 @@ define([
$.ajax({
url: url,
type: 'GET',
dataType: 'jsonp'
}).done(function(kbData){
dataType: 'json'
}).done(function(kbData) {
// the API wont return more than 200KMs ! - remember last bar block with complete KM information
var lastCompleteDiffHourData = 0;
// loop kills and count kills by hour
for(var i = 0; i < kbData.length; i++){
for (var i = 0; i < kbData.length; i++) {
var killmailData = kbData[i];
var killDate = getDateObjectByTimeString(killmailData.killTime);
// get time diff
var timeDiffMin = Math.round( ( serverDate - killDate ) / 1000 / 60 );
var timeDiffHour = Math.round( timeDiffMin / 60 );
var timeDiffMin = Math.round(( serverDate - killDate ) / 1000 / 60);
var timeDiffHour = Math.round(timeDiffMin / 60);
// update chart data
if(chartData[timeDiffHour]){
if (chartData[timeDiffHour]) {
chartData[timeDiffHour].kills++;
// add kill mail data
if(chartData[timeDiffHour].killmails === undefined){
if (chartData[timeDiffHour].killmails === undefined) {
chartData[timeDiffHour].killmails = [];
}
chartData[timeDiffHour].killmails.push(killmailData);
if(timeDiffHour > lastCompleteDiffHourData){
if (timeDiffHour > lastCompleteDiffHourData) {
lastCompleteDiffHourData = timeDiffHour;
}
}
@@ -368,7 +368,7 @@ define([
}
// remove empty chart Data
if(kbData.length >= maxKillmailCount){
if (kbData.length >= maxKillmailCount) {
chartData = chartData.splice(0, lastCompleteDiffHourData + 1);
}
@@ -378,7 +378,7 @@ define([
cache.systemKillsGraphData[cacheKey].count = kbData.length;
// draw table
drawGraph( cache.systemKillsGraphData[cacheKey] );
drawGraph(cache.systemKillsGraphData[cacheKey]);
// show killmail information
showKillmails(moduleElement, cache.systemKillsGraphData[cacheKey]);

View File

@@ -14,19 +14,25 @@ define([
// module info
moduleClass: 'pf-module', // class for each module
routeCacheTTL: 10, // route cache timer (client) in seconds
// system route module
systemRouteModuleClass: 'pf-system-route-module', // class for this module
// headline toolbar
systemModuleHeadlineIcon: 'pf-module-icon-button', // class for toolbar icons in the head
systemModuleHeadlineIconSearch: 'pf-module-icon-button-search', // class for "search" icon
systemModuleHeadlineIconRefresh: 'pf-module-icon-button-refresh', // class for "refresh" icon
systemSecurityClassPrefix: 'pf-system-security-', // prefix class for system security level (color)
// dialog
routeDialogId: 'pf-route-dialog', // id for route dialog
systemDialogSelectClass: 'pf-system-dialog-select', // class for system select Element
systemInfoRoutesTableRowPrefix: 'pf-system-info-routes-row-', // prefix class for a row in the route table
systemInfoRoutesTableClass: 'pf-system-route-table' // class for route tables
systemInfoRoutesTableClass: 'pf-system-route-table', // class for route tables
mapSelectId: 'pf-route-dialog-map-select', // id for "map" select
sigTableActionCellClass: 'pf-table-action-cell' // class for "action" cells
};
@@ -42,41 +48,101 @@ define([
*/
var callbackAddRouteRow = function(context, routesData){
for(var i = 0; i < routesData.length; i++){
var routeData = routesData[i];
if(routesData.length > 0){
for(var i = 0; i < routesData.length; i++){
var routeData = routesData[i];
// format routeData
var rowData = formatRouteData(routeData);
// format routeData
var rowData = formatRouteData(routeData);
if(rowData.route){
var cacheKey = routeData.route[0].system + '_' + routeData.route[ routeData.route.length - 1 ].system;
if(rowData.route){
var cacheKey = routeData.systemFrom.toLowerCase() + '_' + routeData.systemTo.toLowerCase();
// update route cache
cache.systemRoutes[cacheKey] = rowData;
// update route cache
cache.systemRoutes[cacheKey] = {
data: rowData,
updated: Util.getServerTime().getTime() / 1000
};
addRow(context.dataTable, rowData);
}else{
// route not possible
Util.showNotify({title: 'Route not found', type: 'warning'});
var rowElement = addRow(context, rowData);
rowElement.initTooltips({
container: 'body'
});
}
}
// redraw dataTable
context.dataTable.draw();
}
};
/**
* add a new dataTable row to the jump table
* @param dataTable
* add a new dataTable row to the routes table
* @param context
* @param rowData
* @returns {*}
*/
var addRow = function(dataTable, rowData){
var rowClass = config.systemInfoRoutesTableRowPrefix + dataTable.rows().data().length;
var addRow = function(context, rowData){
var dataTable = context.dataTable;
var rowElement = null;
var row = null;
var animationStatus = 'changed';
// add new row
var rowElement = dataTable.row.add( rowData ).draw().nodes().to$();
rowElement.addClass( rowClass );
// search for an existing row (e.g. on mass "table refresh" [all routes])
// get rowIndex where column 0 (equals to "systemTo") matches rowData.systemTo
var indexes = dataTable.rows().eq(0).filter( function (rowIdx) {
return (dataTable.cell(rowIdx, 0 ).data() === rowData.systemTo);
});
rowElement.find('i').tooltip();
if(indexes.length > 0){
// update row with FIRST index
// -> systemFrom should be unique!
row = dataTable.row( parseInt(indexes[0]) );
// update row data
row.data(rowData);
}else{
// no existing route found -> add new row
row = dataTable.row.add( rowData );
animationStatus = 'added';
}
if(row.length > 0){
rowElement = row.nodes().to$();
if(animationStatus !== null){
rowElement.data('animationStatus', animationStatus);
}
}
return rowElement;
};
/**
* update complete routes table (refresh all)
* @param moduleElement
* @param dataTable
*/
var updateRoutesTable = function(moduleElement, dataTable){
var context = {
moduleElement: moduleElement,
dataTable: dataTable
};
var routeData = [];
dataTable.rows().every( function() {
var data = this.data();
routeData.push({
mapIds: data.mapIds,
systemFrom: data.systemFrom,
systemTo: data.systemTo
});
} );
getRouteData({routeData: routeData}, context, callbackAddRouteRow);
};
/**
* show route dialog. User can search for systems and jump-info for each system is added to a data table
@@ -84,10 +150,23 @@ define([
*/
var showFindRouteDialog = function(dialogData){
var mapSelectOptions = [];
var currentMapData = Util.getCurrentMapData();
if(currentMapData !== false){
for(var i = 0; i < currentMapData.length; i++){
mapSelectOptions.push({
id: currentMapData[i].config.id,
name: currentMapData[i].config.name,
selected: (dialogData.mapId === currentMapData[i].config.id)
});
}
}
var data = {
id: config.routeDialogId,
selectClass: config.systemDialogSelectClass,
systemFrom: dialogData.systemFrom
mapSelectId: config.mapSelectId,
systemFrom: dialogData.systemFrom,
mapSelectOptions: mapSelectOptions
};
requirejs(['text!templates/dialog/route.html', 'mustache'], function(template, Mustache) {
@@ -98,8 +177,9 @@ define([
$.fn.modal.Constructor.prototype.enforceFocus = function() {};
var findRouteDialog = bootbox.dialog({
title: 'Search shortest route',
title: 'Route finder',
message: content,
show: false,
buttons: {
close: {
label: 'cancel',
@@ -130,55 +210,112 @@ define([
return false;
}
var requestRouteData = [{
systemFrom: dialogData.systemFrom,
systemTo: routeDialogData.systemTo
}];
var contextData = {
var context = {
moduleElement: dialogData.moduleElement,
dataTable: dialogData.dataTable
};
getRouteData(requestRouteData, contextData, callbackAddRouteRow);
var requestData = {
routeData: [{
mapIds: routeDialogData.mapIds,
systemFrom: dialogData.systemFrom,
systemTo: routeDialogData.systemTo,
stargates: routeDialogData.hasOwnProperty('stargates') ? parseInt( routeDialogData.stargates ) : 0,
jumpbridges: routeDialogData.hasOwnProperty('jumpbridges') ? parseInt( routeDialogData.jumpbridges ) : 0,
wormholes: routeDialogData.hasOwnProperty('wormholes') ? parseInt( routeDialogData.wormholes ) : 0,
wormholesReduced: routeDialogData.hasOwnProperty('wormholesReduced') ? parseInt( routeDialogData.wormholesReduced ) : 0,
wormholesCritical: routeDialogData.hasOwnProperty('wormholesCritical') ? parseInt( routeDialogData.wormholesCritical ) : 0
}]
};
getRouteData(requestData, context, callbackAddRouteRow);
}
}
}
});
findRouteDialog.on('show.bs.modal', function(e) {
findRouteDialog.initTooltips();
// init some dialog/form observer
setDialogObserver( $(this) );
// init map select ----------------------------------------------------------------
var mapSelect = $(this).find('#' + config.mapSelectId);
mapSelect.initMapSelect();
});
// init dialog
findRouteDialog.on('shown.bs.modal', function(e) {
var modalContent = $('#' + config.routeDialogId);
// init system select live search - some delay until modal transition has finished
var selectElement = modalContent.find('.' + config.systemDialogSelectClass);
selectElement.delay(240).initSystemSelect({key: 'name'});
// init system select live search ------------------------------------------------
// -> add some delay until modal transition has finished
var systemTargetSelect = $(this).find('.' + config.systemDialogSelectClass);
systemTargetSelect.delay(240).initSystemSelect({key: 'name'});
});
// show dialog
findRouteDialog.modal('show');
});
};
/**
* set event observer for route finder dialog
* @param routeDialog
*/
var setDialogObserver = function(routeDialog){
var wormholeCheckbox = routeDialog.find('input[type="checkbox"][name="wormholes"]');
var wormholeReducedCheckbox = routeDialog.find('input[type="checkbox"][name="wormholesReduced"]');
var wormholeCriticalCheckbox = routeDialog.find('input[type="checkbox"][name="wormholesCritical"]');
// store current "checked" state for each box ---------------------------------------------
var storeCheckboxStatus = function(){
wormholeReducedCheckbox.data('selectState', wormholeReducedCheckbox.prop('checked'));
wormholeCriticalCheckbox.data('selectState', wormholeCriticalCheckbox.prop('checked'));
};
// on wormhole checkbox change ------------------------------------------------------------
var onWormholeCheckboxChange = function(){
if( $(this).is(':checked') ){
wormholeReducedCheckbox.prop('disabled', false);
wormholeCriticalCheckbox.prop('disabled', false);
wormholeReducedCheckbox.prop('checked', wormholeReducedCheckbox.data('selectState'));
wormholeCriticalCheckbox.prop('checked', wormholeCriticalCheckbox.data('selectState'));
}else{
storeCheckboxStatus();
wormholeReducedCheckbox.prop('checked', false);
wormholeReducedCheckbox.prop('disabled', true);
wormholeCriticalCheckbox.prop('checked', false);
wormholeCriticalCheckbox.prop('disabled', true);
}
}.bind(wormholeCheckbox);
wormholeCheckbox.on('change', onWormholeCheckboxChange);
// initial checkbox check
storeCheckboxStatus();
onWormholeCheckboxChange();
};
/**
* requests route data from eveCentral API and execute callback
* @param requestRouteData
* @param contextData
* @param requestData
* @param context
* @param callback
*/
var getRouteData = function(requestRouteData, contextData, callback){
var getRouteData = function(requestData, context, callback){
var requestData = {routeData: requestRouteData};
contextData.moduleElement.showLoadingAnimation();
context.moduleElement.showLoadingAnimation();
$.ajax({
url: Init.path.searchRoute,
type: 'POST',
dataType: 'json',
data: requestData,
context: contextData
context: context
}).done(function(routesData){
this.moduleElement.hideLoadingAnimation();
@@ -192,11 +329,40 @@ define([
/**
* format route data from API request into dataTable row format
* @param routeData
* @returns {*[]}
* @returns {{}}
*/
var formatRouteData = function(routeData){
var tableRowData = {};
var reloadButton = '<i class="fa ' + ['fa-refresh'].join(' ') + '"></i>';
var deleteButton = '<i class="fa ' + ['fa-close', 'txt-color', 'txt-color-redDarker'].join(' ') + '"></i>';
// default row data (e.g. no route found)
var tableRowData = {
systemFrom: routeData.systemFrom,
systemTo: routeData.systemTo,
jumps: {
value: 0,
formatted: '---'
},
avgTrueSec: {
value: '',
formatted: ''
},
route: 'not found',
stargates: routeData.stargates,
jumpbridges: routeData.jumpbridges,
wormholes: routeData.wormholes,
wormholesReduced: routeData.wormholesReduced,
wormholesCritical: routeData.wormholesCritical,
reload: {
button: reloadButton
},
clear: {
button: deleteButton
},
maps: routeData.maps,
mapIds: routeData.mapIds //map data (mapIds is "redundant")
};
if(
routeData.routePossible === true &&
@@ -205,14 +371,14 @@ define([
// route data available
// add route Data
var rowData = [routeData.route[ routeData.route.length - 1 ].system.toLowerCase(), routeData.routeJumps];
var jumpData = [];
var avgSecTemp = 0;
// loop all systems on this route
for(var i = 0; i < routeData.route.length; i++){
var routeNodeData = routeData.route[i];
// format system name (camelCase)
var systemName = routeNodeData.system.charAt(0).toUpperCase() + routeNodeData.system.slice(1).toLowerCase();
var systemSec = Number(routeNodeData.security).toFixed(1).toString();
var tempSystemSec = systemSec;
@@ -225,23 +391,31 @@ define([
var system = '<i class="fa fa-square ' + systemSecClass + '" ';
system += 'data-toggle="tooltip" data-placement="bottom" data-container="body" ';
system += 'title="' + routeNodeData.system.toLowerCase() + ' [' + systemSec + '] "></i>';
system += 'title="' + systemName + ' [' + systemSec + '] "></i>';
jumpData.push( system );
avgSecTemp += Number(routeNodeData.security);
}
var avgSec = ( avgSecTemp / routeData.route.length).toFixed(2);
var avgSecClass = config.systemSecurityClassPrefix + ( avgSecTemp / routeData.route.length).toFixed(1).toString().replace('.', '-');
var avgSecForClass = Number(avgSec).toFixed(1);
if(avgSecForClass <= 0){
avgSecForClass = '0.0';
}
var avgSecClass = config.systemSecurityClassPrefix + avgSecForClass.toString().replace('.', '-');
tableRowData.jumps = {
value: routeData.routeJumps,
formatted: routeData.routeJumps
};
tableRowData.system = rowData[0];
tableRowData.jumps = rowData[1];
tableRowData.avgTrueSec = {
value: avgSec,
formatted: '<span class="' + avgSecClass + '">' + avgSec + '</span>'
};
tableRowData.route = jumpData.join(' ');
}
return tableRowData;
@@ -249,107 +423,202 @@ define([
/**
* get the route finder moduleElement
* @param systemData
* @returns {*}
*/
var getModule = function(systemData){
var getModule = function(){
var moduleElement = null;
// create new module container
var moduleElement = $('<div>', {
class: [config.moduleClass, config.systemRouteModuleClass].join(' ')
});
// load trade routes for k-space systems
if(systemData.type.id === 2){
// headline toolbar icons
var headlineToolbar = $('<h5>', {
class: 'pull-right'
}).append(
$('<i>', {
class: ['fa', 'fa-fw', 'fa-search', config.systemModuleHeadlineIcon, config.systemModuleHeadlineIconSearch].join(' '),
title: 'find&nbsp;route'
}).attr('data-html', 'true').attr('data-toggle', 'tooltip'),
$('<i>', {
class: ['fa', 'fa-fw', 'fa-refresh', config.systemModuleHeadlineIcon, config.systemModuleHeadlineIconRefresh].join(' '),
title: 'refresh&nbsp;all'
}).attr('data-html', 'true').attr('data-toggle', 'tooltip')
);
// create new module container
moduleElement = $('<div>', {
class: [config.moduleClass, config.systemRouteModuleClass].join(' ')
});
moduleElement.append(headlineToolbar);
// headline
var headline = $('<h5>', {
class: 'pull-left',
text: 'Routes'
});
// headline toolbar icons
var headlineToolbar = $('<h5>', {
class: 'pull-right'
}).append(
$('<i>', {
class: ['fa', 'fa-fw', 'fa-search', config.systemModuleHeadlineIcon].join(' '),
title: 'find route'
}).attr('data-toggle', 'tooltip')
);
moduleElement.append(headline);
moduleElement.append(headlineToolbar);
// crate new route table
var table = $('<table>', {
class: ['compact', 'stripe', 'order-column', 'row-border', config.systemInfoRoutesTableClass].join(' ')
});
// headline
var headline = $('<h5>', {
class: 'pull-left',
text: 'Routes'
});
moduleElement.append( $(table) );
moduleElement.append(headline);
// init tooltips
var tooltipElements = moduleElement.find('[data-toggle="tooltip"]');
tooltipElements.tooltip({
container: 'body'
});
// crate new route table
var table = $('<table>', {
class: ['compact', 'stripe', 'order-column', 'row-border', config.systemInfoRoutesTableClass].join(' ')
});
moduleElement.append( $(table) );
// init empty table
var routesTable = table.DataTable( {
paging: false,
ordering: true,
order: [ 1, 'asc' ],
info: false,
searching: false,
hover: false,
autoWidth: false,
language: {
emptyTable: 'No routes added'
},
columnDefs: [
{
targets: 0,
orderable: true,
title: 'system&nbsp;&nbsp;&nbsp;',
data: 'system'
},{
targets: 1,
orderable: true,
title: 'jumps&nbsp;&nbsp;&nbsp',
width: '40px',
class: 'text-right',
data: 'jumps'
},{
targets: 2,
orderable: true,
title: '&#216;&nbsp;&nbsp;&nbsp',
width: '25px',
class: 'text-right',
data: 'avgTrueSec',
render: {
_: 'formatted',
sort: 'value'
}
},{
targets: 3,
orderable: false,
title: 'route',
data: 'route'
// init empty table
var routesTable = table.DataTable( {
paging: false,
ordering: true,
order: [ 1, 'asc' ],
info: false,
searching: false,
hover: false,
autoWidth: false,
rowId: 'systemTo',
language: {
emptyTable: 'No routes added'
},
columnDefs: [
{
targets: 0,
orderable: true,
title: 'system&nbsp;&nbsp;&nbsp;',
data: 'systemTo'
},{
targets: 1,
orderable: true,
title: '<span title="jumps" data-toggle="tooltip"><i class="fa fa-arrows-h"></i>&nbsp;&nbsp;</span>',
width: '18px',
class: 'text-right',
data: 'jumps',
render: {
_: 'formatted',
sort: 'value'
}
],
data: [] // will be added dynamic
});
},{
targets: 2,
orderable: true,
title: '<span title="average security" data-toggle="tooltip">&#216;&nbsp;&nbsp;</span>',
width: '15px',
class: 'text-right',
data: 'avgTrueSec',
render: {
_: 'formatted',
sort: 'value'
}
},{
targets: 3,
orderable: false,
title: 'route',
data: 'route'
},{
targets: 4,
title: '',
orderable: false,
searchable: false,
width: '10px',
class: ['text-center', config.sigTableActionCellClass].join(' '),
data: 'reload',
render: {
_: 'button'
},
createdCell: function(cell, cellData, rowData, rowIndex, colIndex){
var tempTableApi = this.api();
}
$(cell).on('click', function(e) {
// get current row data (important!)
// -> "rowData" param is not current state, values are "on createCell()" state
rowData = tempTableApi.row( $(cell).parents('tr')).data();
var context = {
moduleElement: moduleElement,
dataTable: tempTableApi
};
var requestData = {
routeData: [{
mapIds: rowData.mapIds,
systemFrom: rowData.systemFrom,
systemTo: rowData.systemTo,
stargates: rowData.stargates ? 1 : 0,
jumpbridges: rowData.jumpbridges ? 1 : 0,
wormholes: rowData.wormholes ? 1 : 0,
wormholesReduced: rowData.wormholesReduced ? 1 : 0,
wormholesCritical: rowData.wormholesCritical ? 1 : 0
}]
};
getRouteData(requestData, context, callbackAddRouteRow);
});
}
},{
targets: 5,
title: '',
orderable: false,
searchable: false,
width: '10px',
class: ['text-center', config.sigTableActionCellClass].join(' '),
data: 'clear',
render: {
_: 'button'
},
createdCell: function(cell, cellData, rowData, rowIndex, colIndex){
var tempTableElement = this;
var confirmationSettings = {
container: 'body',
placement: 'left',
btnCancelClass: 'btn btn-sm btn-default',
btnCancelLabel: 'cancel',
btnCancelIcon: 'fa fa-fw fa-ban',
title: 'delete route',
btnOkClass: 'btn btn-sm btn-danger',
btnOkLabel: 'delete',
btnOkIcon: 'fa fa-fw fa-close',
onConfirm : function(e, target){
var deleteRowElement = $(cell).parents('tr');
tempTableElement.api().rows(deleteRowElement).remove().draw();
}
};
// init confirmation dialog
$(cell).confirmation(confirmationSettings);
}
}
],
drawCallback: function(settings){
var animationRows = this.api().rows().nodes().to$().filter(function() {
return (
$(this).data('animationStatus') ||
$(this).data('animationTimer')
);
});
for(var i = 0; i < animationRows.length; i++){
$(animationRows[i]).pulseTableRow($(animationRows[i]).data('animationStatus'));
$(animationRows[i]).removeData('animationStatus');
}
},
data: [] // will be added dynamic
});
// init tooltips for this module
var tooltipElements = moduleElement.find('[data-toggle="tooltip"]');
tooltipElements.tooltip({
container: 'body'
});
return moduleElement;
};
var initModule = function(moduleElement, systemData){
/**
* init route module
* -> request route path fore "default" trade hub systems
* @param moduleElement
* @param mapId
* @param systemData
*/
var initModule = function(moduleElement, mapId, systemData){
var systemFrom = systemData.name;
var systemsTo = ['Jita', 'Amarr', 'Rens', 'Dodixie'];
@@ -358,13 +627,16 @@ define([
var routesTable = routesTableElement.DataTable();
// init system search dialog -------------------------------------------------------------------------------
moduleElement.find('.' + config.systemModuleHeadlineIcon).on('click', function(e){
// show "find route" dialog
// init refresh routes --------------------------------------------------------------------
moduleElement.find('.' + config.systemModuleHeadlineIconRefresh).on('click', function(e){
updateRoutesTable(moduleElement, routesTable);
});
// init search routes dialog --------------------------------------------------------------
moduleElement.find('.' + config.systemModuleHeadlineIconSearch).on('click', function(e){
var dialogData = {
moduleElement: moduleElement,
mapId: mapId,
systemFrom: systemFrom,
dataTable: routesTable
};
@@ -372,23 +644,40 @@ define([
showFindRouteDialog(dialogData);
});
// fill routesTable with data ------------------------------------------------------------------------------
// fill routesTable with data -------------------------------------------------------------
var requestRouteData = [];
var currentTimestamp = Util.getServerTime().getTime();
for(var i = 0; i < systemsTo.length; i++){
var systemTo = systemsTo[i];
if(systemFrom !== systemTo){
var cacheKey = systemFrom.toUpperCase() + '_' + systemTo.toUpperCase();
var cacheKey = 'route_' + mapId + '_' + systemFrom.toUpperCase() + '_' + systemTo.toUpperCase();
if(cache.systemRoutes.hasOwnProperty(cacheKey)){
addRow(routesTable, cache.systemRoutes[cacheKey]);
if(
cache.systemRoutes.hasOwnProperty(cacheKey) &&
Math.round(
( currentTimestamp - (new Date( cache.systemRoutes[cacheKey].updated * 1000).getTime())) / 1000
) <= config.routeCacheTTL
){
// route data is cached (client side)
var context = {
dataTable: routesTable
};
addRow(context, cache.systemRoutes[cacheKey].data);
}else{
// get route data
requestRouteData.push({
mapIds: [mapId],
systemFrom: systemFrom,
systemTo: systemTo
systemTo: systemTo,
stargates: 1,
jumpbridges: 1,
wormholes: 1,
wormholesReduced: 1,
wormholesCritical: 1
});
}
}
@@ -400,16 +689,22 @@ define([
moduleElement: moduleElement,
dataTable: routesTable
};
getRouteData(requestRouteData, contextData, callbackAddRouteRow);
var requestData = {
routeData: requestRouteData
};
getRouteData(requestData, contextData, callbackAddRouteRow);
}
};
/**
* updates an dom element with the system route module
* @param mapId
* @param systemData
*/
$.fn.drawSystemRouteModule = function(systemData){
$.fn.drawSystemRouteModule = function(mapId, systemData){
var parentElement = $(this);
@@ -423,7 +718,7 @@ define([
duration: Init.animationSpeed.mapModule,
delay: Init.animationSpeed.mapModule,
complete: function(){
initModule(moduleElement, systemData);
initModule(moduleElement, mapId, systemData);
}
});
}
@@ -438,12 +733,12 @@ define([
complete: function(tempElement){
$(tempElement).remove();
moduleElement = getModule(systemData);
moduleElement = getModule();
showModule(moduleElement);
}
});
}else{
moduleElement = getModule(systemData);
moduleElement = getModule();
showModule(moduleElement);
}

View File

@@ -43,16 +43,10 @@ define([
sigTableEditSigDescriptionTextarea: 'pf-sig-table-edit-desc-text', // class for editable fields (sig description)
sigTableCreatedCellClass: 'pf-sig-table-created', // class for "created" cells
sigTableUpdatedCellClass: 'pf-sig-table-updated', // class for "updated" cells
sigTableActionButtonClass: 'pf-sig-table-action-button', // class for row action button
sigTableCounterClass: 'pf-table-counter-cell', // class for "counter" cells
sigTableActionCellClass: 'pf-table-action-cell', // class for "action" cells
// animation
animationPulseSuccessClass: 'pf-animation-pulse-success', // animation class
animationPulseWarningClass: 'pf-animation-pulse-warning', // animation class
// xEditable
editableDiscriptionInputClass: 'pf-editable-description' // class for "description" textarea
};
@@ -184,7 +178,7 @@ define([
var signatureData = $.extend([], signatureDataOrig);
// disable update until function is ready;
disableTableUpdate = true;
lockSignatureTable();
var moduleElement = $(this);
@@ -207,7 +201,7 @@ define([
for(var j = 0; j < tableData.length; j++){
if(signatureData[i].id === tableData[j].id){
// check if row has updated
// check if row was updated
if(signatureData[i].updated.updated > tableData[j].updated.updated){
// row element to remove
@@ -241,7 +235,7 @@ define([
}
}
// delete signatures ====================================================
// delete signatures ------------------------------------------------------------------------------------------
if(deleteOutdatedSignatures === true){
// callback function after row deleted
@@ -260,7 +254,7 @@ define([
}
}
// add new signatures ===================================================
// add new signatures -----------------------------------------------------------------------------------------
for(var k = 0; k < signatureData.length; k++){
// and add "new" row
var newRowElement = addSignatureRow(currentSystemData.systemData, signatureData[k], false);
@@ -272,7 +266,7 @@ define([
}
// show notification ====================================================
// show notification ------------------------------------------------------------------------------------------
if(
notificationCounter.added > 0 ||
notificationCounter.changed > 0 ||
@@ -288,43 +282,35 @@ define([
Util.showNotify({title: 'Signatures updated', text: notification, type: 'success'});
// wait until add/remove animations are finished before enable table for auto update again
setTimeout(function(){ disableTableUpdate = false; }, 2000);
unlockSignatureTable(false);
}else{
// enable table update
disableTableUpdate = false;
// enable table for next update
unlockSignatureTable(true);
}
};
/**
* highlight jquery elements
* add/remove css class for keyframe animation
* @returns {any|JQuery|*}
* lock system signature table for
*/
$.fn.pulseTableRow = function(status){
var lockSignatureTable = function(){
disableTableUpdate = true;
};
/**
* unlock system signature table from been locked
* -> make table "update-able" again
* @param instant
*/
var unlockSignatureTable = function(instant){
if(disableTableUpdate === true){
if(instant === true){
disableTableUpdate = false;
}else{
// wait until add/remove animations are finished before enable table for auto update again
setTimeout(function(){ disableTableUpdate = false; }, 2000);
}
var animationClass = '';
switch(status){
case 'added':
animationClass = config.animationPulseSuccessClass;
break;
case 'changed':
animationClass = config.animationPulseWarningClass;
break;
}
return this.each(function(){
var element = $(this);
element.addClass( animationClass );
var timer = setTimeout(
function() {
element.removeClass( animationClass );
clearTimeout( timer );
}, 3000);
});
};
/**
@@ -463,7 +449,7 @@ define([
// save signature data
// lock update function until request is finished
disableTableUpdate = true;
lockSignatureTable();
// lock copy during request (prevent spamming (ctrl + c )
disableCopyFromClipboard = true;
@@ -478,7 +464,7 @@ define([
data: requestData,
dataType: 'json'
}).done(function(responseData){
disableTableUpdate = false;
unlockSignatureTable(true);
// updates table with new/updated signature information
moduleElement.updateSignatureTable(responseData.signatures, false);
@@ -487,7 +473,7 @@ define([
Util.showNotify({title: jqXHR.status + ': Update signatures', text: reason, type: 'warning'});
$(document).setProgramStatus('problem');
}).always(function() {
disableTableUpdate = false;
unlockSignatureTable(true);
disableCopyFromClipboard = false;
});
}
@@ -626,7 +612,7 @@ define([
var moduleElement = $(this);
// add toolbar buttons for table -------------------------------------
// add toolbar buttons for table ------------------------------------------------------------------------------
var tableToolbar = $('<div>', {
class: config.tableToolsClass
}).append(
@@ -707,7 +693,7 @@ define([
moduleElement.append(tableToolbar);
// add toolbar action for table ================================================================================
// add toolbar action for table -------------------------------------------------------------------------------
var tableToolbarAction = $('<div>', {
class: config.tableToolsActionClass
});
@@ -732,7 +718,7 @@ define([
table.makeEditable(systemData);
// scanned signatures progress bar =============================================================================
// scanned signatures progress bar ----------------------------------------------------------------------------
var moduleConfig = {
name: 'form/progress',
position: tableToolbar,
@@ -750,7 +736,7 @@ define([
Render.showModule(moduleConfig, moduleData);
// event listener for global "paste" signatures into the page ==================================================
// event listener for global "paste" signatures into the page -------------------------------------------------
$(document).off('paste').on('paste', function(e){
// do not read clipboard if pasting into form elements
@@ -766,7 +752,7 @@ define([
/**
* make a table or row editable
* @param systemInfoData
* @param systemData
*/
$.fn.makeEditable = function(systemData){
@@ -843,7 +829,7 @@ define([
});
// Input sig name ------------------------------------------------------------------------
// Input sig name ---------------------------------------------------------------------------------------------
sigNameFields.editable({
type: 'text',
title: 'signature id',
@@ -870,7 +856,7 @@ define([
});
// Select sig group (master) -------------------------------------------------------------
// Select sig group (master) ----------------------------------------------------------------------------------
sigGroupFields.editable({
type: 'select',
title: 'group',
@@ -905,29 +891,37 @@ define([
updateSignatureCell(rowElement, 6, newRowData.updated);
}
// find related "name" select (same row) and change options
var nameSelect = getNextEditableField(signatureTypeField);
// find related "type" select (same row) and change options
var typeSelect = getNextEditableField(signatureTypeField);
var systemTypeId = parseInt( signatureTypeField.attr('data-systemTypeId') );
var areaId = parseInt( signatureTypeField.attr('data-areaid') );
var newSelectOptions = getAllSignatureNames(systemData, systemTypeId, areaId, newValue);
nameSelect.editable('option', 'source', newSelectOptions);
nameSelect.editable('setValue', null);
typeSelect.editable('option', 'source', newSelectOptions);
typeSelect.editable('setValue', null);
if(
newValue > 0 &&
newSelectOptions.length > 0
){
nameSelect.editable('enable');
typeSelect.editable('enable');
}else{
nameSelect.editable('disable');
typeSelect.editable('disable');
}
}
});
// Select sig type (slave: depends on sig type) -----------------------------------------
// Select sig type (slave: depends on sig type) ---------------------------------------------------------------
sigTypeFields.on('init', function(e, editable) {
// check if there are initial options available
var options = editable.input.options.source.bind(e.target)();
if(options.length <= 0){
editable.disable();
}
});
sigTypeFields.editable({ mode: 'popup',
type: 'select',
title: 'type',
@@ -937,23 +931,12 @@ define([
showbuttons: false,
params: modifyFieldParamsOnSend,
source: function(){
var signatureNameField = $(this);
var signatureTypeField = $(this);
var systemTypeId = parseInt( signatureNameField.attr('data-systemTypeId') );
var areaId = parseInt( signatureNameField.attr('data-areaid') );
var groupId = parseInt( signatureNameField.attr('data-groupId') );
var cacheKey = [systemTypeId, areaId, groupId].join('_');
// check for cached signature names
if(sigNameCache.hasOwnProperty( cacheKey )){
return sigNameCache[cacheKey];
}
var signatureNames = getAllSignatureNames(systemData, systemTypeId, areaId, groupId);
// get all available Signature Names
var availableSigs = sigNameCache[cacheKey] = signatureNames;
var systemTypeId = parseInt( signatureTypeField.attr('data-systemTypeId') );
var areaId = parseInt( signatureTypeField.attr('data-areaid') );
var groupId = parseInt( signatureTypeField.attr('data-groupId') );
var availableSigs = getAllSignatureNames(systemData, systemTypeId, areaId, groupId);
return availableSigs;
},
@@ -969,7 +952,7 @@ define([
}
});
// Textarea sig description -------------------------------------------------------------
// Textarea sig description -----------------------------------------------------------------------------------
sigDescriptionFields.editable({
type: 'textarea',
title: 'description',
@@ -1004,7 +987,7 @@ define([
tableElement.parents('.' + config.tableToolsActionClass).css( 'height', '-=35px' );
});
// open next field dialog ---------------------------------------------------------------
// open next field dialog -------------------------------------------------------------------------------------
openNextEditDialogOnSave(sigNameFields);
openNextEditDialogOnSave(sigGroupFields);
};
@@ -1019,99 +1002,146 @@ define([
*/
var getAllSignatureNames = function(systemData, systemTypeId, areaId, groupId){
var newSelectOptions = [];
var cacheKey = [systemTypeId, areaId, groupId].join('_');
var newSelectOptionsCount = 0;
// set new Options ----------
// get all possible "static" signature names by the selected groupId
var tempSelectOptions = Util.getAllSignatureNames(systemTypeId, areaId, groupId);
// check for cached signature names
if(sigNameCache.hasOwnProperty( cacheKey )){
// cached signatures do not include static WHs!
newSelectOptions = sigNameCache[cacheKey].slice(0);
newSelectOptionsCount = sumSignaturesRecursive('children', newSelectOptions);
}else{
// get new Options ----------
// get all possible "static" signature names by the selected groupId
var tempSelectOptions = Util.getAllSignatureNames(systemTypeId, areaId, groupId);
// format options into array with objects advantages: keep order, add more options (whs), use optgroup
if(tempSelectOptions){
var fixSelectOptions = [];
for (var key in tempSelectOptions) {
if (
key > 0 &&
tempSelectOptions.hasOwnProperty(key)
) {
//newSelectOptions.push({value: key, text: tempSelectOptions[key] });
fixSelectOptions.push( {value: key, text: tempSelectOptions[key] } );
newSelectOptionsCount++;
// format options into array with objects advantages: keep order, add more options (whs), use optgroup
if(tempSelectOptions){
var fixSelectOptions = [];
for (var key in tempSelectOptions) {
if (
key > 0 &&
tempSelectOptions.hasOwnProperty(key)
) {
newSelectOptionsCount++;
fixSelectOptions.push( {value: key, text: tempSelectOptions[key] } );
}
}
if(newSelectOptionsCount > 0){
if(groupId === 5){
// "wormhole" selected => multiple <optgroup> available
newSelectOptions.push({ text: 'Wandering WHs', children: fixSelectOptions});
}else{
newSelectOptions = fixSelectOptions;
}
}
}
if(newSelectOptionsCount > 0){
if(groupId === 5){
// "wormhole" selected => multiple <optgroup> available
newSelectOptions.push({ text: 'Wandering WHs', children: fixSelectOptions});
}else{
newSelectOptions = fixSelectOptions;
// wormhole (cached signatures)
if( groupId === 5 ){
// add possible frigate holes
var frigateHoles = getFrigateHolesBySystem(areaId);
var frigateWHData = [];
for(var frigKey in frigateHoles){
if (
frigKey > 0 &&
frigateHoles.hasOwnProperty(frigKey)
) {
newSelectOptionsCount++;
frigateWHData.push( {value: newSelectOptionsCount, text: frigateHoles[frigKey]} );
}
}
if(frigateWHData.length > 0){
newSelectOptions.push({ text: 'Frigate WHs', children: frigateWHData});
}
// add possible incoming holes
var incomingWHData = [];
for(var incomingKey in Init.incomingWormholes){
if (
incomingKey > 0 &&
Init.incomingWormholes.hasOwnProperty(incomingKey)
) {
newSelectOptionsCount++;
incomingWHData.push( {value: newSelectOptionsCount, text: Init.incomingWormholes[incomingKey]} );
}
}
if(incomingWHData.length > 0){
newSelectOptions.push({ text: 'Incoming WHs', children: incomingWHData});
}
}else{
// groups without "children" (optgroup) should be sorted by "value"
// this is completely optional and not necessary!
newSelectOptions = newSelectOptions.sortBy('text');
}
// update cache (clone array) -> further manipulation to this array, should not be cached
sigNameCache[cacheKey] = newSelectOptions.slice(0);
}
// wormhole
// static wormholes (DO NOT CACHE) (not all C2 WHs have the same statics,...
if( groupId === 5 ){
// add static WH(s) for this system
if(systemData.statics){
var staticWHData = [];
for(var i = 0; i < systemData.statics.length; i++){
newSelectOptionsCount++;
var staticWHName = systemData.statics[i].name + ' - ' + systemData.statics[i].security;
staticWHData.push( {value: newSelectOptionsCount, text: staticWHName} );
newSelectOptionsCount++;
staticWHData.push( {value: newSelectOptionsCount, text: staticWHName} );
}
if(staticWHData.length > 0){
newSelectOptions.unshift({ text: 'Static WHs', children: staticWHData});
}
}
// add possible frigate holes
var frigateHoles = getFrigateHolesBySystem(areaId);
var frigateWHData = [];
for(var frigKey in frigateHoles){
if (
frigKey > 0 &&
frigateHoles.hasOwnProperty(frigKey)
) {
frigateWHData.push( {value: newSelectOptionsCount, text: frigateHoles[frigKey]} );
newSelectOptionsCount++;
}
}
if(frigateWHData.length > 0){
newSelectOptions.push({ text: 'Frigate WHs', children: frigateWHData});
}
// add possible incoming holes
var incomingWHData = [];
for(var incomingKey in Init.incomingWormholes){
if (
incomingKey > 0 &&
Init.incomingWormholes.hasOwnProperty(incomingKey)
) {
incomingWHData.push( {value: newSelectOptionsCount, text: Init.incomingWormholes[incomingKey]} );
newSelectOptionsCount++;
}
}
if(incomingWHData.length > 0){
newSelectOptions.push({ text: 'Incoming WHs', children: incomingWHData});
}
}
// if selectOptions available -> add "empty" option as well
if(newSelectOptionsCount > 0){
newSelectOptions.unshift({ value: 0, text: ''});
newSelectOptions.unshift({ value: '0', text: ''});
}
return newSelectOptions;
};
/**
* recursive sum array.length for a given object key
* -> e.g.
* {
* first: {
* count = [4, 2, 1]
* test = { ... }
* },
* second: {
* count = [12, 13]
* test = { ... }
* }
* }
* -> sumSignaturesRecursive('count', obj) => 5;
* @param key
* @param obj
* @returns {number}
*/
var sumSignaturesRecursive = function(key, obj){
var sum = 0;
for (var prop in obj) {
if (obj.hasOwnProperty(prop) && key === prop) {
sum += obj[prop].length;
}
else if (Object.prototype.toString.call(obj[prop]) === '[object Object]') {
sum += sumSignaturesRecursive(key, obj[prop]);
}
}
return sum;
};
/**
* get possible frig holes that could spawn in a system
* filtered by "systemTypeId"
@@ -1334,7 +1364,7 @@ define([
var moduleElement = $(this);
// create new signature table -------------------------------------------
// create new signature table ---------------------------------------------------------------------------------
var table = $('<table>', {
class: ['display', 'compact', 'nowrap', config.sigTableClass, config.sigTablePrimaryClass].join(' ')
});
@@ -1382,27 +1412,26 @@ define([
var tempData = {};
// set id ------------------------------------------------------------------------------------------
// set id ---------------------------------------------------------------------------------------------
var sigId = 0;
if(data.id > 0){
sigId = data.id;
}
tempData.id = sigId;
// set status --------------------------------------------------------------------------------------
var status = '';
// set status -----------------------------------------------------------------------------------------
var statusClass = '';
if(data.updated.character !== undefined){
statusClass = Util.getStatusInfoForCharacter(data.updated.character, 'class');
}
status = '<i class="fa fa-fw fa-circle pf-user-status ' + statusClass + '"></i>';
var status = '<i class="fa fa-fw fa-circle pf-user-status ' + statusClass + '"></i>';
tempData.status = {
status: status,
status_sort: statusClass
};
// set name ----------------------------------------------------------------------------------------
// set name -------------------------------------------------------------------------------------------
var sigName = '<a href="#" class="' + config.sigTableEditSigNameInput + '" ';
if(data.id > 0){
sigName += 'data-pk="' + data.id + '" ';
@@ -1411,7 +1440,7 @@ define([
tempData.name = sigName;
// set group id ------------------------------------------------------------------------------------
// set group id ---------------------------------------------------------------------------------------
var sigGroup = '<a href="#" class="' + config.sigTableEditSigGroupSelect + '" ';
if(data.id > 0){
sigGroup += 'data-pk="' + data.id + '" ';
@@ -1426,7 +1455,7 @@ define([
group_sort: data.groupId
};
// set type id -------------------------------------------------------------------------------------
// set type id ----------------------------------------------------------------------------------------
var sigType = '<a href="#" class="' + config.sigTableEditSigTypeSelect + '" ';
if(data.id > 0){
sigType += 'data-pk="' + data.id + '" ';
@@ -1445,7 +1474,7 @@ define([
tempData.type = sigType;
// set description ---------------------------------------------------------------------------------
// set description ------------------------------------------------------------------------------------
var sigDescription = '<a href="#" class="' + config.sigTableEditSigDescriptionTextarea + '" ';
if(data.id > 0){
sigDescription += 'data-pk="' + data.id + '" ';
@@ -1454,22 +1483,22 @@ define([
tempData.description = sigDescription;
// set created -------------------------------------------------------------------------------------
// set created ----------------------------------------------------------------------------------------
tempData.created = data.created;
// set updated -------------------------------------------------------------------------------------
// set updated ----------------------------------------------------------------------------------------
tempData.updated = data.updated;
// info icon ---------------------------------------------------------------------------------------
// info icon ------------------------------------------------------------------------------------------
var infoButton = '';
if(data.id > 0){
infoButton = '<i class="fa fa-fw fa-question-circle"></i>';
}
tempData.info = infoButton;
// action icon -------------------------------------------------------------------------------------
// action icon ----------------------------------------------------------------------------------------
var actionButton = '<i class="fa ' + options.actionClass + ' ' + config.sigTableActionButtonClass + '"></i>';
var actionButton = '<i class="fa ' + options.actionClass + '"></i>';
tempData.action = {
action: options.action,
button: actionButton
@@ -1623,22 +1652,28 @@ define([
sort: 'action'
},
createdCell: function(cell, cellData, rowData, rowIndex, colIndex){
var tempTableElement = this;
var rowElement = $(cell).parents('tr');
switch(cellData.action){
case 'add':
// add new signature ---------------------------------------------------------------
// add new signature ------------------------------------------------------------------
$(cell).on('click', function(e) {
// submit all fields within a table row
var formFields = rowElement.find('.editable');
// the "toggle" makes sure to take care about open editable fields (e.g. description)
// otherwise, changes would not be submitted in this field (not necessary)
formFields.editable('toggle');
// submit all xEditable fields
formFields.editable('submit', {
url: Init.path.saveSignatureData,
ajaxOptions: {
dataType: 'json' //assuming json response
dataType: 'json', //assuming json response
beforeSend: function( xhr, settings ){
lockSignatureTable();
}
},
data: {
systemId: systemData.id, // additional data to submit
@@ -1646,13 +1681,14 @@ define([
},
error: $.fn.editable.defaults.error, // user default xEditable error function
success: function (data, editableConfig) {
unlockSignatureTable(false);
var newRowElement = addSignatureRow(systemData, data.signatures[0], true);
// highlight
newRowElement.pulseTableRow('added');
// prepare "add signature" table for new entry -> reset --------------------------------------------
// prepare "add signature" table for new entry -> reset -------------------
var signatureData = formatSignatureData(systemData, [emptySignatureData], emptySignatureOptions);
var dataSecondaryElement = $('.' + config.sigTableSecondaryClass);
@@ -1671,14 +1707,14 @@ define([
});
break;
case 'delete':
// delete signature ----------------------------------------------------------------
// delete signature -------------------------------------------------------------------
var confirmationSettings = {
container: 'body',
placement: 'left',
btnCancelClass: 'btn btn-sm btn-default',
btnCancelLabel: 'cancel',
btnCancelIcon: 'fa fa-fw fa-ban',
title: 'Delete signature',
title: 'delete signature',
btnOkClass: 'btn btn-sm btn-danger',
btnOkLabel: 'delete',
btnOkIcon: 'fa fa-fw fa-close',
@@ -1725,7 +1761,7 @@ define([
});
// set multi row select -----------------------------------------------------------------
// set multi row select ---------------------------------------------------------------------------------------
tablePrimaryElement.on('click', 'tr', function(e){
if(e.ctrlKey) {
$(this).toggleClass('selected');
@@ -1735,7 +1771,7 @@ define([
}
});
// draw event for signature table -------------------------------------------------------
// draw event for signature table -----------------------------------------------------------------------------
signatureTableApi.on('draw.dt', function(){
// check delete button
checkDeleteSignaturesButton();
@@ -1834,11 +1870,11 @@ define([
// init dataTables
initSignatureDataTable(systemData);
// draw "new signature" add table --------------------------------------------
// draw "new signature" add table -----------------------------------------------------------------------------
moduleElement.drawSignatureTableToolbar(systemData);
// request signature data for system -----------------------------------------
// request signature data for system --------------------------------------------------------------------------
var requestData = {
systemIds: [systemData.id]
@@ -1882,20 +1918,33 @@ define([
duration: Init.animationSpeed.mapModule,
delay: Init.animationSpeed.mapModule,
complete: function(){
disableTableUpdate = false;
unlockSignatureTable(true);
}
});
}
};
// some custom array functions
var initArrayFunctions = function(){
/**
* sort array of objects by property name
* @param p
* @returns {Array.<T>}
*/
Array.prototype.sortBy = function(p) {
return this.slice(0).sort(function(a,b) {
return (a[p] > b[p]) ? 1 : (a[p] < b[p]) ? -1 : 0;
});
};
};
// check if module already exists
var moduleElement = parentElement.find('.' + config.systemSigModuleClass);
if(moduleElement.length > 0){
// disable update
disableTableUpdate = true;
lockSignatureTable();
moduleElement.velocity('transition.slideDownOut', {
duration: Init.animationSpeed.mapModule,
@@ -1913,6 +1962,9 @@ define([
}
});
}else{
// init array prototype functions
initArrayFunctions();
moduleElement = getModule(parentElement, systemData);
showModule(moduleElement);
}

View File

@@ -41,12 +41,17 @@ define([
// map module
mapModuleId: 'pf-map-module', // id for main map module
mapTabBarId: 'pf-map-tabs' // id for map tab bar
mapTabBarId: 'pf-map-tabs', // id for map tab bar
// animation
animationPulseSuccessClass: 'pf-animation-pulse-success', // animation class
animationPulseWarningClass: 'pf-animation-pulse-warning' // animation class
};
var stopTimerCache = {}; // cache for stopwatch timer
var animationTimerCache = {}; // cache for table row animation timeout
/*
* ===========================================================================================================
@@ -317,15 +322,17 @@ define([
/**
* init form elements for validation (bootstrap3 validation)
* @returns {any|JQuery|*}
* @param options
* @returns {*}
*/
$.fn.initFormValidation = function(){
$.fn.initFormValidation = function(options){
options = (typeof options === 'undefined')? {} : options;
return this.each(function(){
var form = $(this);
// init form validation
form.validator();
form.validator(options);
// validation event listener
form.on('valid.bs.validator', function(validatorObj){
@@ -342,7 +349,6 @@ define([
inputGroup.removeClass('has-success').addClass('has-error');
}
});
});
};
@@ -458,24 +464,27 @@ define([
lineWidth: 2,
animate: 1000
});
};
/**
* init tooltips on an element
* @returns {any|JQuery|*}
* @param {object} options
* @returns {*}
*/
$.fn.initTooltips = function(){
$.fn.initTooltips = function(options){
options = (typeof options === 'object') ? options : {};
var defaultOptions = {
container: this,
delay: 100
};
options = $.extend(defaultOptions, options);
return this.each(function(){
var tooltipElements = $(this).find('[title]');
tooltipElements.tooltip({
container: this,
delay: 100
});
tooltipElements.tooltip('destroy').tooltip(options);
});
};
/**
@@ -545,6 +554,84 @@ define([
}
};
/**
* add character switch popover
* @param userData
*/
$.fn.initCharacterSwitchPopover = function(userData){
var elements = $(this);
var eventNamespace = 'hideCharacterPopup';
requirejs(['text!templates/tooltip/character_switch.html', 'mustache'], function (template, Mustache) {
var data = {
routes: Init.routes,
userData: userData,
otherCharacters: $.grep( userData.characters, function( character ) {
// exclude current active character
return character.id !== userData.character.id;
})
};
var content = Mustache.render(template, data);
return elements.each(function() {
var element = $(this);
// destroy "popover" and remove "click" event for animation
element.popover('destroy').off();
// init popover and add specific class to it (for styling)
element.popover({
html: true,
title: 'select character',
trigger: 'click',
placement: 'bottom',
content: content,
animation: false
}).data('bs.popover').tip().addClass('pf-character-info-popover');
element.on('click', function(e) {
e.preventDefault();
var easeEffect = $(this).attr('data-easein');
var popover = $(this).data('bs.popover').tip();
var velocityOptions = {
duration: CCP.isInGameBrowser() ? 0 : Init.animationSpeed.dialogEvents
};
switch(easeEffect){
case 'shake':
case 'pulse':
case 'tada':
case 'flash':
case 'bounce':
case 'swing':
popover.velocity('callout.' + easeEffect, velocityOptions);
break;
default:
popover.velocity('transition.' + easeEffect, velocityOptions);
break;
}
});
// hide popup on clicking "somewhere" outside
$('body').off('click.' + eventNamespace).on('click.' + eventNamespace, function (e) {
//the 'is' for buttons that trigger popups
//the 'has' for icons within a button that triggers a popup
if (
!$(element).is(e.target) &&
$(element).has(e.target).length === 0 &&
$('.popover').has(e.target).length === 0
){
$(element).popover('hide');
}
});
});
});
};
/**
* add a wormhole tooltip with wh specific data to elements
* @param tooltipData
@@ -575,7 +662,6 @@ define([
var popover = element.data('bs.popover');
popover.options.content = content;
});
});
};
@@ -649,6 +735,52 @@ define([
});
};
/**
* highlight jquery elements
* add/remove css class for keyframe animation
* @returns {any|JQuery|*}
*/
$.fn.pulseTableRow = function(status, clear){
var animationClass = '';
switch(status){
case 'added':
animationClass = config.animationPulseSuccessClass;
break;
case 'changed':
animationClass = config.animationPulseWarningClass;
break;
}
var clearTimer = function(element) {
element.removeClass( animationClass );
var currentTimer = element.data('animationTimer');
if( animationTimerCache.hasOwnProperty(currentTimer) ){
clearTimeout( currentTimer );
delete animationTimerCache[currentTimer];
element.removeData('animationTimer');
}
};
return this.each(function(){
var element = $(this);
if( element.hasClass(animationClass) ){
// clear timer -> set new timer
clearTimer(element);
}
if(clear !== true){
element.addClass( animationClass );
var timer = setTimeout(clearTimer, 1500, element);
element.data('animationTimer', timer);
animationTimerCache[timer] = true;
}
});
};
/*
* ===========================================================================================================
* Util functions that are global available for all modules
@@ -1129,7 +1261,6 @@ define([
* @returns {string}
*/
var getSystemEffectTable = function(data){
var table = '';
if(data.length > 0){
@@ -1500,15 +1631,10 @@ define([
if(
currentUserData &&
currentUserData.character
currentUserData.character &&
currentUserData.character.log
){
if(currentUserData.character.log){
characterLog = currentUserData.character.log;
}else if(CCP.isInGameBrowser() === true){
// if user is IGB online and log data is missing
// -> character API information not found!
showNotify({title: 'Character not found', text: 'Enter API information', type: 'error'});
}
characterLog = currentUserData.character.log;
}
return characterLog;
@@ -1602,7 +1728,7 @@ define([
* @returns {Date}
*/
var convertDateToUTC = function(date) {
return new Date(date.getUTCFullYear(), date.getUTCMonth(), date.getUTCDate(), date.getUTCHours(), date.getUTCMinutes(), date.getUTCSeconds());
return new Date(date.getUTCFullYear(), date.getUTCMonth() + 1, date.getUTCDate(), date.getUTCHours(), date.getUTCMinutes(), date.getUTCSeconds());
};
/**
@@ -1611,7 +1737,7 @@ define([
* @returns {string}
*/
var convertDateToString = function(date){
var dateString = ('0'+date.getDate()).slice(-2) + '.' + ('0'+date.getMonth()).slice(-2) + '.' + date.getFullYear();
var dateString = ('0'+date.getMonth()).slice(-2) + '/' + ('0'+date.getDate()).slice(-2) + '/' + date.getFullYear();
var timeString = ('0'+date.getHours()).slice(-2) + ':' + ('0'+date.getMinutes()).slice(-2);
return dateString + ' ' + timeString;
};
@@ -1650,7 +1776,7 @@ define([
$.ajax({
type: 'POST',
url: Init.path.logOut,
url: Init.path.logout,
data: data,
dataType: 'json'
}).done(function(data){

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -7,24 +7,25 @@
"description": "Pathfinder is a system mapping tool for EVE ONLINE",
"main": "index.php",
"dependencies": {
"requirejs": "^2.1.20"
},
"devDependencies": {
"critical": "^0.7.0",
"gulp": "^3.9.0",
"gulp-clean": "^0.3.1",
"gulp-gzip": "^1.2.0",
"gulp-if": "^2.0.0",
"gulp-jshint": "^1.11.2",
"gulp-notify": "^2.2.0",
"gulp-param": "^0.6.3",
"gulp-plumber": "^1.0.1",
"jshint": "^2.9.1-rc1",
"jshint-stylish": "^2.0.1",
"run-sequence": "^1.1.2"
"critical": "^0.x",
"gulp": "^3.9.x",
"gulp-clean": "^0.3.x",
"gulp-gzip": "^1.x.x",
"gulp-if": "^2.0.x",
"gulp-jshint": "^2.0.x",
"gulp-notify": "^2.2.x",
"gulp-param": "^1.0.x",
"gulp-plumber": "^1.x.x",
"jshint": "^2.9.x",
"jshint-stylish": "^2.x.x",
"requirejs": "^2.x.x",
"run-sequence": "^1.1.x"
},
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
"gulp": "gulp"
},
"repository": {
"type": "git",

File diff suppressed because one or more lines are too long

Binary file not shown.

View File

@@ -169,7 +169,7 @@
<glyph unicode="&#xf08e;" horiz-adv-x="1792" d="M1408 608v-320q0 -119 -84.5 -203.5t-203.5 -84.5h-832q-119 0 -203.5 84.5t-84.5 203.5v832q0 119 84.5 203.5t203.5 84.5h704q14 0 23 -9t9 -23v-64q0 -14 -9 -23t-23 -9h-704q-66 0 -113 -47t-47 -113v-832q0 -66 47 -113t113 -47h832q66 0 113 47t47 113v320 q0 14 9 23t23 9h64q14 0 23 -9t9 -23zM1792 1472v-512q0 -26 -19 -45t-45 -19t-45 19l-176 176l-652 -652q-10 -10 -23 -10t-23 10l-114 114q-10 10 -10 23t10 23l652 652l-176 176q-19 19 -19 45t19 45t45 19h512q26 0 45 -19t19 -45z" />
<glyph unicode="&#xf090;" d="M1184 640q0 -26 -19 -45l-544 -544q-19 -19 -45 -19t-45 19t-19 45v288h-448q-26 0 -45 19t-19 45v384q0 26 19 45t45 19h448v288q0 26 19 45t45 19t45 -19l544 -544q19 -19 19 -45zM1536 992v-704q0 -119 -84.5 -203.5t-203.5 -84.5h-320q-13 0 -22.5 9.5t-9.5 22.5 q0 4 -1 20t-0.5 26.5t3 23.5t10 19.5t20.5 6.5h320q66 0 113 47t47 113v704q0 66 -47 113t-113 47h-288h-11h-13t-11.5 1t-11.5 3t-8 5.5t-7 9t-2 13.5q0 4 -1 20t-0.5 26.5t3 23.5t10 19.5t20.5 6.5h320q119 0 203.5 -84.5t84.5 -203.5z" />
<glyph unicode="&#xf091;" horiz-adv-x="1664" d="M458 653q-74 162 -74 371h-256v-96q0 -78 94.5 -162t235.5 -113zM1536 928v96h-256q0 -209 -74 -371q141 29 235.5 113t94.5 162zM1664 1056v-128q0 -71 -41.5 -143t-112 -130t-173 -97.5t-215.5 -44.5q-42 -54 -95 -95q-38 -34 -52.5 -72.5t-14.5 -89.5q0 -54 30.5 -91 t97.5 -37q75 0 133.5 -45.5t58.5 -114.5v-64q0 -14 -9 -23t-23 -9h-832q-14 0 -23 9t-9 23v64q0 69 58.5 114.5t133.5 45.5q67 0 97.5 37t30.5 91q0 51 -14.5 89.5t-52.5 72.5q-53 41 -95 95q-113 5 -215.5 44.5t-173 97.5t-112 130t-41.5 143v128q0 40 28 68t68 28h288v96 q0 66 47 113t113 47h576q66 0 113 -47t47 -113v-96h288q40 0 68 -28t28 -68z" />
<glyph unicode="&#xf092;" d="M394 184q-8 -9 -20 3q-13 11 -4 19q8 9 20 -3q12 -11 4 -19zM352 245q9 -12 0 -19q-8 -6 -17 7t0 18q9 7 17 -6zM291 305q-5 -7 -13 -2q-10 5 -7 12q3 5 13 2q10 -5 7 -12zM322 271q-6 -7 -16 3q-9 11 -2 16q6 6 16 -3q9 -11 2 -16zM451 159q-4 -12 -19 -6q-17 4 -13 15 t19 7q16 -5 13 -16zM514 154q0 -11 -16 -11q-17 -2 -17 11q0 11 16 11q17 2 17 -11zM572 164q2 -10 -14 -14t-18 8t14 15q16 2 18 -9zM1536 1120v-960q0 -119 -84.5 -203.5t-203.5 -84.5h-224q-16 0 -24.5 1t-19.5 5t-16 14.5t-5 27.5v239q0 97 -52 142q57 6 102.5 18t94 39 t81 66.5t53 105t20.5 150.5q0 121 -79 206q37 91 -8 204q-28 9 -81 -11t-92 -44l-38 -24q-93 26 -192 26t-192 -26q-16 11 -42.5 27t-83.5 38.5t-86 13.5q-44 -113 -7 -204q-79 -85 -79 -206q0 -85 20.5 -150t52.5 -105t80.5 -67t94 -39t102.5 -18q-40 -36 -49 -103 q-21 -10 -45 -15t-57 -5t-65.5 21.5t-55.5 62.5q-19 32 -48.5 52t-49.5 24l-20 3q-21 0 -29 -4.5t-5 -11.5t9 -14t13 -12l7 -5q22 -10 43.5 -38t31.5 -51l10 -23q13 -38 44 -61.5t67 -30t69.5 -7t55.5 3.5l23 4q0 -38 0.5 -103t0.5 -68q0 -22 -11 -33.5t-22 -13t-33 -1.5 h-224q-119 0 -203.5 84.5t-84.5 203.5v960q0 119 84.5 203.5t203.5 84.5h960q119 0 203.5 -84.5t84.5 -203.5z" />
<glyph unicode="&#xf092;" d="M519 336q4 6 -3 13q-9 7 -14 2q-4 -6 3 -13q9 -7 14 -2zM491 377q-5 7 -12 4q-6 -4 0 -12q7 -8 12 -5q6 4 0 13zM450 417q2 4 -5 8q-7 2 -8 -2q-3 -5 4 -8q8 -2 9 2zM471 394q2 1 1.5 4.5t-3.5 5.5q-6 7 -10 3t1 -11q6 -6 11 -2zM557 319q2 7 -9 11q-9 3 -13 -4 q-2 -7 9 -11q9 -3 13 4zM599 316q0 8 -12 8q-10 0 -10 -8t11 -8t11 8zM638 323q-2 7 -13 5t-9 -9q2 -8 12 -6t10 10zM1280 640q0 212 -150 362t-362 150t-362 -150t-150 -362q0 -167 98 -300.5t252 -185.5q18 -3 26.5 5t8.5 20q0 52 -1 95q-6 -1 -15.5 -2.5t-35.5 -2t-48 4 t-43.5 20t-29.5 41.5q-23 59 -57 74q-2 1 -4.5 3.5l-8 8t-7 9.5t4 7.5t19.5 3.5q6 0 15 -2t30 -15.5t33 -35.5q16 -28 37.5 -42t43.5 -14t38 3.5t30 9.5q7 47 33 69q-49 6 -86 18.5t-73 39t-55.5 76t-19.5 119.5q0 79 53 137q-24 62 5 136q19 6 54.5 -7.5t60.5 -29.5l26 -16 q58 17 128 17t128 -17q11 7 28.5 18t55.5 26t57 9q29 -74 5 -136q53 -58 53 -137q0 -57 -14 -100.5t-35.5 -70t-53.5 -44.5t-62.5 -26t-68.5 -12q35 -31 35 -95q0 -40 -0.5 -89t-0.5 -51q0 -12 8.5 -20t26.5 -5q154 52 252 185.5t98 300.5zM1536 1120v-960 q0 -119 -84.5 -203.5t-203.5 -84.5h-960q-119 0 -203.5 84.5t-84.5 203.5v960q0 119 84.5 203.5t203.5 84.5h960q119 0 203.5 -84.5t84.5 -203.5z" />
<glyph unicode="&#xf093;" horiz-adv-x="1664" d="M1280 64q0 26 -19 45t-45 19t-45 -19t-19 -45t19 -45t45 -19t45 19t19 45zM1536 64q0 26 -19 45t-45 19t-45 -19t-19 -45t19 -45t45 -19t45 19t19 45zM1664 288v-320q0 -40 -28 -68t-68 -28h-1472q-40 0 -68 28t-28 68v320q0 40 28 68t68 28h427q21 -56 70.5 -92 t110.5 -36h256q61 0 110.5 36t70.5 92h427q40 0 68 -28t28 -68zM1339 936q-17 -40 -59 -40h-256v-448q0 -26 -19 -45t-45 -19h-256q-26 0 -45 19t-19 45v448h-256q-42 0 -59 40q-17 39 14 69l448 448q18 19 45 19t45 -19l448 -448q31 -30 14 -69z" />
<glyph unicode="&#xf094;" d="M1407 710q0 44 -7 113.5t-18 96.5q-12 30 -17 44t-9 36.5t-4 48.5q0 23 5 68.5t5 67.5q0 37 -10 55q-4 1 -13 1q-19 0 -58 -4.5t-59 -4.5q-60 0 -176 24t-175 24q-43 0 -94.5 -11.5t-85 -23.5t-89.5 -34q-137 -54 -202 -103q-96 -73 -159.5 -189.5t-88 -236t-24.5 -248.5 q0 -40 12.5 -120t12.5 -121q0 -23 -11 -66.5t-11 -65.5t12 -36.5t34 -14.5q24 0 72.5 11t73.5 11q57 0 169.5 -15.5t169.5 -15.5q181 0 284 36q129 45 235.5 152.5t166 245.5t59.5 275zM1535 712q0 -165 -70 -327.5t-196 -288t-281 -180.5q-124 -44 -326 -44 q-57 0 -170 14.5t-169 14.5q-24 0 -72.5 -14.5t-73.5 -14.5q-73 0 -123.5 55.5t-50.5 128.5q0 24 11 68t11 67q0 40 -12.5 120.5t-12.5 121.5q0 111 18 217.5t54.5 209.5t100.5 194t150 156q78 59 232 120q194 78 316 78q60 0 175.5 -24t173.5 -24q19 0 57 5t58 5 q81 0 118 -50.5t37 -134.5q0 -23 -5 -68t-5 -68q0 -10 1 -18.5t3 -17t4 -13.5t6.5 -16t6.5 -17q16 -40 25 -118.5t9 -136.5z" />
<glyph unicode="&#xf095;" horiz-adv-x="1408" d="M1408 296q0 -27 -10 -70.5t-21 -68.5q-21 -50 -122 -106q-94 -51 -186 -51q-27 0 -52.5 3.5t-57.5 12.5t-47.5 14.5t-55.5 20.5t-49 18q-98 35 -175 83q-128 79 -264.5 215.5t-215.5 264.5q-48 77 -83 175q-3 9 -18 49t-20.5 55.5t-14.5 47.5t-12.5 57.5t-3.5 52.5 q0 92 51 186q56 101 106 122q25 11 68.5 21t70.5 10q14 0 21 -3q18 -6 53 -76q11 -19 30 -54t35 -63.5t31 -53.5q3 -4 17.5 -25t21.5 -35.5t7 -28.5q0 -20 -28.5 -50t-62 -55t-62 -53t-28.5 -46q0 -9 5 -22.5t8.5 -20.5t14 -24t11.5 -19q76 -137 174 -235t235 -174 q2 -1 19 -11.5t24 -14t20.5 -8.5t22.5 -5q18 0 46 28.5t53 62t55 62t50 28.5q14 0 28.5 -7t35.5 -21.5t25 -17.5q25 -15 53.5 -31t63.5 -35t54 -30q70 -35 76 -53q3 -7 3 -21z" />
@@ -178,7 +178,7 @@
<glyph unicode="&#xf098;" d="M1280 343q0 11 -2 16q-3 8 -38.5 29.5t-88.5 49.5l-53 29q-5 3 -19 13t-25 15t-21 5q-18 0 -47 -32.5t-57 -65.5t-44 -33q-7 0 -16.5 3.5t-15.5 6.5t-17 9.5t-14 8.5q-99 55 -170.5 126.5t-126.5 170.5q-2 3 -8.5 14t-9.5 17t-6.5 15.5t-3.5 16.5q0 13 20.5 33.5t45 38.5 t45 39.5t20.5 36.5q0 10 -5 21t-15 25t-13 19q-3 6 -15 28.5t-25 45.5t-26.5 47.5t-25 40.5t-16.5 18t-16 2q-48 0 -101 -22q-46 -21 -80 -94.5t-34 -130.5q0 -16 2.5 -34t5 -30.5t9 -33t10 -29.5t12.5 -33t11 -30q60 -164 216.5 -320.5t320.5 -216.5q6 -2 30 -11t33 -12.5 t29.5 -10t33 -9t30.5 -5t34 -2.5q57 0 130.5 34t94.5 80q22 53 22 101zM1536 1120v-960q0 -119 -84.5 -203.5t-203.5 -84.5h-960q-119 0 -203.5 84.5t-84.5 203.5v960q0 119 84.5 203.5t203.5 84.5h960q119 0 203.5 -84.5t84.5 -203.5z" />
<glyph unicode="&#xf099;" horiz-adv-x="1664" d="M1620 1128q-67 -98 -162 -167q1 -14 1 -42q0 -130 -38 -259.5t-115.5 -248.5t-184.5 -210.5t-258 -146t-323 -54.5q-271 0 -496 145q35 -4 78 -4q225 0 401 138q-105 2 -188 64.5t-114 159.5q33 -5 61 -5q43 0 85 11q-112 23 -185.5 111.5t-73.5 205.5v4q68 -38 146 -41 q-66 44 -105 115t-39 154q0 88 44 163q121 -149 294.5 -238.5t371.5 -99.5q-8 38 -8 74q0 134 94.5 228.5t228.5 94.5q140 0 236 -102q109 21 205 78q-37 -115 -142 -178q93 10 186 50z" />
<glyph unicode="&#xf09a;" horiz-adv-x="1024" d="M959 1524v-264h-157q-86 0 -116 -36t-30 -108v-189h293l-39 -296h-254v-759h-306v759h-255v296h255v218q0 186 104 288.5t277 102.5q147 0 228 -12z" />
<glyph unicode="&#xf09b;" d="M1536 640q0 -251 -146.5 -451.5t-378.5 -277.5q-27 -5 -39.5 7t-12.5 30v211q0 97 -52 142q57 6 102.5 18t94 39t81 66.5t53 105t20.5 150.5q0 121 -79 206q37 91 -8 204q-28 9 -81 -11t-92 -44l-38 -24q-93 26 -192 26t-192 -26q-16 11 -42.5 27t-83.5 38.5t-86 13.5 q-44 -113 -7 -204q-79 -85 -79 -206q0 -85 20.5 -150t52.5 -105t80.5 -67t94 -39t102.5 -18q-40 -36 -49 -103q-21 -10 -45 -15t-57 -5t-65.5 21.5t-55.5 62.5q-19 32 -48.5 52t-49.5 24l-20 3q-21 0 -29 -4.5t-5 -11.5t9 -14t13 -12l7 -5q22 -10 43.5 -38t31.5 -51l10 -23 q13 -38 44 -61.5t67 -30t69.5 -7t55.5 3.5l23 4q0 -38 0.5 -89t0.5 -54q0 -18 -13 -30t-40 -7q-232 77 -378.5 277.5t-146.5 451.5q0 209 103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" />
<glyph unicode="&#xf09b;" d="M768 1408q209 0 385.5 -103t279.5 -279.5t103 -385.5q0 -251 -146.5 -451.5t-378.5 -277.5q-27 -5 -40 7t-13 30q0 3 0.5 76.5t0.5 134.5q0 97 -52 142q57 6 102.5 18t94 39t81 66.5t53 105t20.5 150.5q0 119 -79 206q37 91 -8 204q-28 9 -81 -11t-92 -44l-38 -24 q-93 26 -192 26t-192 -26q-16 11 -42.5 27t-83.5 38.5t-85 13.5q-45 -113 -8 -204q-79 -87 -79 -206q0 -85 20.5 -150t52.5 -105t80.5 -67t94 -39t102.5 -18q-39 -36 -49 -103q-21 -10 -45 -15t-57 -5t-65.5 21.5t-55.5 62.5q-19 32 -48.5 52t-49.5 24l-20 3q-21 0 -29 -4.5 t-5 -11.5t9 -14t13 -12l7 -5q22 -10 43.5 -38t31.5 -51l10 -23q13 -38 44 -61.5t67 -30t69.5 -7t55.5 3.5l23 4q0 -38 0.5 -88.5t0.5 -54.5q0 -18 -13 -30t-40 -7q-232 77 -378.5 277.5t-146.5 451.5q0 209 103 385.5t279.5 279.5t385.5 103zM291 305q3 7 -7 12 q-10 3 -13 -2q-3 -7 7 -12q9 -6 13 2zM322 271q7 5 -2 16q-10 9 -16 3q-7 -5 2 -16q10 -10 16 -3zM352 226q9 7 0 19q-8 13 -17 6q-9 -5 0 -18t17 -7zM394 184q8 8 -4 19q-12 12 -20 3q-9 -8 4 -19q12 -12 20 -3zM451 159q3 11 -13 16q-15 4 -19 -7t13 -15q15 -6 19 6z M514 154q0 13 -17 11q-16 0 -16 -11q0 -13 17 -11q16 0 16 11zM572 164q-2 11 -18 9q-16 -3 -14 -15t18 -8t14 14z" />
<glyph unicode="&#xf09c;" horiz-adv-x="1664" d="M1664 960v-256q0 -26 -19 -45t-45 -19h-64q-26 0 -45 19t-19 45v256q0 106 -75 181t-181 75t-181 -75t-75 -181v-192h96q40 0 68 -28t28 -68v-576q0 -40 -28 -68t-68 -28h-960q-40 0 -68 28t-28 68v576q0 40 28 68t68 28h672v192q0 185 131.5 316.5t316.5 131.5 t316.5 -131.5t131.5 -316.5z" />
<glyph unicode="&#xf09d;" horiz-adv-x="1920" d="M1760 1408q66 0 113 -47t47 -113v-1216q0 -66 -47 -113t-113 -47h-1600q-66 0 -113 47t-47 113v1216q0 66 47 113t113 47h1600zM160 1280q-13 0 -22.5 -9.5t-9.5 -22.5v-224h1664v224q0 13 -9.5 22.5t-22.5 9.5h-1600zM1760 0q13 0 22.5 9.5t9.5 22.5v608h-1664v-608 q0 -13 9.5 -22.5t22.5 -9.5h1600zM256 128v128h256v-128h-256zM640 128v128h384v-128h-384z" />
<glyph unicode="&#xf09e;" horiz-adv-x="1408" d="M384 192q0 -80 -56 -136t-136 -56t-136 56t-56 136t56 136t136 56t136 -56t56 -136zM896 69q2 -28 -17 -48q-18 -21 -47 -21h-135q-25 0 -43 16.5t-20 41.5q-22 229 -184.5 391.5t-391.5 184.5q-25 2 -41.5 20t-16.5 43v135q0 29 21 47q17 17 43 17h5q160 -13 306 -80.5 t259 -181.5q114 -113 181.5 -259t80.5 -306zM1408 67q2 -27 -18 -47q-18 -20 -46 -20h-143q-26 0 -44.5 17.5t-19.5 42.5q-12 215 -101 408.5t-231.5 336t-336 231.5t-408.5 102q-25 1 -42.5 19.5t-17.5 43.5v143q0 28 20 46q18 18 44 18h3q262 -13 501.5 -120t425.5 -294 q187 -186 294 -425.5t120 -501.5z" />
@@ -484,7 +484,7 @@
<glyph unicode="&#xf1eb;" horiz-adv-x="2048" d="M1024 13q-20 0 -93 73.5t-73 93.5q0 32 62.5 54t103.5 22t103.5 -22t62.5 -54q0 -20 -73 -93.5t-93 -73.5zM1294 284q-2 0 -40 25t-101.5 50t-128.5 25t-128.5 -25t-101 -50t-40.5 -25q-18 0 -93.5 75t-75.5 93q0 13 10 23q78 77 196 121t233 44t233 -44t196 -121 q10 -10 10 -23q0 -18 -75.5 -93t-93.5 -75zM1567 556q-11 0 -23 8q-136 105 -252 154.5t-268 49.5q-85 0 -170.5 -22t-149 -53t-113.5 -62t-79 -53t-31 -22q-17 0 -92 75t-75 93q0 12 10 22q132 132 320 205t380 73t380 -73t320 -205q10 -10 10 -22q0 -18 -75 -93t-92 -75z M1838 827q-11 0 -22 9q-179 157 -371.5 236.5t-420.5 79.5t-420.5 -79.5t-371.5 -236.5q-11 -9 -22 -9q-17 0 -92.5 75t-75.5 93q0 13 10 23q187 186 445 288t527 102t527 -102t445 -288q10 -10 10 -23q0 -18 -75.5 -93t-92.5 -75z" />
<glyph unicode="&#xf1ec;" horiz-adv-x="1792" d="M384 0q0 53 -37.5 90.5t-90.5 37.5t-90.5 -37.5t-37.5 -90.5t37.5 -90.5t90.5 -37.5t90.5 37.5t37.5 90.5zM768 0q0 53 -37.5 90.5t-90.5 37.5t-90.5 -37.5t-37.5 -90.5t37.5 -90.5t90.5 -37.5t90.5 37.5t37.5 90.5zM384 384q0 53 -37.5 90.5t-90.5 37.5t-90.5 -37.5 t-37.5 -90.5t37.5 -90.5t90.5 -37.5t90.5 37.5t37.5 90.5zM1152 0q0 53 -37.5 90.5t-90.5 37.5t-90.5 -37.5t-37.5 -90.5t37.5 -90.5t90.5 -37.5t90.5 37.5t37.5 90.5zM768 384q0 53 -37.5 90.5t-90.5 37.5t-90.5 -37.5t-37.5 -90.5t37.5 -90.5t90.5 -37.5t90.5 37.5 t37.5 90.5zM384 768q0 53 -37.5 90.5t-90.5 37.5t-90.5 -37.5t-37.5 -90.5t37.5 -90.5t90.5 -37.5t90.5 37.5t37.5 90.5zM1152 384q0 53 -37.5 90.5t-90.5 37.5t-90.5 -37.5t-37.5 -90.5t37.5 -90.5t90.5 -37.5t90.5 37.5t37.5 90.5zM768 768q0 53 -37.5 90.5t-90.5 37.5 t-90.5 -37.5t-37.5 -90.5t37.5 -90.5t90.5 -37.5t90.5 37.5t37.5 90.5zM1536 0v384q0 52 -38 90t-90 38t-90 -38t-38 -90v-384q0 -52 38 -90t90 -38t90 38t38 90zM1152 768q0 53 -37.5 90.5t-90.5 37.5t-90.5 -37.5t-37.5 -90.5t37.5 -90.5t90.5 -37.5t90.5 37.5t37.5 90.5z M1536 1088v256q0 26 -19 45t-45 19h-1280q-26 0 -45 -19t-19 -45v-256q0 -26 19 -45t45 -19h1280q26 0 45 19t19 45zM1536 768q0 53 -37.5 90.5t-90.5 37.5t-90.5 -37.5t-37.5 -90.5t37.5 -90.5t90.5 -37.5t90.5 37.5t37.5 90.5zM1664 1408v-1536q0 -52 -38 -90t-90 -38 h-1408q-52 0 -90 38t-38 90v1536q0 52 38 90t90 38h1408q52 0 90 -38t38 -90z" />
<glyph unicode="&#xf1ed;" d="M1519 890q18 -84 -4 -204q-87 -444 -565 -444h-44q-25 0 -44 -16.5t-24 -42.5l-4 -19l-55 -346l-2 -15q-5 -26 -24.5 -42.5t-44.5 -16.5h-251q-21 0 -33 15t-9 36q9 56 26.5 168t26.5 168t27 167.5t27 167.5q5 37 43 37h131q133 -2 236 21q175 39 287 144q102 95 155 246 q24 70 35 133q1 6 2.5 7.5t3.5 1t6 -3.5q79 -59 98 -162zM1347 1172q0 -107 -46 -236q-80 -233 -302 -315q-113 -40 -252 -42q0 -1 -90 -1l-90 1q-100 0 -118 -96q-2 -8 -85 -530q-1 -10 -12 -10h-295q-22 0 -36.5 16.5t-11.5 38.5l232 1471q5 29 27.5 48t51.5 19h598 q34 0 97.5 -13t111.5 -32q107 -41 163.5 -123t56.5 -196z" />
<glyph unicode="&#xf1ee;" horiz-adv-x="1792" d="M602 949q19 -61 31 -123.5t17 -141.5t-14 -159t-62 -145q-21 81 -67 157t-95.5 127t-99 90.5t-78.5 57.5t-33 19q-62 34 -81.5 100t14.5 128t101 81.5t129 -14.5q138 -83 238 -177zM927 1236q11 -25 20.5 -46t36.5 -100.5t42.5 -150.5t25.5 -179.5t0 -205.5t-47.5 -209.5 t-105.5 -208.5q-51 -72 -138 -72q-54 0 -98 31q-57 40 -69 109t28 127q60 85 81 195t13 199.5t-32 180.5t-39 128t-22 52q-31 63 -8.5 129.5t85.5 97.5q34 17 75 17q47 0 88.5 -25t63.5 -69zM1248 567q-17 -160 -72 -311q-17 131 -63 246q25 174 -5 361q-27 178 -94 342 q114 -90 212 -211q9 -37 15 -80q26 -179 7 -347zM1520 1440q9 -17 23.5 -49.5t43.5 -117.5t50.5 -178t34 -227.5t5 -269t-47 -300t-112.5 -323.5q-22 -48 -66 -75.5t-95 -27.5q-39 0 -74 16q-67 31 -92.5 100t4.5 136q58 126 90 257.5t37.5 239.5t-3.5 213.5t-26.5 180.5 t-38.5 138.5t-32.5 90t-15.5 32.5q-34 65 -11.5 135.5t87.5 104.5q37 20 81 20q49 0 91.5 -25.5t66.5 -70.5z" />
<glyph unicode="&#xf1ee;" horiz-adv-x="1792" d="M441 864q32 0 52 -26q266 -364 362 -774h-446q-127 441 -367 749q-12 16 -3 33.5t29 17.5h373zM1000 507q-49 -199 -125 -393q-79 310 -256 594q40 221 44 449q211 -340 337 -650zM1099 1216q235 -324 384.5 -698.5t184.5 -773.5h-451q-41 665 -553 1472h435zM1792 640 q0 -424 -101 -812q-67 560 -359 1083q-25 301 -106 584q-4 16 5.5 28.5t25.5 12.5h359q21 0 38.5 -13t22.5 -33q115 -409 115 -850z" />
<glyph unicode="&#xf1f0;" horiz-adv-x="2304" d="M1975 546h-138q14 37 66 179l3 9q4 10 10 26t9 26l12 -55zM531 611l-58 295q-11 54 -75 54h-268l-2 -13q311 -79 403 -336zM710 960l-162 -438l-17 89q-26 70 -85 129.5t-131 88.5l135 -510h175l261 641h-176zM849 318h166l104 642h-166zM1617 944q-69 27 -149 27 q-123 0 -201 -59t-79 -153q-1 -102 145 -174q48 -23 67 -41t19 -39q0 -30 -30 -46t-69 -16q-86 0 -156 33l-22 11l-23 -144q74 -34 185 -34q130 -1 208.5 59t80.5 160q0 106 -140 174q-49 25 -71 42t-22 38q0 22 24.5 38.5t70.5 16.5q70 1 124 -24l15 -8zM2042 960h-128 q-65 0 -87 -54l-246 -588h174l35 96h212q5 -22 20 -96h154zM2304 1280v-1280q0 -52 -38 -90t-90 -38h-2048q-52 0 -90 38t-38 90v1280q0 52 38 90t90 38h2048q52 0 90 -38t38 -90z" />
<glyph unicode="&#xf1f1;" horiz-adv-x="2304" d="M671 603h-13q-47 0 -47 -32q0 -22 20 -22q17 0 28 15t12 39zM1066 639h62v3q1 4 0.5 6.5t-1 7t-2 8t-4.5 6.5t-7.5 5t-11.5 2q-28 0 -36 -38zM1606 603h-12q-48 0 -48 -32q0 -22 20 -22q17 0 28 15t12 39zM1925 629q0 41 -30 41q-19 0 -31 -20t-12 -51q0 -42 28 -42 q20 0 32.5 20t12.5 52zM480 770h87l-44 -262h-56l32 201l-71 -201h-39l-4 200l-34 -200h-53l44 262h81l2 -163zM733 663q0 -6 -4 -42q-16 -101 -17 -113h-47l1 22q-20 -26 -58 -26q-23 0 -37.5 16t-14.5 42q0 39 26 60.5t73 21.5q14 0 23 -1q0 3 0.5 5.5t1 4.5t0.5 3 q0 20 -36 20q-29 0 -59 -10q0 4 7 48q38 11 67 11q74 0 74 -62zM889 721l-8 -49q-22 3 -41 3q-27 0 -27 -17q0 -8 4.5 -12t21.5 -11q40 -19 40 -60q0 -72 -87 -71q-34 0 -58 6q0 2 7 49q29 -8 51 -8q32 0 32 19q0 7 -4.5 11.5t-21.5 12.5q-43 20 -43 59q0 72 84 72 q30 0 50 -4zM977 721h28l-7 -52h-29q-2 -17 -6.5 -40.5t-7 -38.5t-2.5 -18q0 -16 19 -16q8 0 16 2l-8 -47q-21 -7 -40 -7q-43 0 -45 47q0 12 8 56q3 20 25 146h55zM1180 648q0 -23 -7 -52h-111q-3 -22 10 -33t38 -11q30 0 58 14l-9 -54q-30 -8 -57 -8q-95 0 -95 95 q0 55 27.5 90.5t69.5 35.5q35 0 55.5 -21t20.5 -56zM1319 722q-13 -23 -22 -62q-22 2 -31 -24t-25 -128h-56l3 14q22 130 29 199h51l-3 -33q14 21 25.5 29.5t28.5 4.5zM1506 763l-9 -57q-28 14 -50 14q-31 0 -51 -27.5t-20 -70.5q0 -30 13.5 -47t38.5 -17q21 0 48 13 l-10 -59q-28 -8 -50 -8q-45 0 -71.5 30.5t-26.5 82.5q0 70 35.5 114.5t91.5 44.5q26 0 61 -13zM1668 663q0 -18 -4 -42q-13 -79 -17 -113h-46l1 22q-20 -26 -59 -26q-23 0 -37 16t-14 42q0 39 25.5 60.5t72.5 21.5q15 0 23 -1q2 7 2 13q0 20 -36 20q-29 0 -59 -10q0 4 8 48 q38 11 67 11q73 0 73 -62zM1809 722q-14 -24 -21 -62q-23 2 -31.5 -23t-25.5 -129h-56l3 14q19 104 29 199h52q0 -11 -4 -33q15 21 26.5 29.5t27.5 4.5zM1950 770h56l-43 -262h-53l3 19q-23 -23 -52 -23q-31 0 -49.5 24t-18.5 64q0 53 27.5 92t64.5 39q31 0 53 -29z M2061 640q0 148 -72.5 273t-198 198t-273.5 73q-181 0 -328 -110q127 -116 171 -284h-50q-44 150 -158 253q-114 -103 -158 -253h-50q44 168 171 284q-147 110 -328 110q-148 0 -273.5 -73t-198 -198t-72.5 -273t72.5 -273t198 -198t273.5 -73q181 0 328 110 q-120 111 -165 264h50q46 -138 152 -233q106 95 152 233h50q-45 -153 -165 -264q147 -110 328 -110q148 0 273.5 73t198 198t72.5 273zM2304 1280v-1280q0 -52 -38 -90t-90 -38h-2048q-52 0 -90 38t-38 90v1280q0 52 38 90t90 38h2048q52 0 90 -38t38 -90z" />
<glyph unicode="&#xf1f2;" horiz-adv-x="2304" d="M313 759q0 -51 -36 -84q-29 -26 -89 -26h-17v220h17q61 0 89 -27q36 -31 36 -83zM2089 824q0 -52 -64 -52h-19v101h20q63 0 63 -49zM380 759q0 74 -50 120.5t-129 46.5h-95v-333h95q74 0 119 38q60 51 60 128zM410 593h65v333h-65v-333zM730 694q0 40 -20.5 62t-75.5 42 q-29 10 -39.5 19t-10.5 23q0 16 13.5 26.5t34.5 10.5q29 0 53 -27l34 44q-41 37 -98 37q-44 0 -74 -27.5t-30 -67.5q0 -35 18 -55.5t64 -36.5q37 -13 45 -19q19 -12 19 -34q0 -20 -14 -33.5t-36 -13.5q-48 0 -71 44l-42 -40q44 -64 115 -64q51 0 83 30.5t32 79.5zM1008 604 v77q-37 -37 -78 -37q-49 0 -80.5 32.5t-31.5 82.5q0 48 31.5 81.5t77.5 33.5q43 0 81 -38v77q-40 20 -80 20q-74 0 -125.5 -50.5t-51.5 -123.5t51 -123.5t125 -50.5q42 0 81 19zM2240 0v527q-65 -40 -144.5 -84t-237.5 -117t-329.5 -137.5t-417.5 -134.5t-504 -118h1569 q26 0 45 19t19 45zM1389 757q0 75 -53 128t-128 53t-128 -53t-53 -128t53 -128t128 -53t128 53t53 128zM1541 584l144 342h-71l-90 -224l-89 224h-71l142 -342h35zM1714 593h184v56h-119v90h115v56h-115v74h119v57h-184v-333zM2105 593h80l-105 140q76 16 76 94q0 47 -31 73 t-87 26h-97v-333h65v133h9zM2304 1274v-1268q0 -56 -38.5 -95t-93.5 -39h-2040q-55 0 -93.5 39t-38.5 95v1268q0 56 38.5 95t93.5 39h2040q55 0 93.5 -39t38.5 -95z" />
@@ -641,15 +641,45 @@
<glyph unicode="&#xf293;" d="M841 483l148 -148l-149 -149zM840 1094l149 -149l-148 -148zM710 -130l464 464l-306 306l306 306l-464 464v-611l-255 255l-93 -93l320 -321l-320 -321l93 -93l255 255v-611zM1429 640q0 -209 -32 -365.5t-87.5 -257t-140.5 -162.5t-181.5 -86.5t-219.5 -24.5 t-219.5 24.5t-181.5 86.5t-140.5 162.5t-87.5 257t-32 365.5t32 365.5t87.5 257t140.5 162.5t181.5 86.5t219.5 24.5t219.5 -24.5t181.5 -86.5t140.5 -162.5t87.5 -257t32 -365.5z" />
<glyph unicode="&#xf294;" horiz-adv-x="1024" d="M596 113l173 172l-173 172v-344zM596 823l173 172l-173 172v-344zM628 640l356 -356l-539 -540v711l-297 -296l-108 108l372 373l-372 373l108 108l297 -296v711l539 -540z" />
<glyph unicode="&#xf295;" d="M1280 256q0 52 -38 90t-90 38t-90 -38t-38 -90t38 -90t90 -38t90 38t38 90zM512 1024q0 52 -38 90t-90 38t-90 -38t-38 -90t38 -90t90 -38t90 38t38 90zM1536 256q0 -159 -112.5 -271.5t-271.5 -112.5t-271.5 112.5t-112.5 271.5t112.5 271.5t271.5 112.5t271.5 -112.5 t112.5 -271.5zM1440 1344q0 -20 -13 -38l-1056 -1408q-19 -26 -51 -26h-160q-26 0 -45 19t-19 45q0 20 13 38l1056 1408q19 26 51 26h160q26 0 45 -19t19 -45zM768 1024q0 -159 -112.5 -271.5t-271.5 -112.5t-271.5 112.5t-112.5 271.5t112.5 271.5t271.5 112.5 t271.5 -112.5t112.5 -271.5z" />
<glyph unicode="&#xf296;" horiz-adv-x="1792" />
<glyph unicode="&#xf297;" horiz-adv-x="1792" />
<glyph unicode="&#xf298;" horiz-adv-x="1792" />
<glyph unicode="&#xf299;" horiz-adv-x="1792" />
<glyph unicode="&#xf29a;" horiz-adv-x="1792" />
<glyph unicode="&#xf29b;" horiz-adv-x="1792" />
<glyph unicode="&#xf29c;" horiz-adv-x="1792" />
<glyph unicode="&#xf29d;" horiz-adv-x="1792" />
<glyph unicode="&#xf29e;" horiz-adv-x="1792" />
<glyph unicode="&#xf296;" horiz-adv-x="1792" d="M104 830l792 -1015l-868 630q-18 13 -25 34.5t0 42.5l101 308v0zM566 830h660l-330 -1015v0zM368 1442l198 -612h-462l198 612q8 23 33 23t33 -23zM1688 830l101 -308q7 -21 0 -42.5t-25 -34.5l-868 -630l792 1015v0zM1688 830h-462l198 612q8 23 33 23t33 -23z" />
<glyph unicode="&#xf297;" horiz-adv-x="1792" d="M384 704h160v224h-160v-224zM1221 372v92q-104 -36 -243 -38q-135 -1 -259.5 46.5t-220.5 122.5l1 -96q88 -80 212 -128.5t272 -47.5q129 0 238 49zM640 704h640v224h-640v-224zM1792 736q0 -187 -99 -352q89 -102 89 -229q0 -157 -129.5 -268t-313.5 -111 q-122 0 -225 52.5t-161 140.5q-19 -1 -57 -1t-57 1q-58 -88 -161 -140.5t-225 -52.5q-184 0 -313.5 111t-129.5 268q0 127 89 229q-99 165 -99 352q0 209 120 385.5t326.5 279.5t449.5 103t449.5 -103t326.5 -279.5t120 -385.5z" />
<glyph unicode="&#xf298;" d="M515 625v-128h-252v128h252zM515 880v-127h-252v127h252zM1273 369v-128h-341v128h341zM1273 625v-128h-672v128h672zM1273 880v-127h-672v127h672zM1408 20v1240q0 8 -6 14t-14 6h-32l-378 -256l-210 171l-210 -171l-378 256h-32q-8 0 -14 -6t-6 -14v-1240q0 -8 6 -14 t14 -6h1240q8 0 14 6t6 14zM553 1130l185 150h-406zM983 1130l221 150h-406zM1536 1260v-1240q0 -62 -43 -105t-105 -43h-1240q-62 0 -105 43t-43 105v1240q0 62 43 105t105 43h1240q62 0 105 -43t43 -105z" />
<glyph unicode="&#xf299;" horiz-adv-x="1792" d="M896 720q-104 196 -160 278q-139 202 -347 318q-34 19 -70 36q-89 40 -94 32t34 -38l39 -31q62 -43 112.5 -93.5t94.5 -116.5t70.5 -113t70.5 -131q9 -17 13 -25q44 -84 84 -153t98 -154t115.5 -150t131 -123.5t148.5 -90.5q153 -66 154 -60q1 3 -49 37q-53 36 -81 57 q-77 58 -179 211t-185 310zM549 177q-76 60 -132.5 125t-98 143.5t-71 154.5t-58.5 186t-52 209t-60.5 252t-76.5 289q273 0 497.5 -36t379 -92t271 -144.5t185.5 -172.5t110 -198.5t56 -199.5t12.5 -198.5t-9.5 -173t-20 -143.5t-13 -107l323 -327h-104l-281 285 q-22 -2 -91.5 -14t-121.5 -19t-138 -6t-160.5 17t-167.5 59t-179 111z" />
<glyph unicode="&#xf29a;" horiz-adv-x="1792" d="M1374 879q-6 26 -28.5 39.5t-48.5 7.5q-261 -62 -401 -62t-401 62q-26 6 -48.5 -7.5t-28.5 -39.5t7.5 -48.5t39.5 -28.5q194 -46 303 -58q-2 -158 -15.5 -269t-26.5 -155.5t-41 -115.5l-9 -21q-10 -25 1 -49t36 -34q9 -4 23 -4q44 0 60 41l8 20q54 139 71 259h42 q17 -120 71 -259l8 -20q16 -41 60 -41q14 0 23 4q25 10 36 34t1 49l-9 21q-28 71 -41 115.5t-26.5 155.5t-15.5 269q109 12 303 58q26 6 39.5 28.5t7.5 48.5zM1024 1024q0 53 -37.5 90.5t-90.5 37.5t-90.5 -37.5t-37.5 -90.5t37.5 -90.5t90.5 -37.5t90.5 37.5t37.5 90.5z M1600 640q0 -143 -55.5 -273.5t-150 -225t-225 -150t-273.5 -55.5t-273.5 55.5t-225 150t-150 225t-55.5 273.5t55.5 273.5t150 225t225 150t273.5 55.5t273.5 -55.5t225 -150t150 -225t55.5 -273.5zM896 1408q-156 0 -298 -61t-245 -164t-164 -245t-61 -298t61 -298 t164 -245t245 -164t298 -61t298 61t245 164t164 245t61 298t-61 298t-164 245t-245 164t-298 61zM1792 640q0 -182 -71 -348t-191 -286t-286 -191t-348 -71t-348 71t-286 191t-191 286t-71 348t71 348t191 286t286 191t348 71t348 -71t286 -191t191 -286t71 -348z" />
<glyph unicode="&#xf29b;" d="M1438 723q34 -35 29 -82l-44 -551q-4 -42 -34.5 -70t-71.5 -28q-6 0 -9 1q-44 3 -72.5 36.5t-25.5 77.5l35 429l-143 -8q55 -113 55 -240q0 -216 -148 -372l-137 137q91 101 91 235q0 145 -102.5 248t-247.5 103q-134 0 -236 -92l-137 138q120 114 284 141l264 300 l-149 87l-181 -161q-33 -30 -77 -27.5t-73 35.5t-26.5 77t34.5 73l239 213q26 23 60 26.5t64 -14.5l488 -283q36 -21 48 -68q17 -67 -26 -117l-205 -232l371 20q49 3 83 -32zM1240 1180q-74 0 -126 52t-52 126t52 126t126 52t126.5 -52t52.5 -126t-52.5 -126t-126.5 -52z M613 -62q106 0 196 61l139 -139q-146 -116 -335 -116q-148 0 -273.5 73t-198.5 198t-73 273q0 188 116 336l139 -139q-60 -88 -60 -197q0 -145 102.5 -247.5t247.5 -102.5z" />
<glyph unicode="&#xf29c;" d="M880 336v-160q0 -14 -9 -23t-23 -9h-160q-14 0 -23 9t-9 23v160q0 14 9 23t23 9h160q14 0 23 -9t9 -23zM1136 832q0 -50 -15 -90t-45.5 -69t-52 -44t-59.5 -36q-32 -18 -46.5 -28t-26 -24t-11.5 -29v-32q0 -14 -9 -23t-23 -9h-160q-14 0 -23 9t-9 23v68q0 35 10.5 64.5 t24 47.5t39 35.5t41 25.5t44.5 21q53 25 75 43t22 49q0 42 -43.5 71.5t-95.5 29.5q-56 0 -95 -27q-29 -20 -80 -83q-9 -12 -25 -12q-11 0 -19 6l-108 82q-10 7 -12 20t5 23q122 192 349 192q129 0 238.5 -89.5t109.5 -214.5zM768 1280q-130 0 -248.5 -51t-204 -136.5 t-136.5 -204t-51 -248.5t51 -248.5t136.5 -204t204 -136.5t248.5 -51t248.5 51t204 136.5t136.5 204t51 248.5t-51 248.5t-136.5 204t-204 136.5t-248.5 51zM1536 640q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5t103 385.5 t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" />
<glyph unicode="&#xf29d;" horiz-adv-x="1408" d="M366 1225q-64 0 -110 45.5t-46 110.5q0 64 46 109.5t110 45.5t109.5 -45.5t45.5 -109.5q0 -65 -45.5 -110.5t-109.5 -45.5zM917 583q0 -50 -30 -67.5t-63.5 -6.5t-47.5 34l-367 438q-7 12 -14 15.5t-11 1.5l-3 -3q-7 -8 4 -21l122 -139l1 -354l-161 -457 q-67 -192 -92 -234q-16 -26 -28 -32q-50 -26 -103 -1q-29 13 -41.5 43t-9.5 57q2 17 197 618l5 416l-85 -164l35 -222q4 -24 -1 -42t-14 -27.5t-19 -16t-17 -7.5l-7 -2q-19 -3 -34.5 3t-24 16t-14 22t-7.5 19.5t-2 9.5l-46 299l211 381q23 34 113 34q75 0 107 -40l424 -521 q7 -5 14 -17l3 -3l-1 -1q7 -13 7 -29zM514 433q43 -113 88.5 -225t69.5 -168l24 -55q36 -93 42 -125q11 -70 -36 -97q-35 -22 -66 -16t-51 22t-29 35h-1q-6 16 -8 25l-124 351zM1338 -159q31 -49 31 -57q0 -5 -3 -7q-9 -5 -14.5 0.5t-15.5 26t-16 30.5q-114 172 -423 661 q3 -1 7 1t7 4l3 2q11 9 11 17z" />
<glyph unicode="&#xf29e;" horiz-adv-x="2304" d="M504 542h171l-1 265zM1530 641q0 87 -50.5 140t-146.5 53h-54v-388h52q91 0 145 57t54 138zM956 1018l1 -756q0 -14 -9.5 -24t-23.5 -10h-216q-14 0 -23.5 10t-9.5 24v62h-291l-55 -81q-10 -15 -28 -15h-267q-21 0 -30.5 18t3.5 35l556 757q9 14 27 14h332q14 0 24 -10 t10 -24zM1783 641q0 -193 -125.5 -303t-324.5 -110h-270q-14 0 -24 10t-10 24v756q0 14 10 24t24 10h268q200 0 326 -109t126 -302zM1939 640q0 -11 -0.5 -29t-8 -71.5t-21.5 -102t-44.5 -108t-73.5 -102.5h-51q38 45 66.5 104.5t41.5 112t21 98t9 72.5l1 27q0 8 -0.5 22.5 t-7.5 60t-20 91.5t-41 111.5t-66 124.5h43q41 -47 72 -107t45.5 -111.5t23 -96t10.5 -70.5zM2123 640q0 -11 -0.5 -29t-8 -71.5t-21.5 -102t-45 -108t-74 -102.5h-51q38 45 66.5 104.5t41.5 112t21 98t9 72.5l1 27q0 8 -0.5 22.5t-7.5 60t-19.5 91.5t-40.5 111.5t-66 124.5 h43q41 -47 72 -107t45.5 -111.5t23 -96t10.5 -70.5zM2304 640q0 -11 -0.5 -29t-8 -71.5t-21.5 -102t-44.5 -108t-73.5 -102.5h-51q38 45 66 104.5t41 112t21 98t9 72.5l1 27q0 8 -0.5 22.5t-7.5 60t-19.5 91.5t-40.5 111.5t-66 124.5h43q41 -47 72 -107t45.5 -111.5t23 -96 t9.5 -70.5z" />
<glyph unicode="&#xf2a0;" horiz-adv-x="1408" d="M617 -153q0 11 -13 58t-31 107t-20 69q-1 4 -5 26.5t-8.5 36t-13.5 21.5q-15 14 -51 14q-23 0 -70 -5.5t-71 -5.5q-34 0 -47 11q-6 5 -11 15.5t-7.5 20t-6.5 24t-5 18.5q-37 128 -37 255t37 255q1 4 5 18.5t6.5 24t7.5 20t11 15.5q13 11 47 11q24 0 71 -5.5t70 -5.5 q36 0 51 14q9 8 13.5 21.5t8.5 36t5 26.5q2 9 20 69t31 107t13 58q0 22 -43.5 52.5t-75.5 42.5q-20 8 -45 8q-34 0 -98 -18q-57 -17 -96.5 -40.5t-71 -66t-46 -70t-45.5 -94.5q-6 -12 -9 -19q-49 -107 -68 -216t-19 -244t19 -244t68 -216q56 -122 83 -161q63 -91 179 -127 l6 -2q64 -18 98 -18q25 0 45 8q32 12 75.5 42.5t43.5 52.5zM776 760q-26 0 -45 19t-19 45.5t19 45.5q37 37 37 90q0 52 -37 91q-19 19 -19 45t19 45t45 19t45 -19q75 -75 75 -181t-75 -181q-21 -19 -45 -19zM957 579q-27 0 -45 19q-19 19 -19 45t19 45q112 114 112 272 t-112 272q-19 19 -19 45t19 45t45 19t45 -19q150 -150 150 -362t-150 -362q-18 -19 -45 -19zM1138 398q-27 0 -45 19q-19 19 -19 45t19 45q90 91 138.5 208t48.5 245t-48.5 245t-138.5 208q-19 19 -19 45t19 45t45 19t45 -19q109 -109 167 -249t58 -294t-58 -294t-167 -249 q-18 -19 -45 -19z" />
<glyph unicode="&#xf2a1;" horiz-adv-x="2176" d="M192 352q-66 0 -113 -47t-47 -113t47 -113t113 -47t113 47t47 113t-47 113t-113 47zM704 352q-66 0 -113 -47t-47 -113t47 -113t113 -47t113 47t47 113t-47 113t-113 47zM704 864q-66 0 -113 -47t-47 -113t47 -113t113 -47t113 47t47 113t-47 113t-113 47zM1472 352 q-66 0 -113 -47t-47 -113t47 -113t113 -47t113 47t47 113t-47 113t-113 47zM1984 352q-66 0 -113 -47t-47 -113t47 -113t113 -47t113 47t47 113t-47 113t-113 47zM1472 864q-66 0 -113 -47t-47 -113t47 -113t113 -47t113 47t47 113t-47 113t-113 47zM1984 864 q-66 0 -113 -47t-47 -113t47 -113t113 -47t113 47t47 113t-47 113t-113 47zM1984 1376q-66 0 -113 -47t-47 -113t47 -113t113 -47t113 47t47 113t-47 113t-113 47zM384 192q0 -80 -56 -136t-136 -56t-136 56t-56 136t56 136t136 56t136 -56t56 -136zM896 192q0 -80 -56 -136 t-136 -56t-136 56t-56 136t56 136t136 56t136 -56t56 -136zM384 704q0 -80 -56 -136t-136 -56t-136 56t-56 136t56 136t136 56t136 -56t56 -136zM896 704q0 -80 -56 -136t-136 -56t-136 56t-56 136t56 136t136 56t136 -56t56 -136zM384 1216q0 -80 -56 -136t-136 -56 t-136 56t-56 136t56 136t136 56t136 -56t56 -136zM1664 192q0 -80 -56 -136t-136 -56t-136 56t-56 136t56 136t136 56t136 -56t56 -136zM896 1216q0 -80 -56 -136t-136 -56t-136 56t-56 136t56 136t136 56t136 -56t56 -136zM2176 192q0 -80 -56 -136t-136 -56t-136 56 t-56 136t56 136t136 56t136 -56t56 -136zM1664 704q0 -80 -56 -136t-136 -56t-136 56t-56 136t56 136t136 56t136 -56t56 -136zM2176 704q0 -80 -56 -136t-136 -56t-136 56t-56 136t56 136t136 56t136 -56t56 -136zM1664 1216q0 -80 -56 -136t-136 -56t-136 56t-56 136 t56 136t136 56t136 -56t56 -136zM2176 1216q0 -80 -56 -136t-136 -56t-136 56t-56 136t56 136t136 56t136 -56t56 -136z" />
<glyph unicode="&#xf2a2;" horiz-adv-x="1792" d="M128 -192q0 -26 -19 -45t-45 -19t-45 19t-19 45t19 45t45 19t45 -19t19 -45zM320 0q0 -26 -19 -45t-45 -19t-45 19t-19 45t19 45t45 19t45 -19t19 -45zM365 365l256 -256l-90 -90l-256 256zM704 384q0 -26 -19 -45t-45 -19t-45 19t-19 45t19 45t45 19t45 -19t19 -45z M1411 704q0 -59 -11.5 -108.5t-37.5 -93.5t-44 -67.5t-53 -64.5q-31 -35 -45.5 -54t-33.5 -50t-26.5 -64t-7.5 -74q0 -159 -112.5 -271.5t-271.5 -112.5q-26 0 -45 19t-19 45t19 45t45 19q106 0 181 75t75 181q0 57 11.5 105.5t37 91t43.5 66.5t52 63q40 46 59.5 72 t37.5 74.5t18 103.5q0 185 -131.5 316.5t-316.5 131.5t-316.5 -131.5t-131.5 -316.5q0 -26 -19 -45t-45 -19t-45 19t-19 45q0 117 45.5 223.5t123 184t184 123t223.5 45.5t223.5 -45.5t184 -123t123 -184t45.5 -223.5zM896 576q0 -26 -19 -45t-45 -19t-45 19t-19 45t19 45 t45 19t45 -19t19 -45zM1184 704q0 -26 -19 -45t-45 -19t-45 19t-19 45q0 93 -65.5 158.5t-158.5 65.5q-92 0 -158 -65.5t-66 -158.5q0 -26 -19 -45t-45 -19t-45 19t-19 45q0 146 103 249t249 103t249 -103t103 -249zM1578 993q10 -25 -1 -49t-36 -34q-9 -4 -23 -4 q-19 0 -35.5 11t-23.5 30q-68 178 -224 295q-21 16 -25 42t12 47q17 21 43 25t47 -12q183 -137 266 -351zM1788 1074q9 -25 -1.5 -49t-35.5 -34q-11 -4 -23 -4q-44 0 -60 41q-92 238 -297 393q-22 16 -25.5 42t12.5 47q16 22 42 25.5t47 -12.5q235 -175 341 -449z" />
<glyph unicode="&#xf2a3;" horiz-adv-x="2304" d="M1032 576q-59 2 -84 55q-17 34 -48 53.5t-68 19.5q-53 0 -90.5 -37.5t-37.5 -90.5q0 -56 36 -89l10 -8q34 -31 82 -31q37 0 68 19.5t48 53.5q25 53 84 55zM1600 704q0 56 -36 89l-10 8q-34 31 -82 31q-37 0 -68 -19.5t-48 -53.5q-25 -53 -84 -55q59 -2 84 -55 q17 -34 48 -53.5t68 -19.5q53 0 90.5 37.5t37.5 90.5zM1174 925q-17 -35 -55 -48t-73 4q-62 31 -134 31q-51 0 -99 -17q3 0 9.5 0.5t9.5 0.5q92 0 170.5 -50t118.5 -133q17 -36 3.5 -73.5t-49.5 -54.5q-18 -9 -39 -9q21 0 39 -9q36 -17 49.5 -54.5t-3.5 -73.5 q-40 -83 -118.5 -133t-170.5 -50h-6q-16 2 -44 4l-290 27l-239 -120q-14 -7 -29 -7q-40 0 -57 35l-160 320q-11 23 -4 47.5t29 37.5l209 119l148 267q17 155 91.5 291.5t195.5 236.5q31 25 70.5 21.5t64.5 -34.5t21.5 -70t-34.5 -65q-70 -59 -117 -128q123 84 267 101 q40 5 71.5 -19t35.5 -64q5 -40 -19 -71.5t-64 -35.5q-84 -10 -159 -55q46 10 99 10q115 0 218 -50q36 -18 49 -55.5t-5 -73.5zM2137 1085l160 -320q11 -23 4 -47.5t-29 -37.5l-209 -119l-148 -267q-17 -155 -91.5 -291.5t-195.5 -236.5q-26 -22 -61 -22q-45 0 -74 35 q-25 31 -21.5 70t34.5 65q70 59 117 128q-123 -84 -267 -101q-4 -1 -12 -1q-36 0 -63.5 24t-31.5 60q-5 40 19 71.5t64 35.5q84 10 159 55q-46 -10 -99 -10q-115 0 -218 50q-36 18 -49 55.5t5 73.5q17 35 55 48t73 -4q62 -31 134 -31q51 0 99 17q-3 0 -9.5 -0.5t-9.5 -0.5 q-92 0 -170.5 50t-118.5 133q-17 36 -3.5 73.5t49.5 54.5q18 9 39 9q-21 0 -39 9q-36 17 -49.5 54.5t3.5 73.5q40 83 118.5 133t170.5 50h6h1q14 -2 42 -4l291 -27l239 120q14 7 29 7q40 0 57 -35z" />
<glyph unicode="&#xf2a4;" horiz-adv-x="1792" d="M1056 704q0 -26 19 -45t45 -19t45 19t19 45q0 146 -103 249t-249 103t-249 -103t-103 -249q0 -26 19 -45t45 -19t45 19t19 45q0 93 66 158.5t158 65.5t158 -65.5t66 -158.5zM835 1280q-117 0 -223.5 -45.5t-184 -123t-123 -184t-45.5 -223.5q0 -26 19 -45t45 -19t45 19 t19 45q0 185 131.5 316.5t316.5 131.5t316.5 -131.5t131.5 -316.5q0 -55 -18 -103.5t-37.5 -74.5t-59.5 -72q-34 -39 -52 -63t-43.5 -66.5t-37 -91t-11.5 -105.5q0 -106 -75 -181t-181 -75q-26 0 -45 -19t-19 -45t19 -45t45 -19q159 0 271.5 112.5t112.5 271.5q0 41 7.5 74 t26.5 64t33.5 50t45.5 54q35 41 53 64.5t44 67.5t37.5 93.5t11.5 108.5q0 117 -45.5 223.5t-123 184t-184 123t-223.5 45.5zM591 561l226 -226l-579 -579q-12 -12 -29 -12t-29 12l-168 168q-12 12 -12 29t12 29zM1612 1524l168 -168q12 -12 12 -29t-12 -30l-233 -233 l-26 -25l-71 -71q-66 153 -195 258l91 91l207 207q13 12 30 12t29 -12z" />
<glyph unicode="&#xf2a5;" d="M866 1021q0 -27 -13 -94q-11 -50 -31.5 -150t-30.5 -150q-2 -11 -4.5 -12.5t-13.5 -2.5q-20 -2 -31 -2q-58 0 -84 49.5t-26 113.5q0 88 35 174t103 124q28 14 51 14q28 0 36.5 -16.5t8.5 -47.5zM1352 597q0 14 -39 75.5t-52 66.5q-21 8 -34 8q-91 0 -226 -77l-2 2 q3 22 27.5 135t24.5 178q0 233 -242 233q-24 0 -68 -6q-94 -17 -168.5 -89.5t-111.5 -166.5t-37 -189q0 -146 80.5 -225t227.5 -79q25 0 25 -3t-1 -5q-4 -34 -26 -117q-14 -52 -51.5 -101t-82.5 -49q-42 0 -42 47q0 24 10.5 47.5t25 39.5t29.5 28.5t26 20t11 8.5q0 3 -7 10 q-24 22 -58.5 36.5t-65.5 14.5q-35 0 -63.5 -34t-41 -75t-12.5 -75q0 -88 51.5 -142t138.5 -54q82 0 155 53t117.5 126t65.5 153q6 22 15.5 66.5t14.5 66.5q3 12 14 18q118 60 227 60q48 0 127 -18q1 -1 4 -1q5 0 9.5 4.5t4.5 8.5zM1536 1120v-960q0 -119 -84.5 -203.5 t-203.5 -84.5h-960q-119 0 -203.5 84.5t-84.5 203.5v960q0 119 84.5 203.5t203.5 84.5h960q119 0 203.5 -84.5t84.5 -203.5z" />
<glyph unicode="&#xf2a6;" horiz-adv-x="1535" d="M744 1231q0 24 -2 38.5t-8.5 30t-21 23t-37.5 7.5q-39 0 -78 -23q-105 -58 -159 -190.5t-54 -269.5q0 -44 8.5 -85.5t26.5 -80.5t52.5 -62.5t81.5 -23.5q4 0 18 -0.5t20 0t16 3t15 8.5t7 16q16 77 48 231.5t48 231.5q19 91 19 146zM1498 575q0 -7 -7.5 -13.5t-15.5 -6.5 l-6 1q-22 3 -62 11t-72 12.5t-63 4.5q-167 0 -351 -93q-15 -8 -21 -27q-10 -36 -24.5 -105.5t-22.5 -100.5q-23 -91 -70 -179.5t-112.5 -164.5t-154.5 -123t-185 -47q-135 0 -214.5 83.5t-79.5 219.5q0 53 19.5 117t63 116.5t97.5 52.5q38 0 120 -33.5t83 -61.5 q0 -1 -16.5 -12.5t-39.5 -31t-46 -44.5t-39 -61t-16 -74q0 -33 16.5 -53t48.5 -20q45 0 85 31.5t66.5 78t48 105.5t32.5 107t16 90v9q0 2 -3.5 3.5t-8.5 1.5h-10t-10 -0.5t-6 -0.5q-227 0 -352 122.5t-125 348.5q0 108 34.5 221t96 210t156 167.5t204.5 89.5q52 9 106 9 q374 0 374 -360q0 -98 -38 -273t-43 -211l3 -3q101 57 182.5 88t167.5 31q22 0 53 -13q19 -7 80 -102.5t61 -116.5z" />
<glyph unicode="&#xf2a7;" horiz-adv-x="1664" d="M831 863q32 0 59 -18l222 -148q61 -40 110 -97l146 -170q40 -46 29 -106l-72 -413q-6 -32 -29.5 -53.5t-55.5 -25.5l-527 -56l-352 -32h-9q-39 0 -67.5 28t-28.5 68q0 37 27 64t65 32l260 32h-448q-41 0 -69.5 30t-26.5 71q2 39 32 65t69 26l442 1l-521 64q-41 5 -66 37 t-19 73q6 35 34.5 57.5t65.5 22.5h10l481 -60l-351 94q-38 10 -62 41.5t-18 68.5q6 36 33 58.5t62 22.5q6 0 20 -2l448 -96l217 -37q1 0 3 -0.5t3 -0.5q23 0 30.5 23t-12.5 36l-186 125q-35 23 -42 63.5t18 73.5q27 38 76 38zM761 661l186 -125l-218 37l-5 2l-36 38 l-238 262q-1 1 -2.5 3.5t-2.5 3.5q-24 31 -18.5 70t37.5 64q31 23 68 17.5t64 -33.5l142 -147l-4 -4t-5 -4q-32 -45 -23 -99t55 -85zM1648 1115l15 -266q4 -73 -11 -147l-48 -219q-12 -59 -67 -87l-106 -54q2 62 -39 109l-146 170q-53 61 -117 103l-222 148q-34 23 -76 23 q-51 0 -88 -37l-235 312q-25 33 -18 73.5t41 63.5q33 22 71.5 14t62.5 -40l266 -352l-262 455q-21 35 -10.5 75t47.5 59q35 18 72.5 6t57.5 -46l241 -420l-136 337q-15 35 -4.5 74t44.5 56q37 19 76 6t56 -51l193 -415l101 -196q8 -15 23 -17.5t27 7.5t11 26l-12 224 q-2 41 26 71t69 31q39 0 67 -28.5t30 -67.5z" />
<glyph unicode="&#xf2a8;" horiz-adv-x="1792" d="M335 180q-2 0 -6 2q-86 57 -168.5 145t-139.5 180q-21 30 -21 69q0 9 2 19t4 18t7 18t8.5 16t10.5 17t10 15t12 15.5t11 14.5q184 251 452 365q-110 198 -110 211q0 19 17 29q116 64 128 64q18 0 28 -16l124 -229q92 19 192 19q266 0 497.5 -137.5t378.5 -369.5 q20 -31 20 -69t-20 -69q-91 -142 -218.5 -253.5t-278.5 -175.5q110 -198 110 -211q0 -20 -17 -29q-116 -64 -127 -64q-19 0 -29 16l-124 229l-64 119l-444 820l7 7q-58 -24 -99 -47q3 -5 127 -234t243 -449t119 -223q0 -7 -9 -9q-13 -3 -72 -3q-57 0 -60 7l-456 841 q-39 -28 -82 -68q24 -43 214 -393.5t190 -354.5q0 -10 -11 -10q-14 0 -82.5 22t-72.5 28l-106 197l-224 413q-44 -53 -78 -106q2 -3 18 -25t23 -34l176 -327q0 -10 -10 -10zM1165 282l49 -91q273 111 450 385q-180 277 -459 389q67 -64 103 -148.5t36 -176.5 q0 -106 -47 -200.5t-132 -157.5zM848 896q0 -20 14 -34t34 -14q86 0 147 -61t61 -147q0 -20 14 -34t34 -14t34 14t14 34q0 126 -89 215t-215 89q-20 0 -34 -14t-14 -34zM1214 961l-9 4l7 -7z" />
<glyph unicode="&#xf2a9;" horiz-adv-x="1280" d="M1050 430q0 -215 -147 -374q-148 -161 -378 -161q-232 0 -378 161q-147 159 -147 374q0 147 68 270.5t189 196.5t268 73q96 0 182 -31q-32 -62 -39 -126q-66 28 -143 28q-167 0 -280.5 -123t-113.5 -291q0 -170 112.5 -288.5t281.5 -118.5t281 118.5t112 288.5 q0 89 -32 166q66 13 123 49q41 -98 41 -212zM846 619q0 -192 -79.5 -345t-238.5 -253l-14 -1q-29 0 -62 5q83 32 146.5 102.5t99.5 154.5t58.5 189t30 192.5t7.5 178.5q0 69 -3 103q55 -160 55 -326zM791 947v-2q-73 214 -206 440q88 -59 142.5 -186.5t63.5 -251.5z M1035 744q-83 0 -160 75q218 120 290 247q19 37 21 56q-42 -94 -139.5 -166.5t-204.5 -97.5q-35 54 -35 113q0 37 17 79t43 68q46 44 157 74q59 16 106 58.5t74 100.5q74 -105 74 -253q0 -109 -24 -170q-32 -77 -88.5 -130.5t-130.5 -53.5z" />
<glyph unicode="&#xf2aa;" d="M1050 495q0 78 -28 147q-41 -25 -85 -34q22 -50 22 -114q0 -117 -77 -198.5t-193 -81.5t-193.5 81.5t-77.5 198.5q0 115 78 199.5t193 84.5q53 0 98 -19q4 43 27 87q-60 21 -125 21q-154 0 -257.5 -108.5t-103.5 -263.5t103.5 -261t257.5 -106t257.5 106.5t103.5 260.5z M872 850q2 -24 2 -71q0 -63 -5 -123t-20.5 -132.5t-40.5 -130t-68.5 -106t-100.5 -70.5q21 -3 42 -3h10q219 139 219 411q0 116 -38 225zM872 850q-4 80 -44 171.5t-98 130.5q92 -156 142 -302zM1207 955q0 102 -51 174q-41 -86 -124 -109q-69 -19 -109 -53.5t-40 -99.5 q0 -40 24 -77q74 17 140.5 67t95.5 115q-4 -52 -74.5 -111.5t-138.5 -97.5q52 -52 110 -52q51 0 90 37t60 90q17 43 17 117zM1536 1120v-960q0 -119 -84.5 -203.5t-203.5 -84.5h-960q-119 0 -203.5 84.5t-84.5 203.5v960q0 119 84.5 203.5t203.5 84.5h960q119 0 203.5 -84.5 t84.5 -203.5z" />
<glyph unicode="&#xf2ab;" d="M1279 388q0 22 -22 27q-67 15 -118 59t-80 108q-7 19 -7 25q0 15 19.5 26t43 17t43 20.5t19.5 36.5q0 19 -18.5 31.5t-38.5 12.5q-12 0 -32 -8t-31 -8q-4 0 -12 2q5 95 5 114q0 79 -17 114q-36 78 -103 121.5t-152 43.5q-199 0 -275 -165q-17 -35 -17 -114q0 -19 5 -114 q-4 -2 -14 -2q-12 0 -32 7.5t-30 7.5q-21 0 -38.5 -12t-17.5 -32q0 -21 19.5 -35.5t43 -20.5t43 -17t19.5 -26q0 -6 -7 -25q-64 -138 -198 -167q-22 -5 -22 -27q0 -46 137 -68q2 -5 6 -26t11.5 -30.5t23.5 -9.5q12 0 37.5 4.5t39.5 4.5q35 0 67 -15t54 -32.5t57.5 -32.5 t76.5 -15q43 0 79 15t57.5 32.5t53.5 32.5t67 15q14 0 39.5 -4t38.5 -4q16 0 23 10t11 30t6 25q137 22 137 68zM1536 640q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5 t103 -385.5z" />
<glyph unicode="&#xf2ac;" horiz-adv-x="1664" d="M848 1408q134 1 240.5 -68.5t163.5 -192.5q27 -58 27 -179q0 -47 -9 -191q14 -7 28 -7q18 0 51 13.5t51 13.5q29 0 56 -18t27 -46q0 -32 -31.5 -54t-69 -31.5t-69 -29t-31.5 -47.5q0 -15 12 -43q37 -82 102.5 -150t144.5 -101q28 -12 80 -23q28 -6 28 -35 q0 -70 -219 -103q-7 -11 -11 -39t-14 -46.5t-33 -18.5q-20 0 -62 6.5t-64 6.5q-37 0 -62 -5q-32 -5 -63 -22.5t-58 -38t-58 -40.5t-76 -33.5t-99 -13.5q-52 0 -96.5 13.5t-75 33.5t-57.5 40.5t-58 38t-62 22.5q-26 5 -63 5q-24 0 -65.5 -7.5t-58.5 -7.5q-25 0 -35 18.5 t-14 47.5t-11 40q-219 33 -219 103q0 29 28 35q52 11 80 23q78 32 144.5 101t102.5 150q12 28 12 43q0 28 -31.5 47.5t-69.5 29.5t-69.5 31.5t-31.5 52.5q0 27 26 45.5t55 18.5q15 0 48 -13t53 -13q18 0 32 7q-9 142 -9 190q0 122 27 180q64 137 172 198t264 63z" />
<glyph unicode="&#xf2ad;" d="M1280 388q0 22 -22 27q-67 14 -118 58t-80 109q-7 14 -7 25q0 15 19.5 26t42.5 17t42.5 20.5t19.5 36.5q0 19 -18.5 31.5t-38.5 12.5q-11 0 -31 -8t-32 -8q-4 0 -12 2q5 63 5 115q0 78 -17 114q-36 78 -102.5 121.5t-152.5 43.5q-198 0 -275 -165q-18 -38 -18 -115 q0 -38 6 -114q-10 -2 -15 -2q-11 0 -31.5 8t-30.5 8q-20 0 -37.5 -12.5t-17.5 -32.5q0 -21 19.5 -35.5t42.5 -20.5t42.5 -17t19.5 -26q0 -11 -7 -25q-64 -138 -198 -167q-22 -5 -22 -27q0 -47 138 -69q2 -5 6 -26t11 -30.5t23 -9.5q13 0 38.5 5t38.5 5q35 0 67.5 -15 t54.5 -32.5t57.5 -32.5t76.5 -15q43 0 79 15t57.5 32.5t54 32.5t67.5 15q13 0 39 -4.5t39 -4.5q15 0 22.5 9.5t11.5 31t5 24.5q138 22 138 69zM1536 1120v-960q0 -119 -84.5 -203.5t-203.5 -84.5h-960q-119 0 -203.5 84.5t-84.5 203.5v960q0 119 84.5 203.5t203.5 84.5h960 q119 0 203.5 -84.5t84.5 -203.5z" />
<glyph unicode="&#xf2ae;" horiz-adv-x="1792" />
<glyph unicode="&#xf2b0;" horiz-adv-x="1792" />
<glyph unicode="&#xf2b1;" horiz-adv-x="1792" />
<glyph unicode="&#xf2b2;" horiz-adv-x="1792" />
<glyph unicode="&#xf2b3;" horiz-adv-x="1792" />
<glyph unicode="&#xf2b4;" horiz-adv-x="1792" />
<glyph unicode="&#xf2b5;" horiz-adv-x="1792" />
<glyph unicode="&#xf2b6;" horiz-adv-x="1792" />
<glyph unicode="&#xf2b7;" horiz-adv-x="1792" />
<glyph unicode="&#xf2b8;" horiz-adv-x="1792" />
<glyph unicode="&#xf2b9;" horiz-adv-x="1792" />
<glyph unicode="&#xf2ba;" horiz-adv-x="1792" />
<glyph unicode="&#xf2bb;" horiz-adv-x="1792" />
<glyph unicode="&#xf2bc;" horiz-adv-x="1792" />
<glyph unicode="&#xf2bd;" horiz-adv-x="1792" />
<glyph unicode="&#xf2be;" horiz-adv-x="1792" />
<glyph unicode="&#xf500;" horiz-adv-x="1792" />
</font>
</defs></svg>

Before

Width:  |  Height:  |  Size: 357 KiB

After

Width:  |  Height:  |  Size: 377 KiB

Binary file not shown.

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