- ESI API switch, closed #473

This commit is contained in:
Exodus4D
2017-04-12 14:40:24 +02:00
parent b44861ad10
commit 75da842849
41 changed files with 640 additions and 776 deletions

2
.gitignore vendored
View File

@@ -50,3 +50,5 @@ Temporary Items
.sass-cache
.usage
*.gz
/composer-dev.lock
/composer.lock

View File

@@ -27,10 +27,12 @@ DB_CCP_USER = root
DB_CCP_PASS =
; 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 =
CCP_SSO_URL = https://sisilogin.testeveonline.com
CCP_SSO_CLIENT_ID =
CCP_SSO_SECRET_KEY =
CCP_ESI_URL = https://esi.tech.ccp.is
CCP_ESI_DATASOURCE = singularity
CCP_ESI_SCOPES = esi-location.read_location.v1,esi-location.read_ship_type.v1,esi-ui.write_waypoint.v1,esi-ui.open_window.v1
; CCP XML APIv2
CCP_XML = https://api.testeveonline.com
@@ -71,10 +73,12 @@ 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 =
CCP_SSO_URL = https://login.eveonline.com
CCP_SSO_CLIENT_ID =
CCP_SSO_SECRET_KEY =
CCP_ESI_URL = https://esi.tech.ccp.is
CCP_ESI_DATASOURCE = tranquility
CCP_ESI_SCOPES = esi-location.read_location.v1,esi-location.read_ship_type.v1,esi-ui.write_waypoint.v1,esi-ui.open_window.v1
; CCP XML APIv2
CCP_XML = https://api.eveonline.com

View File

@@ -385,20 +385,25 @@ class System extends Controller\AccessController {
$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'], [
if( $accessToken = $activeCharacter->getAccessToken() ){
$options = [
'clearOtherWaypoints' => $return->clearOtherWaypoints,
'first' => $return->first,
]);
if($waypointData['systemId']){
$return->systemData[] = $systemData;
}elseif( isset($waypointData['error']) ){
$return->error[] = $waypointData['error'];
'addToBeginning' => $return->first,
];
foreach($postData['systemData'] as $systemData){
$response = $f3->ccpClient->setWaypoint($systemData['systemId'], $accessToken, $options);
if(empty($response)){
$return->systemData[] = $systemData;
}else{
$error = (object) [];
$error->type = 'error';
$error->message = $response['error'];
$return->error[] = $error;
}
}
}
}

View File

