- Added docking information for "Structures" ( e.g. Citadels)

- Added system environment var check to /setup page
- Added new ESI scope "read_structures.v1"
This commit is contained in:
Mark Friedrich
2017-10-31 14:40:02 +01:00
parent a8edf39697
commit 444d54d642
18 changed files with 450 additions and 80 deletions

View File

@@ -34,7 +34,7 @@ CCP_SSO_SECRET_KEY =
; CCP ESI API
CCP_ESI_URL = https://esi.tech.ccp.is
CCP_ESI_DATASOURCE = singularity
CCP_ESI_SCOPES = esi-location.read_online.v1,esi-location.read_location.v1,esi-location.read_ship_type.v1,esi-ui.write_waypoint.v1,esi-ui.open_window.v1
CCP_ESI_SCOPES = esi-location.read_online.v1,esi-location.read_location.v1,esi-location.read_ship_type.v1,esi-ui.write_waypoint.v1,esi-ui.open_window.v1,esi-universe.read_structures.v1
CCP_ESI_SCOPES_ADMIN = esi-corporations.read_corporation_membership.v1
; SMTP settings (optional)
@@ -80,7 +80,7 @@ CCP_SSO_SECRET_KEY =
; CCP ESI API
CCP_ESI_URL = https://esi.tech.ccp.is
CCP_ESI_DATASOURCE = tranquility
CCP_ESI_SCOPES = esi-location.read_online.v1,esi-location.read_location.v1,esi-location.read_ship_type.v1,esi-ui.write_waypoint.v1,esi-ui.open_window.v1
CCP_ESI_SCOPES = esi-location.read_online.v1,esi-location.read_location.v1,esi-location.read_ship_type.v1,esi-ui.write_waypoint.v1,esi-ui.open_window.v1,esi-universe.read_structures.v1
CCP_ESI_SCOPES_ADMIN = esi-corporations.read_corporation_membership.v1
; SMTP settings (optional)

View File