@@ -119,7 +119,7 @@ class User extends Controller\Controller{
}else{
$characterError = (object) [];
$characterError->type = 'warning';
$characterError->message = 'This can happen through "invalid cookie data", "login restrictions", "CREST problems".';
$characterError->message = 'This can happen through "invalid cookies(SSO)", "login restrictions", "ESI problems".';
$return->error[] = $characterError;
}
}
@@ -196,10 +196,39 @@ class User extends Controller\Controller{
echo json_encode($return);
}
/**
* remote open ingame information window (character, corporation or alliance) Id
* -> the type is auto-recognized by CCP
* @param \Base $f3
*/
public function openIngameWindow(\Base $f3){
$data = $f3->get('POST');
$return = (object) [];
$return->error = [];
if( $targetId = (int)$data['targetId']){
$activeCharacter = $this->getCharacter();
$response = $f3->ccpClient->openWindow($targetId, $activeCharacter->getAccessToken());
if(empty($response)){
$return->targetId = $targetId;
}else{
$error = (object) [];
$error->type = 'error';
$error->message = $response['error'];
$return->error[] = $error;
}
}
echo json_encode($return);
}
/**
* update user account data
* -> a fresh user automatically generated on first login with a new character
* -> see CREST SSO login
* -> see SSO login
* @param \Base $f3
*/
public function saveAccount(\Base $f3){

View File

@@ -5,7 +5,7 @@
* Date: 23.01.2016
* Time: 17:18
*
* Handles access to EVE-Online "CREST API" and "SSO" auth functions
* Handles access to EVE-Online "ESI 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!
@@ -14,7 +14,6 @@
namespace Controller\Ccp;
use Controller;
use Controller\Api as Api;
use Data\Mapper as Mapper;
use Model;
use Lib;
@@ -23,7 +22,7 @@ class Sso extends Api\User{
/**
* @var int timeout (seconds) for API calls
*/
const CREST_TIMEOUT = 4;
const SSO_TIMEOUT = 4;
/**
* @var int expire time (seconds) for an valid "accessToken"
@@ -37,32 +36,16 @@ class Sso extends Api\User{
const SESSION_KEY_SSO_FROM_MAP = 'SESSION.SSO.FROM_MAP';
// 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_CCP_SSO_URL = 'Invalid "ENVIRONMENT.[ENVIRONMENT].CCP_SSO_URL" url. %s';
const ERROR_CCP_CLIENT_ID = 'Missing "ENVIRONMENT.[ENVIRONMENT].CCP_SSO_CLIENT_ID".';
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_VERIFICATION = 'Character verification failed by SSP SSO';
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)
@@ -70,7 +53,7 @@ class Sso extends Api\User{
*/
public function requestAuthorization($f3){
if( !empty($ssoCcpClientId = Controller\Controller::getEnvironmentData('SSO_CCP_CLIENT_ID')) ){
if( !empty($ssoCcpClientId = Controller\Controller::getEnvironmentData('CCP_SSO_CLIENT_ID')) ){
$params = $f3->get('GET');
if(
@@ -94,8 +77,8 @@ class Sso extends Api\User{
($activeCharacter->getUser()->_id === $character->getUser()->_id)
){
// requested character belongs to current user
// -> update character vom CREST (e.g. corp changed,..)
$updateStatus = $character->updateFromCrest();
// -> update character vom ESI (e.g. corp changed,..)
$updateStatus = $character->updateFromESI();
if( empty($updateStatus) ){
@@ -111,7 +94,7 @@ class Sso extends Api\User{
if($loginCheck){
// set "login" cookie
$this->setLoginCookie($character);
$this->setLoginCookie($character, $this->getRequestedScopeHash());
// -> pass current character data to target page
$f3->set(Api\User::SESSION_KEY_TEMP_CHARACTER_ID, $character->_id);
@@ -136,8 +119,8 @@ class Sso extends Api\User{
$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),
'client_id' => Controller\Controller::getEnvironmentData('CCP_SSO_CLIENT_ID'),
'scope' => implode(' ', Controller\Controller::getEnvironmentData('CCP_ESI_SCOPES')),
'state' => $state
];
@@ -149,7 +132,7 @@ class Sso extends Api\User{
}else{
// SSO clientId missing
$f3->set(self::SESSION_KEY_SSO_ERROR, self::ERROR_CCP_CLIENT_ID);
self::getCrestLogger()->write(self::ERROR_CCP_CLIENT_ID);
self::getSSOLogger()->write(self::ERROR_CCP_CLIENT_ID);
$f3->reroute('@login');
}
}
@@ -199,11 +182,11 @@ class Sso extends Api\User{
// verification available data. Data is needed for "ownerHash" check
// get character data from CREST
$characterData = $this->getCharacterData($accessData->accessToken);
// get character data from ESI
$characterData = $this->getCharacterData($verificationCharacterData->CharacterID);
if( isset($characterData->character) ){
// add "ownerHash" and CREST tokens
// add "ownerHash" and SSO tokens
$characterData->character['ownerHash'] = $verificationCharacterData->CharacterOwnerHash;
$characterData->character['crestAccessToken'] = $accessData->accessToken;
$characterData->character['crestRefreshToken'] = $accessData->refreshToken;
@@ -253,7 +236,7 @@ class Sso extends Api\User{
if($loginCheck){
// set "login" cookie
$this->setLoginCookie($characterModel);
$this->setLoginCookie($characterModel, $this->getRequestedScopeHash());
// -> pass current character data to target page
$f3->set(Api\User::SESSION_KEY_TEMP_CHARACTER_ID, $characterModel->_id);
@@ -270,15 +253,15 @@ class Sso extends Api\User{
}
}
}else{
// failed to verify character by CREST
// failed to verify character by CCP SSO
$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));
// SSO "accessData" missing (e.g. timeout)
$f3->set(self::SESSION_KEY_SSO_ERROR, sprintf(self::ERROR_SERVICE_TIMEOUT, self::SSO_TIMEOUT));
}
}else{
// invalid CREST response
// invalid SSO response
$f3->set(self::SESSION_KEY_SSO_ERROR, sprintf(self::ERROR_LOGIN_FAILED, 'Invalid response'));
}
}
@@ -345,7 +328,7 @@ class Sso extends Api\User{
$accessData = $this->verifyAuthorizationCode($authCode);
}else{
// Unable to get Token -> trigger error
self::getCrestLogger()->write(sprintf(self::ERROR_ACCESS_TOKEN, $authCode));
self::getSSOLogger()->write(sprintf(self::ERROR_ACCESS_TOKEN, $authCode));
}
return $accessData;
@@ -398,7 +381,7 @@ class Sso extends Api\User{
if($verifyAuthCodeUrlParts){
$contentType = 'application/x-www-form-urlencoded';
$requestOptions = [
'timeout' => self::CREST_TIMEOUT,
'timeout' => self::SSO_TIMEOUT,
'method' => 'POST',
'user_agent' => $this->getUserAgent(),
'header' => [
@@ -428,7 +411,7 @@ class Sso extends Api\User{
}
}
}else{
self::getCrestLogger()->write(
self::getSSOLogger()->write(
sprintf(
self::ERROR_ACCESS_TOKEN,
print_r($requestParams, true)
@@ -436,7 +419,7 @@ class Sso extends Api\User{
);
}
}else{
self::getCrestLogger()->write(
self::getSSOLogger()->write(
sprintf(self::ERROR_CCP_SSO_URL, __METHOD__)
);
}
@@ -447,7 +430,7 @@ class Sso extends Api\User{
/**
* verify character data by "access_token"
* -> get some basic information (like character id)
* -> if more character information is required, use CREST endpoints request instead
* -> if more character information is required, use ESI "characters" endpoints request instead
* @param $accessToken
* @return mixed|null
*/
@@ -458,7 +441,7 @@ class Sso extends Api\User{
if($verifyUrlParts){
$requestOptions = [
'timeout' => self::CREST_TIMEOUT,
'timeout' => self::SSO_TIMEOUT,
'method' => 'GET',
'user_agent' => $this->getUserAgent(),
'header' => [
@@ -472,271 +455,83 @@ class Sso extends Api\User{
if($apiResponse['body']){
$characterData = json_decode($apiResponse['body']);
}else{
self::getCrestLogger()->write(sprintf(self::ERROR_VERIFY_CHARACTER, __METHOD__));
self::getSSOLogger()->write(sprintf(self::ERROR_VERIFY_CHARACTER, __METHOD__));
}
}else{
self::getCrestLogger()->write(sprintf(self::ERROR_CCP_SSO_URL, __METHOD__));
self::getSSOLogger()->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-v5+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
* @param int $characterId
* @return object
*/
public function getCharacterData($accessToken, $additionalOptions = []){
$endpoints = $this->getEndpoints($accessToken, $additionalOptions);
public function getCharacterData($characterId){
$characterData = (object) [];
$endpoint = $this->walkEndpoint($endpoints, $accessToken, [
'decode',
'character'
], $additionalOptions);
$characterDataBasic = $this->getF3()->ccpClient->getCharacterData($characterId);
if( !empty($endpoint) ){
$crestCharacterData = (new Mapper\CrestCharacter($endpoint))->getData();
$characterData->character = $crestCharacterData
;
if(isset($endpoint['corporation'])){
$characterData->corporation = (new Mapper\CrestCorporation($endpoint['corporation']))->getData();
}
if( !empty($characterDataBasic) ){
// remove some "unwanted" data -> not relevant for Pathfinder
$characterData->character = array_filter($characterDataBasic, function($key){
return in_array($key, ['id', 'name', 'securityStatus']);
}, ARRAY_FILTER_USE_KEY);
// 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();
}
*/
$characterData->corporation = null;
$characterData->alliance = null;
$xmlCharacterData = (new Xml())->getPublicCharacterData( (int)$crestCharacterData['id'] );
if(isset($xmlCharacterData['alli'])){
$characterData->alliance = $xmlCharacterData['alli'];
}
}
if(isset($characterDataBasic['corporation'])){
$corporationId = (int)$characterDataBasic['corporation']['id'];
return $characterData;
}
/**
* @var Model\CorporationModel $corporationModel
*/
$corporationModel = Model\BasicModel::getNew('CorporationModel');
$corporationModel->getById($corporationId, 0);
/**
* get current character location data (result is cached!)
* -> solarSystem data where character is currently active
* @param $accessToken
* @param array $additionalOptions
* @return array
*/
public function getCharacterLocationData($accessToken, $additionalOptions = []){
// null == CREST call failed (e.g. timeout)
$locationData = [
'timeout' => false
];
if($corporationModel->dry()){
// request corporation data
$corporationData = $this->getF3()->ccpClient->getCorporationData($corporationId);
$endpoints = $this->getEndpoints($accessToken, $additionalOptions);
if( !empty($corporationData) ){
// check for NPC corporation
$corporationData['isNPC'] = $this->getF3()->ccpClient->isNpcCorporation($corporationId);
$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();
}
}else{
// timeout
$locationData['timeout'] = true;
}
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 ){
$accessToken = $character->getAccessToken();
$endpoints = $this->getEndpoints($accessToken);
// get endpoint list for "ui" endpoints
$uiEndpoints = $endpoint = $this->walkEndpoint($endpoints, $accessToken, [
'decode',
'character',
'ui'
]);
if(
isset($uiEndpoints['setWaypoints']) &&
isset($uiEndpoints['setWaypoints']['href'])
){
$endpointUrl = $uiEndpoints['setWaypoints']['href'];
$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;
$corporationModel->copyfrom($corporationData, ['id', 'name', 'isNPC']);
$characterData->corporation = $corporationModel->save();
}
}else{
$characterData->corporation = $corporationModel;
}
}
if(isset($characterDataBasic['alliance'])){
$allianceId = (int)$characterDataBasic['alliance']['id'];
/**
* @var Model\AllianceModel $allianceModel
*/
$allianceModel = Model\BasicModel::getNew('AllianceModel');
$allianceModel->getById($allianceId, 0);
if($allianceModel->dry()){
// request alliance data
$allianceData = $this->getF3()->ccpClient->getAllianceData($allianceId);
if( !empty($allianceData) ){
$allianceModel->copyfrom($allianceData, ['id', 'name']);
$characterData->alliance = $allianceModel->save();
}
}else{
$characterData->alliance = $allianceModel;
}
}
}
return $waypointData;
return $characterData;
}
/**
@@ -746,96 +541,24 @@ class Sso extends Api\User{
* @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((int)$characterData->corporation['id'], 0);
$corporationModel->copyfrom($characterData->corporation);
$corporationModel->save();
}
if( !empty($characterData->character) ){
if( isset($characterData->alliance) ){
/**
* @var Model\AllianceModel $allianceModel
*/
$allianceModel = Model\BasicModel::getNew('AllianceModel');
$allianceModel->getById((int)$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((int)$characterData->character['id'], 0);
$characterModel->copyfrom($characterData->character);
$characterModel->corporationId = $corporationModel;
$characterModel->allianceId = $allianceModel;
$characterModel->copyfrom($characterData->character, ['id', 'name', 'ownerHash', 'crestAccessToken', 'crestRefreshToken', 'securityStatus']);
$characterModel->corporationId = $characterData->corporation;
$characterModel->allianceId = $characterData->alliance;
$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 = (string) $endpoint;
}
$endpoint = $this->walkEndpoint($endpoints, '', ['userCount_str']);
if( !empty($endpoint) ){
$data->crestOffline = false;
$data->userCounts = (string) $endpoint;
}
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!
@@ -843,29 +566,11 @@ class Sso extends Api\User{
*/
protected function getAuthorizationHeader(){
return base64_encode(
Controller\Controller::getEnvironmentData('SSO_CCP_CLIENT_ID') . ':'
. Controller\Controller::getEnvironmentData('SSO_CCP_SECRET_KEY')
Controller\Controller::getEnvironmentData('CCP_SSO_CLIENT_ID') . ':'
. Controller\Controller::getEnvironmentData('CCP_SSO_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
@@ -873,11 +578,11 @@ class Sso extends Api\User{
*/
static function getSsoUrlRoot(){
$url = '';
if( \Audit::instance()->url(self::getEnvironmentData('SSO_CCP_URL')) ){
$url = self::getEnvironmentData('SSO_CCP_URL');
if( \Audit::instance()->url(self::getEnvironmentData('CCP_SSO_URL')) ){
$url = self::getEnvironmentData('CCP_SSO_URL');
}else{
$error = sprintf(self::ERROR_CCP_SSO_URL, __METHOD__);
self::getCrestLogger()->write($error);
self::getSSOLogger()->write($error);
\Base::instance()->error(502, $error);
}
@@ -897,10 +602,10 @@ class Sso extends Api\User{
}
/**
* get logger for CREST logging
* get logger for SSO logging
* @return \Log
*/
static function getCrestLogger(){
return parent::getLogger('CREST');
static function getSSOLogger(){
return parent::getLogger('SSO');
}
}

View File

@@ -1,74 +0,0 @@
<?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

@@ -189,8 +189,9 @@ class Controller {
* set/update logged in cookie by character model
* -> store validation data in DB
* @param Model\CharacterModel $character
* @param string $scopeHash
*/
protected function setLoginCookie(Model\CharacterModel $character){
protected function setLoginCookie(Model\CharacterModel $character, $scopeHash = ''){
if( $this->getCookieState() ){
$expireSeconds = (int) $this->getF3()->get('PATHFINDER.LOGIN.COOKIE_EXPIRE');
@@ -222,7 +223,8 @@ class Controller {
'characterId' => $character,
'selector' => $selector,
'token' => $token,
'expires' => $expireTime->format('Y-m-d H:i:s')
'expires' => $expireTime->format('Y-m-d H:i:s'),
'scopeHash' => $scopeHash
];
$authenticationModel = $character->rel('characterAuthentications');
@@ -270,20 +272,20 @@ class Controller {
// cookie data is well formatted
$characterAuth->getByForeignKey('selector', $data[0], ['limit' => 1]);
// validate expire data
// validate token
// validate "scope hash", "expire data" and "validate token"
if( !$characterAuth->dry() ){
if(
$characterAuth->scopeHash === $this->getRequestedScopeHash() &&
strtotime($characterAuth->expires) >= $currentTime->getTimestamp() &&
hash_equals($characterAuth->token, hash('sha256', $data[1]))
){
// cookie information is valid
// -> try to update character information from CREST
// -> try to update character information from ESI
// e.g. Corp has changed, this also ensures valid "access_token"
/**
* @var $character Model\CharacterModel
*/
$updateStatus = $characterAuth->characterId->updateFromCrest();
$updateStatus = $characterAuth->characterId->updateFromESI();
if( empty($updateStatus) ){
// make sure character data is up2date!
@@ -293,13 +295,15 @@ class Controller {
// 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,..)
// -> do NOT remove cookie on failure. This can be a temporary problem (e.g. ESI is down,..)
if(
$character->hasUserCharacter() &&
$character->isAuthorized()
){
$characters[$name] = $character;
}
}else{
$invalidCookie = true;
}
}else{
// clear existing authentication data from DB
@@ -449,6 +453,15 @@ class Controller {
return $user;
}
/**
* get a hash over all requested ESI scopes
* -> this helps to invalidate "authentication data" after scope change
* @return string
*/
protected function getRequestedScopeHash(){
return md5(serialize( self::getEnvironmentData('CCP_ESI_SCOPES') ));
}
/**
* log out current character
* @param \Base $f3
@@ -477,16 +490,33 @@ class Controller {
* @param \Base $f3
*/
public function getEveServerStatus(\Base $f3){
// server status can be cached for some seconds
$cacheKey = 'eve_server_status';
if( !$f3->exists($cacheKey, $return) ){
$return = (object) [];
$return->error = [];
$return->status = [
'serverName' => strtoupper( self::getEnvironmentData('CCP_ESI_DATASOURCE') ),
'serviceStatus' => 'offline'
];
$sso = new Sso();
$return->status = $sso->getCrestServerStatus();
$response = $f3->ccpClient->getServerStatus();
if( !empty($response) ){
// calculate time diff since last server restart
$timezone = new \DateTimeZone( $f3->get('TZ') );
$dateNow = new \DateTime('now', $timezone);
$dateServerStart = new \DateTime($response['startTime']);
$interval = $dateNow->diff($dateServerStart);
$startTimestampFormat = $interval->format('%hh %im');
if($interval->days > 0){
$startTimestampFormat = $interval->days . 'd ' . $startTimestampFormat;
}
$response['serverName'] = strtoupper( self::getEnvironmentData('CCP_ESI_DATASOURCE') );
$response['serviceStatus'] = 'online';
$response['startTime'] = $startTimestampFormat;
$return->status = $response;
if( !$return->status->crestOffline ){
$f3->set($cacheKey, $return, 60);
}
}
@@ -797,7 +827,7 @@ class Controller {
/**
* get environment specific configuration data
* @param string $key
* @return string|null
* @return string|array|null
*/
static function getEnvironmentData($key){
return Config::getEnvironmentData($key);

View File

@@ -34,10 +34,11 @@ class Setup extends Controller {
'DB_CCP_NAME',
'DB_CCP_USER',
'DB_CCP_PASS',
'CCP_CREST_URL',
'SSO_CCP_URL',
'SSO_CCP_CLIENT_ID',
'SSO_CCP_SECRET_KEY',
'CCP_SSO_URL',
'CCP_SSO_CLIENT_ID',
'CCP_SSO_SECRET_KEY',
'CCP_ESI_URL',
'CCP_ESI_DATASOURCE',
'CCP_XML',
'SMTP_HOST',
'SMTP_PORT',
@@ -183,6 +184,8 @@ class Setup extends Controller {
$this->exportTable($params['exportTable']);
}elseif( !empty($params['clearCache']) ){
$this->clearCache($f3);
}elseif( !empty($params['invalidateCookies']) ){
$this->invalidateCookies($f3);
}
// set template data ----------------------------------------------------------------
@@ -314,7 +317,7 @@ class Setup extends Controller {
];
// obscure some values
$obscureVars = ['SSO_CCP_CLIENT_ID', 'SSO_CCP_SECRET_KEY', 'SMTP_PASS'];
$obscureVars = ['CCP_SSO_CLIENT_ID', 'CCP_SSO_SECRET_KEY', 'SMTP_PASS'];
foreach($this->environmentVars as $var){
if( !in_array($var, $excludeVars) ){
@@ -1203,6 +1206,21 @@ class Setup extends Controller {
$f3->clear('CACHE');
}
/**
* clear all character authentication (Cookie) data
* @param \Base $f3
*/
protected function invalidateCookies(\Base $f3){
$this->getDB('PF');
$authentidationModel = Model\BasicModel::getNew('CharacterAuthenticationModel');
$results = $authentidationModel->find();
if($results){
foreach($results as $result){
$result->erase();
}
}
}
/**
* convert Bytes to string + suffix
* @param int $bytes

View File

@@ -1,37 +0,0 @@
<?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

@@ -1,18 +0,0 @@
<?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

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

View File

@@ -1,20 +0,0 @@
<?php
/**
* Created by PhpStorm.
* User: Exodus
* Date: 31.01.2016
* Time: 03:55
*/
namespace Data\Mapper;
class CrestCorporation extends AbstractIterator {
protected static $map = [
'id' => 'id',
'name' => 'name',
'isNPC' => 'isNPC'
];
}

View File

@@ -1,18 +0,0 @@
<?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

@@ -1,18 +0,0 @@
<?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,45 @@
<?php
/**
* Created by PhpStorm.
* User: Exodus4D
* Date: 26.03.2017
* Time: 19:17
*/
namespace Lib;
use \Exodus4D\ESI\ESI;
class CcpClient extends \Prefab {
private $apiClient;
public function __construct(){
$f3 = \Base::instance();
$this->apiClient = new ESI($f3);
$this->apiClient->setEsiUrl( Config::getEnvironmentData('CCP_ESI_URL') );
$this->apiClient->setEsiDatasource( Config::getEnvironmentData('CCP_ESI_DATASOURCE') );
$this->apiClient->setUserAgent($this->getUserAgent($f3));
$f3->set('ccpClient', $this);
}
/**
* @param \Base $f3
* @return string
*/
protected function getUserAgent($f3){
$userAgent = '';
$userAgent .= $f3->get('PATHFINDER.NAME');
$userAgent .= ' - ' . $f3->get('PATHFINDER.VERSION');
$userAgent .= ' | ' . $f3->get('PATHFINDER.CONTACT');
$userAgent .= ' (' . $_SERVER['SERVER_NAME'] . ')';
return $userAgent;
}
public function __call($name, $arguments){
return call_user_func_array([$this->apiClient, $name], $arguments);
}
}

View File

@@ -16,6 +16,12 @@ class Config extends \Prefab {
const HIVE_KEY_PATHFINDER = 'PATHFINDER';
const HIVE_KEY_ENVIRONMENT = 'ENVIRONMENT';
/**
* environment config keys that should be parsed as array
* -> use "," as delimiter in config files/data
*/
const ARRAY_KEYS = ['CCP_ESI_SCOPES'];
/**
* all environment data
* @var array
@@ -79,6 +85,12 @@ class Config extends \Prefab {
if( !empty($this->serverConfigData['ENV']) ){
// get environment config from $_SERVER data
$environmentData = (array)$this->serverConfigData['ENV'];
// some environment variables should be parsed as array
array_walk($environmentData, function(&$item, $key){
$item = (in_array($key, self::ARRAY_KEYS)) ? explode(',', $item) : $item;
});
$environmentData['TYPE'] = 'PHP: environment variables';
}else{
// get environment data from *.ini file config

View File

@@ -8,8 +8,6 @@
namespace lib;
use controller\LogController;
class Socket {
// max TTL time (ms)

View File

@@ -15,8 +15,8 @@ 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
* max number of curls calls for a single resource until giving up...
* this is because SSO API is not very stable
*/
const RETRY_COUNT_MAX = 3;

View File

@@ -50,6 +50,11 @@ class CharacterAuthenticationModel extends BasicModel{
'type' => Schema::DT_TIMESTAMP,
'default' => Schema::DF_CURRENT_TIMESTAMP,
'index' => true
],
'scopeHash' => [
'type' => Schema::DT_VARCHAR128,
'nullable' => false,
'default' => ''
]
];

View File

@@ -35,9 +35,6 @@ class CharacterLogModel extends BasicModel {
]
]
],
// --------------------------------------------------------------------
'systemId' => [
'type' => Schema::DT_INT,
'index' => true
@@ -47,26 +44,6 @@ class CharacterLogModel extends BasicModel {
'nullable' => false,
'default' => ''
],
'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
@@ -110,24 +87,6 @@ class CharacterLogModel extends BasicModel {
$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'];
@@ -161,16 +120,6 @@ class CharacterLogModel extends BasicModel {
$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;

View File

@@ -75,15 +75,6 @@ class CharacterModel extends BasicModel {
]
]
],
'factionId' => [
'type' => Schema::DT_INT,
'index' => true
],
'factionName' => [
'type' => Schema::DT_VARCHAR128,
'nullable' => false,
'default' => ''
],
'shared' => [
'type' => Schema::DT_BOOL,
'nullable' => false,
@@ -94,6 +85,11 @@ class CharacterModel extends BasicModel {
'nullable' => false,
'default' => 1
],
'securityStatus' => [
'type' => Schema::DT_FLOAT,
'nullable' => false,
'default' => 0
],
'userCharacter' => [
'has-one' => ['Model\UserCharacterModel', 'characterId']
],
@@ -178,7 +174,7 @@ class CharacterModel extends BasicModel {
}
/**
* set CREST accessToken for current session
* set API accessToken for current session
* -> update "tokenUpdated" column on change
* -> this is required for expire checking!
* @param string $accessToken
@@ -191,6 +187,11 @@ class CharacterModel extends BasicModel {
return $accessToken;
}
/**
* logLocation specifies whether the current system should be tracked or not
* @param $logLocation
* @return bool
*/
public function set_logLocation($logLocation){
$logLocation = (bool)$logLocation;
if(
@@ -304,7 +305,7 @@ class CharacterModel extends BasicModel {
}
/**
* get CREST API "access_token" from OAuth
* get ESI API "access_token" from OAuth
* @return bool|string
*/
public function getAccessToken(){
@@ -405,32 +406,75 @@ class CharacterModel extends BasicModel {
/**
* update character log (active system, ...)
* -> CREST API request for character log data
* -> API request for character log data
* @param array $additionalOptions (optional) request options for cURL request
* @return $this
*/
public function updateLog($additionalOptions = []){
$deleteLog = true;
//check if log update is enabled for this user
if( $this->logLocation ){
// Try to pull data from CREST
$ssoController = new Sso();
$logData = $ssoController->getCharacterLocationData($this->getAccessToken(), $additionalOptions);
// Try to pull data from API
if( $accessToken = $this->getAccessToken() ){
if($logData['timeout'] === false){
if( empty($logData['system']) ){
// character is not in-game
if( $this->hasLog() ){
// delete existing log
$this->characterLog->erase();
$this->save();
}
}else{
$locationData = self::getF3()->ccpClient->getCharacterLocationData($this->_id, $accessToken, $additionalOptions);
if( !empty($locationData['system']['id']) ){
// character is currently in-game
// IDs for "systemId", "stationId and "shipTypeId" that require more data
$lookupIds = [];
if( !$characterLog = $this->getLog() ){
// create new log
$characterLog = $this->rel('characterLog');
$characterLog->characterId = $this->_id;
}
// get current log data and modify on change
$logData = json_decode(json_encode( $characterLog->getData()), true);
if($logData['system']['id'] !== $locationData['system']['id']){
// system changed -> request "system name" for current system
$lookupIds[] = $locationData['system']['id'];
}
if( !empty($locationData['station']['id']) ){
if( $logData['station']['id'] !== $locationData['station']['id'] ){
// station changed -> request "station name" for current station
$lookupIds[] = $locationData['station']['id'];
}
}else{
unset($logData['station']);
}
$logData = array_replace_recursive($logData, $locationData);
// get current ship data
$shipData = self::getF3()->ccpClient->getCharacterShipData($this->_id, $accessToken, $additionalOptions);
if( !empty($shipData['ship']['typeId']) ){
if($logData['ship']['typeId'] !== $shipData['ship']['typeId']){
// ship changed -> request "station name" for current station
$lookupIds[] = $shipData['ship']['typeId'];
}
// "shipName"/"shipId" could have changed...
$logData = array_replace_recursive($logData, $shipData);
}else{
unset($logData['ship']);
}
if( !empty($lookupIds) ){
// get "more" information for some Ids (e.g. name)
$universeData = self::getF3()->ccpClient->getUniverseNamesData($lookupIds, $additionalOptions);
$logData = array_replace_recursive($logData, $universeData);
}
$deleteLog = false;
$characterLog->setData($logData);
$characterLog->save();
@@ -439,14 +483,23 @@ class CharacterModel extends BasicModel {
}
}
if(
$deleteLog &&
$this->hasLog()
){
// delete existing log
$this->characterLog->erase();
$this->save();
}
return $this;
}
/**
* update character data from CCPs CREST API
* update character data from CCPs ESI API
* @return array (some status messages)
*/
public function updateFromCrest(){
public function updateFromESI(){
$status = [];
if( $accessToken = $this->getAccessToken() ){
@@ -458,36 +511,14 @@ class CharacterModel extends BasicModel {
!is_null( $verificationCharacterData = $ssoController->verifyCharacterData($accessToken) ) &&
$verificationCharacterData->CharacterID === $this->_id
){
// get character data from CREST
$characterData = $ssoController->getCharacterData($accessToken);
if( isset($characterData->character) ){
// get character data from API
$characterData = $ssoController->getCharacterData($this->_id);
if( !empty($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->copyfrom($characterData->character, ['ownerHash', 'securityStatus']);
$this->corporationId = $characterData->corporation;
$this->allianceId = $characterData->alliance;
$this->save();
}
}else{

View File

@@ -142,8 +142,8 @@ EXPIRE_SIGNATURES = 259200
[PATHFINDER.LOGFILES]
; error log
ERROR = error
; CREST error log
CREST = crest
; SSO error log
SSO = sso
; login information
LOGIN = login
; session warnings (suspect)

28
composer-dev.json Normal file
View File

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

View File

@@ -14,9 +14,15 @@
"Exodus4D\\Pathfinder\\": "app/main"
}
},
"repositories": [
{
"type": "vcs",
"url": "https://github.com/exodus4d/pathfinder_esi"
}],
"require": {
"php-64bit": ">=7.0",
"ext-zmq": "1.1.*",
"react/zmq": "0.3.*"
"react/zmq": "0.3.*",
"exodus4d/pathfinder_esi": "dev-master"
}
}
}

View File

@@ -1,6 +1,12 @@
<?php
session_name('pathfinder_session');
$f3 = require('app/lib/base.php');
$composerAutoloader = 'vendor/autoload.php';
if(file_exists($composerAutoloader)){
require_once($composerAutoloader);
}
$f3 = require_once('app/lib/base.php');
// load main config
$f3->config('app/config.ini');
@@ -8,6 +14,9 @@ $f3->config('app/config.ini');
// load environment dependent config
lib\Config::instance();
// initiate CCP API Client (ESI)
lib\CcpClient::instance();
// initiate cron-jobs
Cron::instance();

View File

@@ -16,6 +16,7 @@ define(['jquery'], function($) {
logIn: 'api/user/logIn', // ajax URL - login
logout: 'api/user/logout', // ajax URL - logout
deleteLog: 'api/user/deleteLog', // ajax URL - delete character log
openIngameWindow: 'api/user/openIngameWindow', // ajax URL - open inGame Window
saveUserConfig: 'api/user/saveAccount', // ajax URL - saves/update user account
deleteAccount: 'api/user/deleteAccount', // ajax URL - delete Account data
// access API

View File

@@ -2719,7 +2719,7 @@ define([
break;
case 'info':
// open map info dialog
$(document).triggerMenuEvent('ShowMapInfo');
$(document).triggerMenuEvent('ShowMapInfo', {tab: 'information'});
break;
}
@@ -3015,7 +3015,7 @@ define([
// set current location data for header update
headerUpdateData.currentSystemId = $(system).data('id');
//headerUpdateData.currentSystemName = currentCharacterLog.system.name;
headerUpdateData.currentSystemName = currentCharacterLog.system.name;
}
system.updateSystemUserData(map, tempUserData, currentUserIsHere);

View File

@@ -323,7 +323,7 @@ define([
class: 'fa fa-street-view fa-fw'
})
).on('click', function(){
$(document).triggerMenuEvent('ShowMapInfo');
$(document).triggerMenuEvent('ShowMapInfo', {tab: 'information'});
})
).append(
getMenuHeadline('Settings')
@@ -496,7 +496,7 @@ define([
// active pilots
$('.' + config.headActiveUserClass).find('a').on('click', function(){
$(document).triggerMenuEvent('ShowMapInfo');
$(document).triggerMenuEvent('ShowMapInfo', {tab: 'activity'});
});
// current location
@@ -661,9 +661,9 @@ define([
return false;
});
$(document).on('pf:menuShowMapInfo', function(e){
$(document).on('pf:menuShowMapInfo', function(e, data){
// show map information dialog
$.fn.showMapInfoDialog();
$.fn.showMapInfoDialog(data);
return false;
});

View File

@@ -35,6 +35,7 @@ define([
tableImageSmallCellClass: 'pf-table-image-small-cell', // class for table "small image" cells
tableActionCellClass: 'pf-table-action-cell', // class for table "action" cells
tableCounterCellClass: 'pf-table-counter-cell', // class for table "counter" cells
tableActionCellIconClass: 'pf-table-action-icon-cell', // class for table "action" icon (icon is part of cell content)
loadingOptions: { // config for loading overlay
icon: {
@@ -625,8 +626,16 @@ define([
// table init complete
userTable.on( 'init.dt', function () {
usersElement.hideLoadingAnimation();
// init table tooltips
let tooltipElements = usersElement.find('[data-toggle="tooltip"]');
tooltipElements.tooltip();
});
let getIconForInformationWindow = () => {
return '<i class="fa fa-fw fa-id-card ' + config.tableActionCellIconClass + '" title="open ingame" data-toggle="tooltip"></i>';
};
// users table ================================================================================================
// prepare users data for dataTables
let currentMapUserData = Util.getCurrentMapUserData( mapData.config.id );
@@ -681,7 +690,9 @@ define([
searchable: true,
data: 'log.ship',
render: {
_: 'typeName',
_: function(data, type, row){
return data.typeName + '&nbsp;<i class="fa fa-fw fa-question-circle pf-help" title="' + data.name + '" data-toggle="tooltip"></i>';
},
sort: 'typeName'
}
},{
@@ -702,7 +713,24 @@ define([
title: 'pilot',
orderable: true,
searchable: true,
data: 'name'
className: [config.tableActionCellClass].join(' '),
data: 'name',
render: {
_: function(data, type, row, meta){
let value = data;
if(type === 'display'){
value += '&nbsp;' + getIconForInformationWindow();
}
return value;
}
},
createdCell: function(cell, cellData, rowData, rowIndex, colIndex){
// open character information window (ingame)
$(cell).on('click', { tableApi: this.DataTable() }, function(e) {
let rowData = e.data.tableApi.row(this).data();
Util.openIngameWindow(rowData.id);
});
}
},{
targets: 4,
title: '',
@@ -721,9 +749,23 @@ define([
title: 'corporation',
orderable: true,
searchable: true,
className: [config.tableActionCellClass].join(' '),
data: 'corporation',
render: {
_: 'name'
_: function (data, type, row, meta) {
let value = data.name;
if(type === 'display'){
value += '&nbsp;' + getIconForInformationWindow();
}
return value;
}
},
createdCell: function(cell, cellData, rowData, rowIndex, colIndex){
// open character information window (ingame)
$(cell).on('click', { tableApi: this.DataTable() }, function(e) {
let cellData = e.data.tableApi.cell(this).data();
Util.openIngameWindow(cellData.id);
});
}
},{
targets: 6,
@@ -753,9 +795,9 @@ define([
/**
* shows the map information modal dialog
* @param options
*/
$.fn.showMapInfoDialog = function(){
$.fn.showMapInfoDialog = function(options){
let activeMap = Util.getMapModule().getActiveMap();
let mapData = activeMap.getMapDataFromClient({forceData: true});
@@ -770,7 +812,11 @@ define([
mapInfoId: config.mapInfoId,
mapInfoSystemsId: config.mapInfoSystemsId,
mapInfoConnectionsId: config.mapInfoConnectionsId,
mapInfoUsersId: config.mapInfoUsersId
mapInfoUsersId: config.mapInfoUsersId,
// default open tab ----------
openTabInformation: options.tab === 'information',
openTabActivity: options.tab === 'activity'
};
let content = Mustache.render(template, data);

View File

@@ -1899,6 +1899,34 @@ define([
return $('.' + config.dialogClass).filter(':visible');
};
/**
* send Ajax request that remote opens an ingame Window
* @param targetId
*/
let openIngameWindow = (targetId) => {
targetId = parseInt(targetId);
if(targetId > 0){
$.ajax({
type: 'POST',
url: Init.path.openIngameWindow,
data: {
targetId: targetId
},
dataType: 'json'
}).done(function(data){
if(data.error.length > 0){
showNotify({title: 'Open window in client', text: 'Remote window open failed', type: 'error'});
}else{
showNotify({title: 'Open window in client', text: 'Check your EVE client', type: 'success'});
}
}).fail(function( jqXHR, status, error) {
let reason = status + ' ' + error;
showNotify({title: jqXHR.status + ': openWindow', text: reason, type: 'error'});
});
}
};
/**
* formats a price string into an ISK Price
* @param price
@@ -2066,6 +2094,7 @@ define([
setDestination: setDestination,
convertDateToString: convertDateToString,
getOpenDialogs: getOpenDialogs,
openIngameWindow: openIngameWindow,
formatPrice: formatPrice,
getLocalStorage: getLocalStorage,
getDocumentPath: getDocumentPath,

File diff suppressed because one or more lines are too long

View File

@@ -16,6 +16,7 @@ define(['jquery'], function($) {
logIn: 'api/user/logIn', // ajax URL - login
logout: 'api/user/logout', // ajax URL - logout
deleteLog: 'api/user/deleteLog', // ajax URL - delete character log
openIngameWindow: 'api/user/openIngameWindow', // ajax URL - open inGame Window
saveUserConfig: 'api/user/saveAccount', // ajax URL - saves/update user account
deleteAccount: 'api/user/deleteAccount', // ajax URL - delete Account data
// access API

View File

@@ -2719,7 +2719,7 @@ define([
break;
case 'info':
// open map info dialog
$(document).triggerMenuEvent('ShowMapInfo');
$(document).triggerMenuEvent('ShowMapInfo', {tab: 'information'});
break;
}
@@ -3015,7 +3015,7 @@ define([
// set current location data for header update
headerUpdateData.currentSystemId = $(system).data('id');
//headerUpdateData.currentSystemName = currentCharacterLog.system.name;
headerUpdateData.currentSystemName = currentCharacterLog.system.name;
}
system.updateSystemUserData(map, tempUserData, currentUserIsHere);

View File

@@ -323,7 +323,7 @@ define([
class: 'fa fa-street-view fa-fw'
})
).on('click', function(){
$(document).triggerMenuEvent('ShowMapInfo');
$(document).triggerMenuEvent('ShowMapInfo', {tab: 'information'});
})
).append(
getMenuHeadline('Settings')
@@ -496,7 +496,7 @@ define([
// active pilots
$('.' + config.headActiveUserClass).find('a').on('click', function(){
$(document).triggerMenuEvent('ShowMapInfo');
$(document).triggerMenuEvent('ShowMapInfo', {tab: 'activity'});
});
// current location
@@ -661,9 +661,9 @@ define([
return false;
});
$(document).on('pf:menuShowMapInfo', function(e){
$(document).on('pf:menuShowMapInfo', function(e, data){
// show map information dialog
$.fn.showMapInfoDialog();
$.fn.showMapInfoDialog(data);
return false;
});

View File

@@ -35,6 +35,7 @@ define([
tableImageSmallCellClass: 'pf-table-image-small-cell', // class for table "small image" cells
tableActionCellClass: 'pf-table-action-cell', // class for table "action" cells
tableCounterCellClass: 'pf-table-counter-cell', // class for table "counter" cells
tableActionCellIconClass: 'pf-table-action-icon-cell', // class for table "action" icon (icon is part of cell content)
loadingOptions: { // config for loading overlay
icon: {
@@ -625,8 +626,16 @@ define([
// table init complete
userTable.on( 'init.dt', function () {
usersElement.hideLoadingAnimation();
// init table tooltips
let tooltipElements = usersElement.find('[data-toggle="tooltip"]');
tooltipElements.tooltip();
});
let getIconForInformationWindow = () => {
return '<i class="fa fa-fw fa-id-card ' + config.tableActionCellIconClass + '" title="open ingame" data-toggle="tooltip"></i>';
};
// users table ================================================================================================
// prepare users data for dataTables
let currentMapUserData = Util.getCurrentMapUserData( mapData.config.id );
@@ -681,7 +690,9 @@ define([
searchable: true,
data: 'log.ship',
render: {
_: 'typeName',
_: function(data, type, row){
return data.typeName + '&nbsp;<i class="fa fa-fw fa-question-circle pf-help" title="' + data.name + '" data-toggle="tooltip"></i>';
},
sort: 'typeName'
}
},{
@@ -702,7 +713,24 @@ define([
title: 'pilot',
orderable: true,
searchable: true,
data: 'name'
className: [config.tableActionCellClass].join(' '),
data: 'name',
render: {
_: function(data, type, row, meta){
let value = data;
if(type === 'display'){
value += '&nbsp;' + getIconForInformationWindow();
}
return value;
}
},
createdCell: function(cell, cellData, rowData, rowIndex, colIndex){
// open character information window (ingame)
$(cell).on('click', { tableApi: this.DataTable() }, function(e) {
let rowData = e.data.tableApi.row(this).data();
Util.openIngameWindow(rowData.id);
});
}
},{
targets: 4,
title: '',
@@ -721,9 +749,23 @@ define([
title: 'corporation',
orderable: true,
searchable: true,
className: [config.tableActionCellClass].join(' '),
data: 'corporation',
render: {
_: 'name'
_: function (data, type, row, meta) {
let value = data.name;
if(type === 'display'){
value += '&nbsp;' + getIconForInformationWindow();
}
return value;
}
},
createdCell: function(cell, cellData, rowData, rowIndex, colIndex){
// open character information window (ingame)
$(cell).on('click', { tableApi: this.DataTable() }, function(e) {
let cellData = e.data.tableApi.cell(this).data();
Util.openIngameWindow(cellData.id);
});
}
},{
targets: 6,
@@ -753,9 +795,9 @@ define([
/**
* shows the map information modal dialog
* @param options
*/
$.fn.showMapInfoDialog = function(){
$.fn.showMapInfoDialog = function(options){
let activeMap = Util.getMapModule().getActiveMap();
let mapData = activeMap.getMapDataFromClient({forceData: true});
@@ -770,7 +812,11 @@ define([
mapInfoId: config.mapInfoId,
mapInfoSystemsId: config.mapInfoSystemsId,
mapInfoConnectionsId: config.mapInfoConnectionsId,
mapInfoUsersId: config.mapInfoUsersId
mapInfoUsersId: config.mapInfoUsersId,
// default open tab ----------
openTabInformation: options.tab === 'information',
openTabActivity: options.tab === 'activity'
};
let content = Mustache.render(template, data);

View File

@@ -1899,6 +1899,34 @@ define([
return $('.' + config.dialogClass).filter(':visible');
};
/**
* send Ajax request that remote opens an ingame Window
* @param targetId
*/
let openIngameWindow = (targetId) => {
targetId = parseInt(targetId);
if(targetId > 0){
$.ajax({
type: 'POST',
url: Init.path.openIngameWindow,
data: {
targetId: targetId
},
dataType: 'json'
}).done(function(data){
if(data.error.length > 0){
showNotify({title: 'Open window in client', text: 'Remote window open failed', type: 'error'});
}else{
showNotify({title: 'Open window in client', text: 'Check your EVE client', type: 'success'});
}
}).fail(function( jqXHR, status, error) {
let reason = status + ' ' + error;
showNotify({title: jqXHR.status + ': openWindow', text: reason, type: 'error'});
});
}
};
/**
* formats a price string into an ISK Price
* @param price
@@ -2066,6 +2094,7 @@ define([
setDestination: setDestination,
convertDateToString: convertDateToString,
getOpenDialogs: getOpenDialogs,
openIngameWindow: openIngameWindow,
formatPrice: formatPrice,
getLocalStorage: getLocalStorage,
getDocumentPath: getDocumentPath,

View File

@@ -1,12 +1,12 @@
<nav class="navbar navbar-default" role="navigation">
<div class="navbar-header pull-left">
<ul class="nav navbar-nav {{dialogNavigationClass}}" role="tablist">
<li class="active">
<li class="{{#openTabInformation}}active{{/openTabInformation}}">
<a role="tab" data-toggle="tab" data-name="infoSummary" href="#{{dialogSummaryContainerId}}">
<i class="fa fa-street-view fa-fw"></i>&nbsp;Information
</a>
</li>
<li class="">
<li class="{{#openTabActivity}}active{{/openTabActivity}}">
<a role="tab" data-toggle="tab" data-name="infoUsers" href="#{{dialogUsersContainerId}}">
<i class="fa fa-fighter-jet fa-fw"></i>&nbsp;Activity
</a>
@@ -28,7 +28,7 @@
<div class="tab-content">
{{! "summary" tab ------------------------------------------------------ }}
<div role="tabpanel" class="tab-pane fade in active" id="{{dialogSummaryContainerId}}">
<div role="tabpanel" class="tab-pane fade {{#openTabInformation}}in active{{/openTabInformation}}" id="{{dialogSummaryContainerId}}">
<div class="alert alert-info fade in hidden-md hidden-lg">
<button type="button" class="close" data-dismiss="alert" aria-label="Close"><i class="fa fa-close"></i></button>
<span class="txt-color txt-color-information">Info</span>
@@ -52,7 +52,7 @@
</div>
{{! "users" tab -------------------------------------------------------- }}
<div role="tabpanel" class="tab-pane fade" id="{{dialogUsersContainerId}}">
<div role="tabpanel" class="tab-pane fade {{#openTabActivity}}in active{{/openTabActivity}}" id="{{dialogUsersContainerId}}">
<h4><i class="fa fa-male fa-lg fa-fw"></i> Active pilots</h4>
<div id="{{mapInfoUsersId}}" class="pf-dynamic-area">

View File

@@ -4,8 +4,14 @@
{{#serviceStatus}}
<li><i class="fa-li fa fa-server " aria-hidden="true"></i><span class="txt-color {{ style }}">{{ eve }}</span></li>
{{/serviceStatus}}
{{#userCounts}}
<li><i class="fa-li fa fa-users" aria-hidden="true"></i>{{ userCounts }}</li>
{{/userCounts}}
{{#playerCount}}
<li><i class="fa-li fa fa-users" aria-hidden="true"></i>{{ playerCount }}</li>
{{/playerCount}}
{{#startTime}}
<li><i class="fa-li fa fa-clock-o" aria-hidden="true"></i>up {{ startTime }}</li>
{{/startTime}}
{{#serverVersion}}
<li><i class="fa-li fa fa-certificate" aria-hidden="true"></i>v. {{ serverVersion }}</li>
{{/serverVersion}}
</ul>
</div>

View File

@@ -987,6 +987,27 @@
</div>
</div>
</div>
<div class="col-xs-12 col-md-6 pf-landing-pricing-panel">
{* Cookie *}
<div class="panel panel-default pricing-big">
<div class="panel-heading text-left">
<h3 class="panel-title">Cookies
<i class="fa fa-fw fa-question-circle pf-help-light" title="Force all users to re-login through SSO. "></i>
</h3>
</div>
<div class="panel-body no-padding">
<div class="btn-group btn-group-justified">
<span class="btn btn-default disabled btn-fake">Invalidate all Cookie data</span>
<a href="?invalidateCookies=1#pf-setup-administration" class="btn btn-warning" role="button">
<i class="fa fa-fw fa-times"></i> Clear authentication data
</a>
</div>
</div>
</div>
</div>
</div>
</div>
</section>

View File

@@ -552,7 +552,7 @@ $modal-header-border-color: #e5e5e5;
$modal-footer-border-color: $modal-header-border-color;
$modal-lg: 1100px;
$modal-md: 600px;
$modal-md: 700px;
$modal-sm: 300px;

View File

@@ -216,6 +216,19 @@ select:active, select:hover {
td{
&.pf-table-action-cell{
cursor: pointer;
// icon within <td> cell content that should be highlighted on hover
> .pf-table-action-icon-cell{
@extend .txt-color;
@extend .txt-color-gray;
@include transition( color 0.08s ease-out );
}
&:hover{
> .pf-table-action-icon-cell{
@extend .txt-color-orange;
}
}
}
&.pf-table-image-cell{