@@ -600,6 +600,7 @@ class Controller {
* @return bool
*/
public function showError(\Base $f3){
if(!headers_sent()){
// collect error info -------------------------------------------------------------------------------------
$error = $this->getErrorObject(

View File

@@ -100,6 +100,7 @@ class Setup extends Controller {
'info' => [],
'models' => [
'Model\Universe\TypeModel',
'Model\Universe\StructureModel',
//'Model\Universe\RegionModel',
//'Model\Universe\ConstellationModel'
],
@@ -240,6 +241,9 @@ class Setup extends Controller {
// set php config check information
$f3->set('checkPHPConfig', $this->checkPHPConfig($f3));
// set system config check information
$f3->set('checkSystemConfig', $this->checkSystemConfig($f3));
// set map default config
$f3->set('mapsDefaultConfig', $this->getMapsDefaultConfig($f3));
@@ -623,6 +627,13 @@ class Setup extends Controller {
*/
protected function checkPHPConfig(\Base $f3): array {
$phpConfig = [
'exec' => [
'label' => 'exec()',
'required' => $f3->get('REQUIREMENTS.PHP.EXEC'),
'version' => function_exists('exec'),
'check' => function_exists('exec') == $f3->get('REQUIREMENTS.PHP.EXEC'),
'tooltip' => 'exec() funktion. Check "disable_functions" in php.ini'
],
'maxInputVars' => [
'label' => 'max_input_vars',
'required' => $f3->get('REQUIREMENTS.PHP.MAX_INPUT_VARS'),
@@ -670,6 +681,81 @@ class Setup extends Controller {
return $phpConfig;
}
/**
* check system environment vars
* -> mostly relevant for development/build/deployment
* @param \Base $f3
* @return array
*/
protected function checkSystemConfig(\Base $f3): array {
$systemConf = [];
if(function_exists('exec')){
$gitOut = $composerOut = $rubyOut = $rubyGemsOut = $compassOut = $nodeOut = $npmOut = [];
$gitStatus = $composerStatus = $rubyStatus = $rubyGemsStatus = $compassStatus = $nodeStatus = $npmStatus = 1;
exec('git --version', $gitOut, $gitStatus);
exec('composer -V', $composerOut, $composerStatus);
exec('ruby -v', $rubyOut, $rubyStatus);
exec('gem -v', $rubyGemsOut, $rubyGemsStatus);
exec('compass -v', $compassOut, $compassStatus);
exec('node -v', $nodeOut, $nodeStatus);
exec('npm -v', $npmOut, $npmStatus);
$normalizeVersion = function($version): string {
return preg_replace("/[^0-9\.\s]/", '', (string)$version);
};
$systemConf = [
'git' => [
'label' => 'Git',
'version' => $gitOut[0] ? 'installed' : 'missing',
'check' => $gitStatus == 0,
'tooltip' => 'Git # git --version : ' . $gitOut[0]
],
'composer' => [
'label' => 'Composer',
'version' => $composerOut[0] ? 'installed' : 'missing',
'check' => $composerStatus == 0,
'tooltip' => 'Composer # composer -V : ' . $composerOut[0]
],
'Ruby' => [
'label' => 'Ruby',
'version' => $rubyOut[0] ? 'installed' : 'missing',
'check' => $rubyStatus == 0,
'tooltip' => 'Ruby # ruby -v : ' . $rubyOut[0]
],
'rubyGems' => [
'label' => 'Ruby gem',
'version' => $normalizeVersion($rubyGemsOut[0]) ?: 'missing',
'check' => $rubyGemsStatus == 0,
'tooltip' => 'gem # gem -v'
],
'compass' => [
'label' => 'Compass',
'version' => $compassOut[0] ? 'installed' : 'missing',
'check' => $compassStatus == 0,
'tooltip' => 'Compass # compass -v : ' . $compassOut[0]
],
'node' => [
'label' => 'NodeJs',
'required' => number_format((float)$f3->get('REQUIREMENTS.PATH.NODE'), 1, '.', ''),
'version' => $normalizeVersion($nodeOut[0]) ?: 'missing',
'check' => version_compare( $normalizeVersion($nodeOut[0]), number_format((float)$f3->get('REQUIREMENTS.PATH.NODE'), 1, '.', ''), '>='),
'tooltip' => 'NodeJs # node -v'
],
'npm' => [
'label' => 'npm',
'required' => $f3->get('REQUIREMENTS.PATH.NPM'),
'version' => $normalizeVersion($npmOut[0]) ?: 'missing',
'check' => version_compare( $normalizeVersion($npmOut[0]), $f3->get('REQUIREMENTS.PATH.NPM'), '>='),
'tooltip' => 'npm # npm -v'
]
];
}
return $systemConf;
}
/**
* get default map config
* @param \Base $f3
@@ -756,7 +842,7 @@ class Setup extends Controller {
$dbCreate = false;
// enable database ::setup() function on UI
$dbSetupEnable = false;
// check of everything is OK (connection, tables, columns, indexes,..)
// check if everything is OK (connection, tables, columns, indexes,..)
$dbStatusCheckCount = 0;
// db queries for column fixes (types, indexes, unique)
$dbColumnQueries = [];

View File

@@ -28,6 +28,18 @@ class Util {
return $arr;
}
/**
* flatten multidimensional array
* -> overwrites duplicate keys!
* @param array $array
* @return array
*/
static function arrayFlatten(array $array) : array {
$return = [];
array_walk_recursive($array, function($value, $key) use (&$return) { $return[$key] = $value; });
return $return;
}
/**
* checks whether an array is associative or not (sequential)
* @param mixed $array

View File

@@ -75,6 +75,15 @@ class CharacterLogModel extends BasicModel {
'type' => Schema::DT_VARCHAR128,
'nullable' => false,
'default' => ''
],
'structureId' => [
'type' => Schema::DT_BIGINT,
'index' => true
],
'structureName' => [
'type' => Schema::DT_VARCHAR128,
'nullable' => false,
'default' => ''
]
];
@@ -114,6 +123,14 @@ class CharacterLogModel extends BasicModel {
$this->stationName = '';
}
if( isset($logData['structure']) ){
$this->structureId = (int)$logData['structure']['id'];
$this->structureName = $logData['structure']['name'];
}else{
$this->structureId = null;
$this->structureName = '';
}
}
/**
@@ -138,6 +155,10 @@ class CharacterLogModel extends BasicModel {
$logData->station->id = (int)$this->stationId;
$logData->station->name = $this->stationName;
$logData->structure = (object) [];
$logData->structure->id = (int)$this->structureId;
$logData->structure->name = $this->structureName;
return $logData;
}

View File

@@ -726,14 +726,45 @@ class CharacterModel extends BasicModel {
}
}
// check structure data for changes -------------------------------------------------------
if(!$deleteLog){
// IDs for "structureId" that require more data
$lookupStructureId = 0;
if( !empty($locationData['structure']['id']) ){
if(
empty($logData['structure']['name']) ||
$logData['structure']['id'] !== $locationData['structure']['id']
){
// structure changed -> request "structure name" for current station
$lookupStructureId = $locationData['structure']['id'];
}
}else{
unset($logData['structure']);
}
// get "more" data for structureId ---------------------------------------------------
if($lookupStructureId > 0){
/**
* @var $structureModel Universe\StructureModel
*/
$structureModel = Universe\BasicUniverseModel::getNew('StructureModel');
$structureModel->loadById($lookupStructureId, $accessToken, $additionalOptions);
if(!$structureModel->dry()){
$structureData['structure'] = (array)$structureModel->getData();
$logData = array_replace_recursive($logData, $structureData);
}else{
unset($logData['structure']);
}
}
}
// check ship data for changes ------------------------------------------------------------
if( !$deleteLog ){
$shipData = self::getF3()->ccpClient->getCharacterShipData($this->_id, $accessToken, $additionalOptions);
// IDs for "systemId", "stationId" that require more data
// IDs for "shipTypeId" that require more data
$lookupShipTypeId = 0;
if( !empty($shipData['ship']['typeId']) ){
if(
empty($logData['ship']['typeName']) ||
@@ -757,7 +788,7 @@ class CharacterModel extends BasicModel {
* @var $typeModel Universe\TypeModel
*/
$typeModel = Universe\BasicUniverseModel::getNew('TypeModel');
$typeModel->loadById($lookupShipTypeId, $additionalOptions);
$typeModel->loadById($lookupShipTypeId, '', $additionalOptions);
if(!$typeModel->dry()){
$shipData['ship'] = (array)$typeModel->getShipData();
$logData = array_replace_recursive($logData, $shipData);

View File

@@ -35,4 +35,51 @@ abstract class BasicUniverseModel extends BasicModel {
return $class;
}
/**
* load data from API into $this and save $this
* @param int $id
* @param string $accessToken
* @param array $additionalOptions
*/
abstract protected function loadData(int $id, string $accessToken = '', array $additionalOptions = []);
/**
* load object by $id
* -> if $id not exists in DB -> query API
* @param int $id
* @param string $accessToken
* @param array $additionalOptions
*/
public function loadById(int $id, string $accessToken = '', array $additionalOptions = []){
/**
* @var $model self
*/
$model = $this->getById($id);
if($model->isOutdated()){
$model->loadData($id, $accessToken, $additionalOptions);
}
}
/**
* checks whether data is outdated and should be refreshed
* @return bool
*/
protected function isOutdated(): bool {
$outdated = true;
if(!$this->dry()){
$timezone = $this->getF3()->get('getTimeZone')();
$currentTime = new \DateTime('now', $timezone);
$updateTime = \DateTime::createFromFormat(
'Y-m-d H:i:s',
$this->updated,
$timezone
);
$interval = $updateTime->diff($currentTime);
if($interval->days < self::CACHE_MAX_DAYS ){
$outdated = false;
}
}
return $outdated;
}
}

View File

@@ -0,0 +1,122 @@
<?php
/**
* Created by PhpStorm.
* User: exodu
* Date: 14.10.2017
* Time: 15:56
*/
namespace Model\Universe;
use DB\SQL;
use DB\SQL\Schema;
use Lib\Util;
class StructureModel extends BasicUniverseModel {
protected $table = 'structure';
protected $fieldConf = [
'name' => [
'type' => Schema::DT_VARCHAR128,
'nullable' => false,
'default' => ''
],
'systemId' => [
'type' => Schema::DT_INT,
'nullable' => false,
'default' => 0,
'index' => true
],
'typeId' => [
'type' => Schema::DT_INT,
'index' => true,
'belongs-to-one' => 'Model\Universe\TypeModel',
'constraint' => [
[
'table' => 'type',
'on-delete' => 'CASCADE'
]
]
],
'x' => [
'type' => Schema::DT_FLOAT,
'nullable' => false,
'default' => 0
],
'y' => [
'type' => Schema::DT_FLOAT,
'nullable' => false,
'default' => 0
],
'z' => [
'type' => Schema::DT_FLOAT,
'nullable' => false,
'default' => 0
]
];
/**
* get data from object
* -> more fields can be added in here if needed
* @return \stdClass
*/
public function getData(): \stdClass {
$data = (object) [];
if(!$this->dry()){
$data->id = $this->_id;
$data->name = $this->name;
}
return $data;
}
/**
* load data from API into $this and save $this
* @param int $id
* @param string $accessToken
* @param array $additionalOptions
*/
protected function loadData(int $id, string $accessToken = '', array $additionalOptions = []){
$data = self::getF3()->ccpClient->getUniverseStructureData($id, $accessToken, $additionalOptions);
if(!empty($data)){
$type = $this->rel('typeId');
$type->loadById($data['typeId'], $accessToken, $additionalOptions);
$data['typeId'] = $type;
$this->copyfrom($data);
$this->save();
}
}
/**
* @param array|string $key
* @param null $fields
* @return NULL
*/
public function copyfrom($key, $fields = null){
// flatten array (e.g. "position" key)
$key = Util::arrayFlatten((array)$key);
parent::copyfrom($key, $fields);
}
/**
* overwrites parent
* @param null|SQL $db
* @param null $table
* @param null $fields
* @return bool
*/
public static function setup($db=null, $table=null, $fields=null){
if($status = parent::setup($db,$table,$fields)){
//change `id` column to BigInt
$schema = new Schema($db);
$typeQuery = $schema->findQuery($schema->dataTypes[Schema::DT_BIGINT]);
$db->exec("ALTER TABLE " . $db->quotekey('structure') .
" MODIFY COLUMN " . $db->quotekey('id') . " " . $typeQuery . " NOT NULL");
}
return $status;
}
}

View File

@@ -53,12 +53,14 @@ class TypeModel extends BasicUniverseModel {
'groupId' => [
'type' => Schema::DT_INT,
'nullable' => false,
'default' => 0
'default' => 0,
'index' => true
],
'marketGroupId' => [
'type' => Schema::DT_INT,
'nullable' => false,
'default' => 0
'default' => 0,
'index' => true
],
'packagedVolume' => [
'type' => Schema::DT_FLOAT,
@@ -73,7 +75,11 @@ class TypeModel extends BasicUniverseModel {
'graphicId' => [
'type' => Schema::DT_INT,
'nullable' => false,
'default' => 0
'default' => 0,
'index' => true
],
'structures' => [
'has-many' => ['Model\Universe\StructureModel', 'typeId']
]
];
@@ -95,51 +101,14 @@ class TypeModel extends BasicUniverseModel {
/**
* load data from API into $this and save $this
* @param int $id
* @param string $accessToken
* @param array $additionalOptions
*/
protected function loadData(int $id, array $additionalOptions = []){
protected function loadData(int $id, string $accessToken = '', array $additionalOptions = []){
$data = self::getF3()->ccpClient->getUniverseTypesData($id, $additionalOptions);
if(!empty($data)){
$this->copyfrom($data);
$this->save();
}
}
/**
* load object by $id
* -> if $id not exists in DB -> query API
* @param int $id
* @param array $additionalOptions
*/
public function loadById(int $id, array $additionalOptions = []){
/**
* @var $model self
*/
$model = parent::getById($id);
if($model->isOutdated()){
$model->loadData($id, $additionalOptions);
}
}
/**
* checks whether data is outdated and should be refreshed
* @return bool
*/
protected function isOutdated(): bool {
$outdated = true;
if(!$this->dry()){
$timezone = $this->getF3()->get('getTimeZone')();
$currentTime = new \DateTime('now', $timezone);
$updateTime = \DateTime::createFromFormat(
'Y-m-d H:i:s',
$this->updated,
$timezone
);
$interval = $updateTime->diff($currentTime);
if($interval->days < self::CACHE_MAX_DAYS ){
$outdated = false;
}
}
return $outdated;
}
}

View File

@@ -30,6 +30,9 @@ ZMQ = 1.1.3
; https://pecl.php.net/package/event
EVENT = 2.3.0
; exec() function required for run Shell scripts from PHP
EXEC = 1
; max execution time for requests (seconds)
MAX_EXECUTION_TIME = 10
@@ -65,4 +68,7 @@ COLLATION_DATABASE = utf8_general_ci
COLLATION_CONNECTION = utf8_general_ci
FOREIGN_KEY_CHECKS = ON
[REQUIREMENTS.PATH]
NODE = 6.0
NPM = 3.10.0

View File

@@ -476,25 +476,32 @@ define([
},{
targets: 4,
orderable: false,
title: '<i title="docked station" data-toggle="tooltip" class="fa fa-home text-right"></i>',
title: '',
width: '10px',
className: ['pf-help-default'].join(' '),
data: 'log.station',
data: 'log',
render: {
_: function(data, type, row, meta){
let value = '';
if(
type === 'display' &&
data.id
){
value = '<i class="fa fa-home"></i>';
if(type === 'display'){
if(data.station && data.station.id > 0){
value = '<i class="fa fa-home"></i>';
}else if(data.structure && data.structure.id > 0){
value = '<i class="fa fa-industry"></i>';
}
}
return value;
}
},
createdCell: function(cell, cellData, rowData, rowIndex, colIndex){
let selector = '';
if(cellData.station && cellData.station.id > 0){
selector = 'log.station.name';
}else if(cellData.structure && cellData.structure.id > 0){
selector = 'log.structure.name';
}
let api = this.DataTable();
initCellTooltip(api, cell, 'log.station.name');
initCellTooltip(api, cell, selector);
}
},{
targets: 5,

View File

@@ -62,13 +62,23 @@ define([
};
/**
* get image that marks a table cell as clickable
* get icon that marks a table cell as clickable
* @returns {string}
*/
let getIconForInformationWindow = () => {
return '<i class="fa fa-fw fa-id-card ' + config.tableCellActionIconClass + '" title="open ingame" data-toggle="tooltip"></i>';
};
/**
* get icon for socked status
* @param type
* @returns {string}
*/
let getIconForDockedStatus = (type) => {
let icon = type === 'station' ? 'fa-home' : type === 'structure' ? 'fa-industry' : '';
return icon.length ? '<i class="fa fa-fw ' + icon + ' ' + config.tableCellActionIconClass + '" title="' + type + '" data-toggle="tooltip"></i>' : '';
};
/**
* loads the map info data into an element
* @param mapData
@@ -840,13 +850,21 @@ define([
}
},{
targets: 7,
title: 'station',
title: 'docked',
orderable: true,
searchable: true,
data: 'log.station',
className: [config.tableCellActionClass].join(' '),
data: 'log',
render: {
_: 'name',
sort: 'name'
_: function (data, type, row, meta) {
let value = '';
if(data.station && data.station.id > 0){
value = data.station.name + '&nbsp;' + getIconForDockedStatus('station');
}else if(data.structure && data.structure.id > 0){
value = data.structure.name + '&nbsp;' + getIconForDockedStatus('structure');
}
return value;
}
}
}
]

File diff suppressed because one or more lines are too long

View File

@@ -476,25 +476,32 @@ define([
},{
targets: 4,
orderable: false,
title: '<i title="docked station" data-toggle="tooltip" class="fa fa-home text-right"></i>',
title: '',
width: '10px',
className: ['pf-help-default'].join(' '),
data: 'log.station',
data: 'log',
render: {
_: function(data, type, row, meta){
let value = '';
if(
type === 'display' &&
data.id
){
value = '<i class="fa fa-home"></i>';
if(type === 'display'){
if(data.station && data.station.id > 0){
value = '<i class="fa fa-home"></i>';
}else if(data.structure && data.structure.id > 0){
value = '<i class="fa fa-industry"></i>';
}
}
return value;
}
},
createdCell: function(cell, cellData, rowData, rowIndex, colIndex){
let selector = '';
if(cellData.station && cellData.station.id > 0){
selector = 'log.station.name';
}else if(cellData.structure && cellData.structure.id > 0){
selector = 'log.structure.name';
}
let api = this.DataTable();
initCellTooltip(api, cell, 'log.station.name');
initCellTooltip(api, cell, selector);
}
},{
targets: 5,

View File

@@ -62,13 +62,23 @@ define([
};
/**
* get image that marks a table cell as clickable
* get icon that marks a table cell as clickable
* @returns {string}
*/
let getIconForInformationWindow = () => {
return '<i class="fa fa-fw fa-id-card ' + config.tableCellActionIconClass + '" title="open ingame" data-toggle="tooltip"></i>';
};
/**
* get icon for socked status
* @param type
* @returns {string}
*/
let getIconForDockedStatus = (type) => {
let icon = type === 'station' ? 'fa-home' : type === 'structure' ? 'fa-industry' : '';
return icon.length ? '<i class="fa fa-fw ' + icon + ' ' + config.tableCellActionIconClass + '" title="' + type + '" data-toggle="tooltip"></i>' : '';
};
/**
* loads the map info data into an element
* @param mapData
@@ -840,13 +850,21 @@ define([
}
},{
targets: 7,
title: 'station',
title: 'docked',
orderable: true,
searchable: true,
data: 'log.station',
className: [config.tableCellActionClass].join(' '),
data: 'log',
render: {
_: 'name',
sort: 'name'
_: function (data, type, row, meta) {
let value = '';
if(data.station && data.station.id > 0){
value = data.station.name + '&nbsp;' + getIconForDockedStatus('station');
}else if(data.structure && data.structure.id > 0){
value = data.structure.name + '&nbsp;' + getIconForDockedStatus('structure');
}
return value;
}
}
}
]

View File

@@ -1,7 +1,7 @@
<table class="table">
<thead>
<tr>
<td>Feature</td>
<td></td>
<td class="text-right col-md-3">required</td>
<td class="text-right col-md-3">found</td>
<td class="text-right col-md-1"></td>

View File

@@ -190,7 +190,7 @@
<h3 class="panel-title txt-color txt-color-success">OK</h3>
</true>
<false>
<h3 class="panel-title txt-color txt-color-warning">Warnings</h3>
<h3 class="panel-title txt-color txt-color-warning">{{ @tplCounter('get') }} Warnings</h3>
</false>
</check>
</div>
@@ -199,7 +199,7 @@
</div>
<div class="col-xs-12 col-md-6 pf-landing-pricing-panel">
{* PHP config php.ini requirements*}
{* PHP config php.ini requirements *}
<set requirementsData="{{ @checkPHPConfig }}" />
<div class="panel panel-default pricing-big">
@@ -215,12 +215,37 @@
<h3 class="panel-title txt-color txt-color-success">OK</h3>
</true>
<false>
<h3 class="panel-title txt-color txt-color-warning">Warnings</h3>
<h3 class="panel-title txt-color txt-color-warning">{{ @tplCounter('get') }} Warnings</h3>
</false>
</check>
</div>
</div>
{{ @tplCounter('reset') }}
{* System Env requirements *}
<check if="{{ @checkSystemConfig }}">
<set requirementsData="{{ @checkSystemConfig }}" />
<div class="panel panel-default pricing-big">
<div class="panel-heading text-left">
<h3 class="panel-title">Environment variables [optional]</h3>
</div>
<div class="panel-body no-padding text-align-center">
<include href="templates/modules/requirements_table.html"/>
</div>
<div class="panel-footer text-align-center">
<check if="{{ !@tplCounter('get') }}">
<true>
<h3 class="panel-title txt-color txt-color-success">OK</h3>
</true>
<false>
<h3 class="panel-title txt-color txt-color-warning">{{ @tplCounter('get') }} Warnings</h3>
</false>
</check>
</div>
</div>
{{ @tplCounter('reset') }}
</check>
</div>
</div>

View File

@@ -51,7 +51,7 @@
top: 13px;
right: 5px;
color: $gray-light;
@extend .pf-animate-rotate.right;
@include rotate( 90deg );
}
}
}