- New "Intel module" for Citadel tracking, closed #246

- added some new tables (for SDE replacement), #628
- new "index build" functions added to `/setup`, #628
- updated "Cortex" PHP ORM lib `1.5.0` -> `1.6.0-dev`
This commit is contained in:
Mark Friedrich
2018-05-31 22:51:06 +02:00
parent 87a0341a41
commit eb52a0754d
33 changed files with 2307 additions and 242 deletions

View File

@@ -16,6 +16,10 @@ HALT = FALSE
; Timezone to use. Sync program with eve server time. (default: UTC)
TZ = UTC
; Cache key prefix. Same for all cache values for this installation
; CLI (cronjob) scripts use it for cache manipulation
SEED = {{ md5(@SERVER.SERVER_NAME) }}
; Cache backend. Can handle Redis, Memcache module, APC, WinCache, XCache and a filesystem-based cache.
; (default: folder=tmp/cache/)
CACHE = folder=tmp/cache/

View File

@@ -18,8 +18,8 @@
* https://github.com/ikkez/F3-Sugar/
*
* @package DB
* @version 1.5.0
* @date 30.06.2017
* @version 1.6.0-dev
* @date 25.04.2018
* @since 24.04.2012
*/
@@ -157,11 +157,19 @@ class Cortex extends Cursor {
$f3->get('CORTEX.standardiseID') : TRUE;
if(!empty($this->fieldConf))
foreach($this->fieldConf as &$conf) {
$conf=static::resolveRelationConf($conf);
$conf=static::resolveRelationConf($conf,$this->primary);
unset($conf);
}
}
/**
* return raw mapper instance
* @return Cursor
*/
public function getMapper() {
return $this->mapper;
}
/**
* get fields or set whitelist / blacklist of fields
* @param array $fields
@@ -512,9 +520,10 @@ class Cortex extends Cursor {
/**
* resolve relation field types
* @param array $field
* @param string $pkey
* @return array
*/
protected static function resolveRelationConf($field) {
protected static function resolveRelationConf($field,$pkey=NULL) {
if (array_key_exists('belongs-to-one', $field)) {
// find primary field definition
if (!is_array($relConf = $field['belongs-to-one']))
@@ -552,7 +561,10 @@ class Cortex extends Cursor {
$field['has-many']['relField'] = $relConf[1];
$field['has-many']['relFieldType'] = isset($rel['fieldConf'][$relConf[1]]['type']) ?
$rel['fieldConf'][$relConf[1]]['type'] : Schema::DT_INT;
$field['has-many']['relPK'] = isset($relConf[3])?$relConf[3]:$rel['primary'];
$field['has-many']['relPK'] = isset($relConf['relPK'])?
$relConf['relPK']:$rel['primary'];
$field['has-many']['localKey'] = isset($relConf['localKey'])?
$relConf['localKey']:($pkey?:'_id');
} else {
// has-many <> belongs-to-one (m:1)
$field['has-many']['hasRel'] = 'belongs-to-one';
@@ -713,7 +725,15 @@ class Cortex extends Cursor {
}
elseif ($result = $this->_hasRefsInMM($key,$has_filter,$has_options,$ttl))
$addToFilter = array($id.' IN ?', $result);
} // *-to-one
}
// *-to-one
elseif ($this->dbsType == 'sql') {
// use sub-query inclusion
$has_filter=$this->mergeFilter([$has_filter,
[$this->rel($key)->getTable().'.'.$fromConf[1].'='.$this->getTable().'.'.$id]]);
$result = $this->_refSubQuery($key,$has_filter,$has_options);
$addToFilter = ['exists('.$result[0].')']+$result[1];
}
elseif ($result = $this->_hasRefsIn($key,$has_filter,$has_options,$ttl))
$addToFilter = array($id.' IN ?', $result);
break;
@@ -754,7 +774,7 @@ class Cortex extends Cursor {
$qtable = $this->db->quotekey($this->table);
if (isset($options['order']) && $this->db->driver() == 'pgsql')
// PostgreSQLism: sort NULL values to the end of a table
$options['order'] = preg_replace('/\h+DESC/i',' DESC NULLS LAST',$options['order']);
$options['order'] = preg_replace('/\h+DESC(?=\s*(?:$|,))/i',' DESC NULLS LAST',$options['order']);
if ($hasJoin) {
// assemble full sql query
$adhoc='';
@@ -838,7 +858,7 @@ class Cortex extends Cursor {
}
}
if ($options) {
$options = $this->queryParser->prepareOptions($options,$this->dbsType);
$options = $this->queryParser->prepareOptions($options,$this->dbsType,$this->db);
if ($count)
unset($options['order']);
}
@@ -912,6 +932,22 @@ class Cortex extends Cursor {
return empty($hasSetByRelId) ? false : $hasSetByRelId;
}
/**
* build sub query on relation
* @param $key
* @param $filter
* @param $options
* @return mixed
*/
protected function _refSubQuery($key, $filter, $options,$fields=null) {
$type = $this->fieldConf[$key]['relType'];
$fieldConf = $this->fieldConf[$key][$type];
$rel = $this->getRelFromConf($fieldConf,$key);
$filter[0]=$this->queryParser->sql_quoteCondition($filter[0],$this->db);
return $rel->mapper->stringify(implode(',',array_map([$this->db,'quotekey'],
$fields?:[$rel->primary])),$filter,$options);
}
/**
* return IDs of own mappers that match the given relation filter on pivot tables
* @param string $key
@@ -1164,30 +1200,69 @@ class Cortex extends Cursor {
foreach($this->saveCsd as $key => $val) {
if($fields[$key]['relType'] == 'has-many') {
$relConf = $fields[$key]['has-many'];
$mmTable = $this->mmTable($relConf,$key);
$rel = $this->getRelInstance(null, array('db'=>$this->db, 'table'=>$mmTable));
$id = $this->get($relConf['relPK'],true);
$filter = [$relConf['relField'].' = ?',$id];
if ($relConf['isSelf']) {
$filter[0].= ' OR '.$relConf['relField'].'_ref = ?';
$filter[] = $id;
if ($relConf['hasRel'] == 'has-many') {
$mmTable = $this->mmTable($relConf,$key);
$mm = $this->getRelInstance(null, array('db'=>$this->db, 'table'=>$mmTable));
$id = $this->get($relConf['localKey'],true);
$filter = [$relConf['relField'].' = ?',$id];
if ($relConf['isSelf']) {
$filter[0].= ' OR '.$relConf['relField'].'_ref = ?';
$filter[] = $id;
}
// delete all refs
if (is_null($val))
$mm->erase($filter);
// update refs
elseif (is_array($val)) {
$mm->erase($filter);
foreach($val as $v) {
if ($relConf['isSelf'] && $v==$id)
continue;
$mm->set($key,$v);
$mm->set($relConf['relField'].($relConf['isSelf']?'_ref':''),$id);
$mm->save();
$mm->reset();
}
}
unset($mm);
}
// delete all refs
if (is_null($val))
$rel->erase($filter);
// update refs
elseif (is_array($val)) {
$rel->erase($filter);
foreach($val as $v) {
if ($relConf['isSelf'] && $v==$id)
continue;
$rel->set($key,$v);
$rel->set($relConf['relField'].($relConf['isSelf']?'_ref':''),$id);
$rel->save();
$rel->reset();
elseif($relConf['hasRel'] == 'belongs-to-one') {
$rel = $this->getRelInstance($relConf[0],$relConf,$key);
// find existing relations
$refs = $rel->find([$relConf[1].' = ?',$this->getRaw($relConf['relField'])]);
if (is_null($val)) {
foreach ($refs?:[] as $model) {
$model->set($relConf[1],NULL);
$model->save();
}
$this->fieldsCache[$key] = NULL;
} else {
if ($refs) {
$ref_ids = $refs->getAll('_id');
// unlink removed relations
$remove_refs = array_diff($ref_ids,$val);
foreach ($refs as $model)
if (in_array($model->getRaw($relConf['relField']),$remove_refs)) {
$model->set($relConf[1],null);
$model->save();
}
// get new relation keys
$val = array_diff($val,$ref_ids);
} else
$refs = new CortexCollection();
if (!empty($val)) {
// find models that need to be linked
$new_refs = $rel->find([$relConf['relField'].' IN ?',$val]);
foreach ($new_refs?:[] as $model) {
// set relation to new models
$model->set($relConf[1],$this->getRaw($relConf['relField']));
$model->save();
$refs->add($model);
}
}
$this->fieldsCache[$key] = $refs;
}
}
unset($rel);
} elseif($fields[$key]['relType'] == 'has-one') {
$val->save();
}
@@ -1372,7 +1447,9 @@ class Cortex extends Cursor {
if (is_null($val))
$val = NULL;
elseif (is_object($val) &&
!($this->dbsType=='mongo' && $val instanceof \MongoId)) {
!($this->dbsType=='mongo' && (
($this->db->legacy() && $val instanceof \MongoId) ||
(!$this->db->legacy() && $val instanceof \MongoDB\BSON\ObjectId)))) {
// fetch fkey from mapper
if (!$val instanceof Cortex || $val->dry())
trigger_error(self::E_INVALID_RELATION_OBJECT,E_USER_ERROR);
@@ -1381,8 +1458,9 @@ class Cortex extends Cursor {
$rel_field = (is_array($relConf) ? $relConf[1] : '_id');
$val = $val->get($rel_field,true);
}
} elseif ($this->dbsType == 'mongo' && !$val instanceof \MongoId)
$val = new \MongoId($val);
} elseif ($this->dbsType == 'mongo' && (($this->db->legacy() && !$val instanceof \MongoId)
|| (!$this->db->legacy() && !$val instanceof \MongoDB\BSON\ObjectId)))
$val = $this->db->legacy() ? new \MongoId($val) : new \MongoDB\BSON\ObjectId($val);
} elseif (isset($fields[$key]['has-one'])){
$relConf = $fields[$key]['has-one'];
if (is_null($val)) {
@@ -1406,45 +1484,54 @@ class Cortex extends Cursor {
$val = $this->getForeignKeysArray($val, $rel_field, $key);
}
elseif (isset($fields[$key]['has-many'])) {
// many-to-many, bidirectional
$relConf = $fields[$key]['has-many'];
if ($relConf['hasRel'] == 'has-many') {
// many-to-many, bidirectional
// many-to-one, inverse
if ($relConf['hasRel'] == 'has-many'
|| $relConf['hasRel'] == 'belongs-to-one') {
// custom setter
$val = $this->emit('set_'.$key, $val);
$val = $this->getForeignKeysArray($val,'_id',$key);
if (empty($val) && is_array($val))
$val=new CortexCollection();
$this->saveCsd[$key] = $val; // array of keys
$this->fieldsCache[$key] = $val;
return $val;
} elseif ($relConf['hasRel'] == 'belongs-to-one') {
// TODO: many-to-one, bidirectional, inverse way
trigger_error("not implemented",E_USER_ERROR);
}
}
// convert array content
if (is_array($val) && $this->dbsType == 'sql')
if ($fields[$key]['type'] == self::DT_SERIALIZED)
$val = serialize($val);
elseif ($fields[$key]['type'] == self::DT_JSON)
$val = json_encode($val);
else
trigger_error(sprintf(self::E_ARRAY_DATATYPE, $key),E_USER_ERROR);
// add nullable polyfill
if ($val === NULL && ($this->dbsType == 'jig' || $this->dbsType == 'mongo')
&& array_key_exists('nullable', $fields[$key]) && $fields[$key]['nullable'] === false)
trigger_error(sprintf(self::E_NULLABLE_COLLISION,$key),E_USER_ERROR);
// MongoId shorthand
if ($this->dbsType == 'mongo' && !$val instanceof \MongoId) {
if ($this->dbsType == 'mongo' && (($this->db->legacy() && !$val instanceof \MongoId)
|| (!$this->db->legacy() && !$val instanceof \MongoDB\BSON\ObjectId))) {
if ($key == '_id')
$val = new \MongoId($val);
$val = $this->db->legacy() ? new \MongoId($val) : new \MongoDB\BSON\ObjectId($val);
elseif (preg_match('/INT/i',$fields[$key]['type'])
&& !isset($fields[$key]['relType']))
$val = (int) $val;
}
// cast boolean
if (preg_match('/BOOL/i',$fields[$key]['type'])) {
$val = !$val || $val==='false' ? false : (bool) $val;
if ($this->dbsType == 'sql')
$val = (int) $val;
}
// custom setter
$val = $this->emit('set_'.$key, $val);
// convert array content
if (is_array($val) && $this->dbsType == 'sql') {
if ($fields[$key]['type']==self::DT_SERIALIZED)
$val=serialize($val);
elseif ($fields[$key]['type']==self::DT_JSON)
$val=json_encode($val);
else
trigger_error(sprintf(self::E_ARRAY_DATATYPE,$key),E_USER_ERROR);
}
} else {
// custom setter
$val = $this->emit('set_'.$key, $val);
}
// fluid SQL
if ($this->fluid && $this->dbsType == 'sql') {
@@ -1474,8 +1561,6 @@ class Cortex extends Cursor {
$this->mapper->schema($fields);
}
}
// custom setter
$val = $this->emit('set_'.$key, $val);
return $this->mapper->set($key, $val);
}
@@ -1555,6 +1640,8 @@ class Cortex extends Cursor {
}
if ($raw) {
$out = $this->exists($key) ? $this->mapper->{$key} : NULL;
if ($this->dbsType == 'mongo' && !$this->db->legacy() && $out instanceof \MongoDB\Model\BSONArray)
$out = (array) $out;
return $out;
}
if (!empty($fields) && isset($fields[$key]) && is_array($fields[$key])
@@ -1692,7 +1779,7 @@ class Cortex extends Cursor {
} // no collection
else {
// find foreign keys
$fId=$this->get($fromConf['relPK'],true);
$fId=$this->get($fromConf['localKey'],true);
$filter = [$fromConf['relField'].' = ?',$fId];
if ($fromConf['isSelf']) {
$filter = [$fromConf['relField'].' = ?',$fId];
@@ -1713,7 +1800,7 @@ class Cortex extends Cursor {
unset($rel);
$rel = $this->getRelInstance($fromConf[0],null,$key,true);
// load foreign models
$filter = array($toConf['relPK'].' IN ?', $fkeys);
$filter = array($fromConf['relPK'].' IN ?', $fkeys);
$filter = $this->mergeWithRelFilter($key, $filter);
$this->fieldsCache[$key] = $rel->find($filter,
$this->getRelFilterOption($key),$this->_ttl);
@@ -1724,7 +1811,7 @@ class Cortex extends Cursor {
elseif (isset($fields[$key]['belongs-to-many'])) {
// many-to-many, unidirectional
$fields[$key]['type'] = self::DT_JSON;
$result = !$this->exists($key) ? null :$this->mapper->get($key);
$result = $this->getRaw($key);
if ($this->dbsType == 'sql')
$result = json_decode($result, true);
if (!is_array($result))
@@ -1786,7 +1873,8 @@ class Cortex extends Cursor {
// fetch cached value, if existing
$val = array_key_exists($key,$this->fieldsCache) ? $this->fieldsCache[$key]
: (($this->exists($key)) ? $this->mapper->{$key} : null);
if ($this->dbsType == 'mongo' && $val instanceof \MongoId) {
if ($this->dbsType == 'mongo' && (($this->db->legacy() && $val instanceof \MongoId) ||
(!$this->db->legacy() && $val instanceof \MongoDB\BSON\ObjectId))) {
// conversion to string makes further processing in template, etc. much easier
$val = (string) $val;
}
@@ -1836,13 +1924,14 @@ class Cortex extends Cursor {
$isMongo = ($this->dbsType == 'mongo');
foreach ($val as &$item) {
if (is_object($item) &&
!($isMongo && $item instanceof \MongoId)) {
!($isMongo && (($this->db->legacy() && $item instanceof \MongoId) ||
(!$this->db->legacy() && $item instanceof \MongoDB\BSON\ObjectId)))) {
if (!$item instanceof Cortex || $item->dry())
trigger_error(self::E_INVALID_RELATION_OBJECT,E_USER_ERROR);
else $item = $item->get($rel_field,true);
}
if ($isMongo && $rel_field == '_id' && is_string($item))
$item = new \MongoId($item);
$item = $this->db->legacy() ? new \MongoId($item) : new \MongoDB\BSON\ObjectId($item);
if (is_numeric($item))
$item = (int) $item;
unset($item);
@@ -1896,16 +1985,16 @@ class Cortex extends Cursor {
/**
* get relation model from config
* @param $fieldConf
* @param $relConf
* @param $key
* @return Cortex
*/
protected function getRelFromConf(&$fieldConf, $key) {
if (!is_array($fieldConf))
$fieldConf = array($fieldConf, '_id');
$rel = $this->getRelInstance($fieldConf[0],null,$key,true);
if($this->dbsType=='sql' && $fieldConf[1] == '_id')
$fieldConf[1] = $rel->primary;
protected function getRelFromConf(&$relConf, $key) {
if (!is_array($relConf))
$relConf = array($relConf, '_id');
$rel = $this->getRelInstance($relConf[0],null,$key,true);
if($this->dbsType=='sql' && $relConf[1] == '_id')
$relConf[1] = $rel->primary;
return $rel;
}
@@ -2097,10 +2186,11 @@ class Cortex extends Cursor {
public function reset($mapper = true) {
if ($mapper)
$this->mapper->reset();
$this->fieldsCache=array();
$this->saveCsd=array();
$this->countFields=array();
$this->preBinds=array();
$this->fieldsCache=[];
$this->saveCsd=[];
$this->countFields=[];
$this->preBinds=[];
$this->vFields=[];
$this->grp_stack=null;
// set default values
if (($this->dbsType == 'jig' || $this->dbsType == 'mongo')
@@ -2132,6 +2222,8 @@ class Cortex extends Cursor {
* @return mixed
*/
public function changed($key=null) {
if ($key=='_id')
$key = $this->primary;
if (method_exists($this->mapper,'changed'))
return $this->mapper->changed($key);
else
@@ -2245,7 +2337,7 @@ class CortexQueryParser extends \Prefab {
if (is_int(strpos($where, ':')))
list($parts, $args) = $this->convertNamedParams($parts, $args);
foreach ($parts as &$part) {
$part = $this->_mongo_parse_relational_op($part, $args, $fieldConf);
$part = $this->_mongo_parse_relational_op($part, $args, $db, $fieldConf);
unset($part);
}
$ncond = $this->_mongo_parse_logical_op($parts);
@@ -2341,18 +2433,19 @@ class CortexQueryParser extends \Prefab {
// https://www.debuggex.com/r/6AXwJ1Y3Aac8aocQ/3
// https://regex101.com/r/yM5vK4/1
// this took me lots of sleepless nights
return preg_replace_callback('/'.
'(\w+\((?:[^)(]+|(?R))*\))|'. // exclude SQL function names "foo("
$out = preg_replace_callback('/'.
'\w+\((?:(?>[^()]+)|\((?:(?>[^()]+)|^(?R))*\))*\)|'. // exclude SQL function names "foo("
'(?:(\b(?<!:)'. // exclude bind parameter ":foo"
'[a-zA-Z_](?:[\w\-_.]+\.?))'. // match only identifier, exclude values
'(?=[\s<>=!)]|$))/i', // only when part of condition or within brackets
function($match) use($db) {
if (!isset($match[2]))
if (!isset($match[1]))
return $match[0];
if (preg_match('/\b(AND|OR|IN|LIKE|NOT)\b/i',$match[1]))
return $match[1];
if (preg_match('/\b(AND|OR|IN|LIKE|NOT)\b/i',$match[2]))
return $match[2];
return $db->quotekey($match[2]);
return $db->quotekey($match[1]);
}, $cond);
return $out ?: $cond;
}
/**
@@ -2362,8 +2455,8 @@ class CortexQueryParser extends \Prefab {
* @return string
*/
public function sql_prependTableToFields($cond, $table) {
return preg_replace_callback('/'.
'(\w+\((?:[^)(]+|(?R))*\))|'.
$out = preg_replace_callback('/'.
'(\w+\((?:[^)(]+|\((?:[^)(]+|(?R))*\))*\))|'.
'(?:(\s)|^|(?<=[(]))'.
'([a-zA-Z_](?:[\w\-_]+))'.
'(?=[\s<>=!)]|$)/i',
@@ -2374,6 +2467,7 @@ class CortexQueryParser extends \Prefab {
return $match[0];
return $match[2].$table.'.'.$match[3];
}, $cond);
return $out ?: $cond;
}
/**
@@ -2391,7 +2485,7 @@ class CortexQueryParser extends \Prefab {
if (preg_match('/\s*\b(AND|OR)\b\s*/i',$part))
continue;
// prefix field names
$part = preg_replace('/([a-z_-]+)/i', '@$1', $part, -1, $count);
$part = preg_replace('/([a-z_-]+(?:[\w-]+))/i', '@$1', $part, -1, $count);
// value comparison
if (is_int(strpos($part, '?'))) {
$val = array_shift($args);
@@ -2401,7 +2495,7 @@ class CortexQueryParser extends \Prefab {
if (is_int(strpos($upart = strtoupper($part), ' @LIKE '))) {
if ($not = is_int($npos = strpos($upart, '@NOT')))
$pos = $npos;
$val = $this->_likeValueToRegEx($val);
$val = '/'.$this->_likeValueToRegEx($val).'/iu';
$part = ($not ? '!' : '').'preg_match(?,'.$match[0].')';
} // find IN operator
elseif (is_int($pos = strpos($upart, ' @IN '))) {
@@ -2495,10 +2589,11 @@ class CortexQueryParser extends \Prefab {
* find and convert relational operators
* @param $part
* @param $args
* @param \DB\Mongo $db
* @param null $fieldConf
* @return array|null
*/
protected function _mongo_parse_relational_op($part, &$args, $fieldConf=null) {
protected function _mongo_parse_relational_op($part, &$args, \DB\Mongo $db, $fieldConf=null) {
if (is_null($part))
return $part;
if (preg_match('/\<\=|\>\=|\<\>|\<|\>|\!\=|\=\=|\=|like|not like|in|not in/i', $part, $match)) {
@@ -2516,17 +2611,21 @@ class CortexQueryParser extends \Prefab {
if ($key == '_id' || (isset($fieldConf[$key]) && isset($fieldConf[$key]['relType']))) {
if (is_array($var))
foreach ($var as &$id) {
if (!$id instanceof \MongoId)
if ($db->legacy() && !$id instanceof \MongoId)
$id = new \MongoId($id);
elseif (!$db->legacy() && !$id instanceof \MongoDB\BSON\ObjectId)
$id = new \MongoDB\BSON\ObjectId($id);
unset($id);
}
elseif(!$var instanceof \MongoId)
elseif($db->legacy() && !$var instanceof \MongoId)
$var = new \MongoId($var);
elseif(!$db->legacy() && !$var instanceof \MongoDB\BSON\ObjectId)
$var = new \MongoDB\BSON\ObjectId($var);
}
// find LIKE operator
if (in_array($upart, array('LIKE','NOT LIKE'))) {
$rgx = $this->_likeValueToRegEx($var);
$var = new \MongoRegex($rgx);
$var = $db->legacy() ? new \MongoRegex('/'.$rgx.'/iu') : new \MongoDB\BSON\Regex($rgx,'iu');
if ($upart == 'NOT LIKE')
$var = array('$not' => $var);
} // find IN operator
@@ -2561,7 +2660,7 @@ class CortexQueryParser extends \Prefab {
// %var -> /var$/
elseif ($var[0] == '%')
$var = substr($var, 1).'$';
return '/'.$var.'/iu';
return $var;
}
/**
@@ -2573,16 +2672,18 @@ class CortexQueryParser extends \Prefab {
*
* @param array $options
* @param string $engine
* @param object $db
* @return array|null
*/
public function prepareOptions($options, $engine) {
public function prepareOptions($options, $engine, $db) {
if (empty($options) || !is_array($options))
return null;
switch ($engine) {
case 'jig':
if (array_key_exists('order', $options))
$options['order'] = str_replace(array('asc', 'desc'),
array('SORT_ASC', 'SORT_DESC'), strtolower($options['order']));
$options['order'] = preg_replace(
['/(?<=\h)(ASC)(?=\W|$)/i','/(?<=\h)(DESC)(?=\W|$)/i'],
['SORT_ASC','SORT_DESC'],$options['order']);
break;
case 'mongo':
if (array_key_exists('order', $options)) {
@@ -2598,12 +2699,27 @@ class CortexQueryParser extends \Prefab {
if (array_key_exists('group', $options) && is_string($options['group'])) {
$keys = explode(',',$options['group']);
$options['group']=array('keys'=>array(),'initial'=>array(),
'reduce'=>'function (obj, prev) {}','finalize'=>'');
'reduce'=>'function (obj, prev) {}','finalize'=>'');
$keys = array_combine($keys,array_fill(0,count($keys),1));
$options['group']['keys']=$keys;
$options['group']['initial']=$keys;
}
break;
case 'sql':
$char=substr($db->quotekey(''),0,1);
if (array_key_exists('order', $options) &&
FALSE===strpos($options['order'],$char))
$options['order']=preg_replace_callback(
'/(\w+\h?\(|'. // skip function names
'\b(?!\w+)(?:\s+\w+)+|' . // skip field args
'\)\s+\w+)|'. // skip function args
'(\b\d?[a-zA-Z_](?:[\w\-.])*)/i', // match table/field keys
function($match) use($db) {
if (!isset($match[2]))
return $match[1];
return $db->quotekey($match[2]);
}, $options['order']);
break;
}
return $options;
}
@@ -2702,7 +2818,7 @@ class CortexCollection extends \ArrayIterator {
if (!$this->hasRelSet($prop) || !($relSet = $this->getRelSet($prop)))
return null;
foreach ($keys as &$key) {
if ($key instanceof \MongoId)
if ($key instanceof \MongoId || $key instanceof \MongoDB\BSON\ObjectId)
$key = (string) $key;
unset($key);
}

View File

@@ -141,8 +141,10 @@ class Map extends Controller\AccessController {
$wormholes = Model\BasicModel::getNew('WormholeModel');
$rows = $wormholes->find('id > 0', null, $expireTimeSQL);
$wormholesData = [];
foreach((array)$rows as $rowData){
$wormholesData[$rowData->name] = $rowData->getData();
if($rows){
foreach((array)$rows as $rowData){
$wormholesData[$rowData->name] = $rowData->getData();
}
}
$return->wormholes = $wormholesData;

View File

@@ -0,0 +1,134 @@
<?php
/**
* Created by PhpStorm.
* User: exodu
* Date: 27.05.2018
* Time: 14:17
*/
namespace Controller\Api;
use Controller;
use DB\Database;
use Model;
class Setup extends Controller\Controller {
/**
* build search index from existing data (e.g. Systems)
* OR import data from ESI (e.g. Structures)
* -> optional build/import smaller chunks of data
* @param \Base $f3
* @throws \Exception
*/
public function buildIndex(\Base $f3){
$postData = (array)$f3->get('POST');
$type = (string)$postData['type'];
$count = (int)$postData['count'];
$return = (object) [];
$return->error = [];
$return->type = $type;
$return->count = $count;
$return->countAll = 0;
$return->countBuild = 0;
$return->countBuildAll = 0;
$return->progress = 0;
/**
* sum array values
* @param int $carry
* @param int $value
* @return int
*/
$sum = function(int $carry, int $value){
$carry += $value;
return $carry;
};
/**
* calc percent
* @param int $countAll
* @param int $count
* @return int
*/
$percent = function(int $countAll, int $count){
return $countAll ? floor((100/$countAll) * $count) : 0;
};
$controller = new Controller\Ccp\Universe();
switch($type){
case 'Systems':
$length = 200;
$offset = $count * $length;
$buildInfo = $controller->buildSystemsIndex($offset, $length);
$return->countAll = $buildInfo['countAll'];
$return->countBuild = $buildInfo['countBuild'];
$return->countBuildAll = count($controller->getSystemsIndex());
$return->progress = $percent($return->countAll, $offset + $length);
break;
case 'Structures':
$categoryId = 65;
$length = 2;
$offset = $count * $length;
$buildInfo = $controller->setupCategory($categoryId, $offset, $length);
$categoryUniverseModel = Model\Universe\BasicUniverseModel::getNew('CategoryModel');
$return->countAll = (int)$f3->get('REQUIREMENTS.DATA.STRUCTURES');
$return->countBuild = array_reduce($buildInfo, $sum, 0);
$return->countBuildAll = $categoryUniverseModel->getById($categoryId, 0)->getTypesCount(false);
$return->progress = $percent($return->countAll, $return->countBuildAll);
break;
case 'Ships':
$categoryId = 6;
$length = 2;
$offset = $count * $length;
$buildInfo = $controller->setupCategory($categoryId, $offset, $length);
$categoryUniverseModel = Model\Universe\BasicUniverseModel::getNew('CategoryModel');
$return->countAll = (int)$f3->get('REQUIREMENTS.DATA.SHIPS');
$return->countBuild = array_reduce($buildInfo, $sum, 0);
$return->countBuildAll = $categoryUniverseModel->getById($categoryId, 0)->getTypesCount(false);
$return->progress = $percent($return->countAll, $return->countBuildAll);
break;
}
if($return->countBuildAll < $return->countAll){
$return->count++;
}
echo json_encode($return);
}
/**
* clear search index
* @param \Base $f3
* @throws \Exception
*/
public function clearIndex(\Base $f3){
$postData = (array)$f3->get('POST');
$type = (string)$postData['type'];
$return = (object) [];
$return->error = [];
$return->type = $type;
$return->count = 0;
$return->countAll = 0;
$return->countBuild = 0;
$return->countBuildAll = 0;
$return->progress = 0;
$controller = new Controller\Ccp\Universe();
switch($type) {
case 'Systems':
$controller->clearSystemsIndex();
$systemUniverseModel = Model\Universe\BasicUniverseModel::getNew('SystemModel');
$return->countAll = Database::instance()->getRowCount($systemUniverseModel->getTable(), 'UNIVERSE');
break;
}
echo json_encode($return);
}
}

View File

@@ -8,6 +8,7 @@
namespace Controller;
use Controller\Ccp\Universe;
use data\filesystem\Search;
use DB;
use DB\SQL;
@@ -111,8 +112,14 @@ class Setup extends Controller {
'Model\Universe\GroupModel',
'Model\Universe\CategoryModel',
'Model\Universe\StructureModel',
//'Model\Universe\RegionModel',
//'Model\Universe\ConstellationModel'
'Model\Universe\WormholeModel',
'Model\Universe\StargateModel',
'Model\Universe\StarModel',
'Model\Universe\PlanetModel',
'Model\Universe\SystemModel',
'Model\Universe\ConstellationModel',
'Model\Universe\RegionModel',
'Model\Universe\SystemStaticModel'
],
'tables' => []
],
@@ -267,7 +274,7 @@ class Setup extends Controller {
$f3->set('socketInformation', $this->getSocketInformation());
// set index information
$f3->set('indexInformation', $this->getIndexData());
$f3->set('indexInformation', $this->getIndexData($f3));
// set cache size
$f3->set('cacheSize', $this->getCacheData($f3));
@@ -932,7 +939,7 @@ class Setup extends Controller {
foreach($requiredTables as $requiredTableName => $data){
$tableExists = false;
$tableEmpty = true;
$tableRows = 0;
// Check if table status is OK (no errors/warnings,..)
$tableStatusCheckCount = 0;
@@ -944,8 +951,7 @@ class Setup extends Controller {
$tableModifierTemp = new MySQL\TableModifier($requiredTableName, $schema);
$currentColumns = $tableModifierTemp->getCols(true);
// get row count
$countRes = $db->exec("SELECT COUNT(*) `num` FROM " . $db->quotekey($requiredTableName) );
$tableEmpty = $countRes[0]['num'] > 0 ? false : true;
$tableRows = $this->dbLib->getRowCount($requiredTableName, $dbKey);
}else{
// table missing
$dbStatusCheckCount++;
@@ -1124,7 +1130,7 @@ class Setup extends Controller {
}
$dbStatusCheckCount += $tableStatusCheckCount;
$requiredTables[$requiredTableName]['empty'] = $tableEmpty;
$requiredTables[$requiredTableName]['rows'] = $tableRows;
$requiredTables[$requiredTableName]['exists'] = $tableExists;
$requiredTables[$requiredTableName]['statusCheckCount'] = $tableStatusCheckCount;
}
@@ -1339,76 +1345,136 @@ class Setup extends Controller {
return $socketInformation;
}
/** get indexed (cache) data information
/**
* get indexed (cache) data information
* @param \Base $f3
* @return array
* @throws \Exception
*/
protected function getIndexData(){
protected function getIndexData(\Base $f3){
// active DB and tables are required for obtain index data
if(!$this->databaseHasError){
$categoryUniverseModel = Model\Universe\BasicUniverseModel::getNew('CategoryModel');
$systemUniverseModel = Model\Universe\BasicUniverseModel::getNew('SystemModel');
$systemNeighbourModel = Model\BasicModel::getNew('SystemNeighbourModel');
$wormholeModel = Model\BasicModel::getNew('WormholeModel');
$systemWormholeModel = Model\BasicModel::getNew('SystemWormholeModel');
$constellationWormholeModel = Model\BasicModel::getNew('ConstellationWormholeModel');
$indexInfo = [
'SystemNeighbourModel' => [
'Systems' => [
'task' => [
[
'action' => 'clearIndex',
'label' => 'Clear',
'icon' => 'fa-times',
'btn' => 'btn-danger'
],[
'action' => 'buildIndex',
'label' => 'build',
'label' => 'Build',
'icon' => 'fa-sync',
'btn' => 'btn-primary'
]
],
'table' => Model\BasicModel::getNew('SystemNeighbourModel')->getTable(),
'count' => $this->dbLib->getRowCount( Model\BasicModel::getNew('SystemNeighbourModel')->getTable() )
'label' => 'build systems index',
'countBuild' => count((new Universe())->getSystemsIndex()),
'countAll' => $this->dbLib->getRowCount($systemUniverseModel->getTable(), 'UNIVERSE'),
'tooltip' => 'build up a static search index over all systems found on DB. Do not refresh page until import is complete (check progress)! Runtime: ~5min'
],
'Structures' => [
'task' => [
[
'action' => 'buildIndex',
'label' => 'Import',
'icon' => 'fa-sync',
'btn' => 'btn-primary'
]
],
'label' => 'import structures data',
'countBuild' => $categoryUniverseModel->getById(65, 0)->getTypesCount(false),
'countAll' => (int)$f3->get('REQUIREMENTS.DATA.STRUCTURES'),
'tooltip' => 'import all structure types (e.g. Citadels) from ESI. Runtime: ~15s'
],
'Ships' => [
'task' => [
[
'action' => 'buildIndex',
'label' => 'Import',
'icon' => 'fa-sync',
'btn' => 'btn-primary'
]
],
'label' => 'import ships data',
'countBuild' => $categoryUniverseModel->getById(6, 0)->getTypesCount(false),
'countAll' => (int)$f3->get('REQUIREMENTS.DATA.SHIPS'),
'tooltip' => 'import all ships types from ESI. Runtime: ~2min'
],
'SystemNeighbourModel' => [
'task' => [
[
'action' => 'buildIndex',
'label' => 'Build',
'icon' => 'fa-sync',
'btn' => 'btn-primary'
]
],
'label' => 'system_neighbour',
'countBuild' => $this->dbLib->getRowCount($systemNeighbourModel->getTable()),
'countAll' => 5214
],
'WormholeModel' => [
'task' => [
[
'action' => 'exportTable',
'label' => 'export',
'label' => 'Export',
'icon' => 'fa-download',
'btn' => 'btn-default'
],[
'action' => 'importTable',
'label' => 'import',
'label' => 'Import',
'icon' => 'fa-upload',
'btn' => 'btn-primary'
]
],
'table' => Model\BasicModel::getNew('WormholeModel')->getTable(),
'count' => $this->dbLib->getRowCount( Model\BasicModel::getNew('WormholeModel')->getTable() )
'label' => 'wormhole',
'countBuild' => $this->dbLib->getRowCount($wormholeModel->getTable()),
'countAll' => 89
],
'SystemWormholeModel' => [
'task' => [
[
'action' => 'exportTable',
'label' => 'export',
'label' => 'Export',
'icon' => 'fa-download',
'btn' => 'btn-default'
],[
'action' => 'importTable',
'label' => 'import',
'label' => 'Import',
'icon' => 'fa-upload',
'btn' => 'btn-primary'
]
],
'table' => Model\BasicModel::getNew('SystemWormholeModel')->getTable(),
'count' => $this->dbLib->getRowCount( Model\BasicModel::getNew('SystemWormholeModel')->getTable() )
'label' => 'system_wormhole',
'countBuild' => $this->dbLib->getRowCount($systemWormholeModel->getTable()),
'countAll' => 233
],
'ConstellationWormholeModel' => [
'task' => [
[
'action' => 'exportTable',
'label' => 'export',
'label' => 'Export',
'icon' => 'fa-download',
'btn' => 'btn-default'
],[
'action' => 'importTable',
'label' => 'import',
'label' => 'Import',
'icon' => 'fa-upload',
'btn' => 'btn-primary'
]
],
'table' => Model\BasicModel::getNew('ConstellationWormholeModel')->getTable(),
'count' => $this->dbLib->getRowCount( Model\BasicModel::getNew('ConstellationWormholeModel')->getTable() )
'label' => 'constellation_wormhole',
'countBuild' => $this->dbLib->getRowCount( $constellationWormholeModel->getTable() ),
'countAll' => 460
]
];
}else{

246
app/main/cron/universe.php Normal file
View File

@@ -0,0 +1,246 @@
<?php
/**
* Created by PhpStorm.
* User: exodu
* Date: 19.05.2018
* Time: 03:46
*/
namespace Cron;
use Model;
class Universe {
const LOG_TEXT = '%s type: %s %s/%s peak: %s total: %s msg: %s';
/**
* format counter for output (min length)
* @param int $counter
* @return string
*/
private function formatCounterValue(int $counter){
return str_pad($counter, 4, ' ', STR_PAD_LEFT);
}
/**
* format id for output (min length)
* @param int $counter
* @return string
*/
private function formatIdValue(int $counter){
return str_pad($counter, 10, ' ', STR_PAD_LEFT);
}
/**
* format Byte §size for output
* @param int $size
* @return string
*/
private function formatMemoryValue(int $size){
$unit = ['B','KB','MB','GB','TB','PB'];
return str_pad(number_format(@round($size/pow(1024,($i=floor(log($size,1024)))),2), 2, '.', '') . '' . $unit[$i], 9, ' ', STR_PAD_LEFT);
}
/**
* format seconds for output
* @param float $time
* @return string
*/
private function formatSeconds(float $time){
$time = round($time, 5);
$formatSeconds = function($seconds){
return str_pad(number_format(round($seconds, 5), 5), 8, ' ', STR_PAD_LEFT);
};
$formatted = $time < 60 ? $formatSeconds($time) . 's' : floor($time / 60) . 'm ' . $formatSeconds(fmod($time, 60)) . 's';
return str_pad($formatted, 14, ' ', STR_PAD_LEFT);
}
/**
* flush output
*/
private function echoFlush(){
flush();
ob_flush();
}
/**
* echo input parameters
* @param string $type
* @param int $paramOffset
* @param int $paramLength
*/
private function echoParams(string $type, int $paramOffset, int $paramLength){
echo 'params ───────────────────────────────────────────────────────────────────────────────────────────────────────' . PHP_EOL;
echo 'type : ' . $type . PHP_EOL;
echo 'offset : ' . $paramOffset . PHP_EOL;
echo 'length : ' . $paramLength . PHP_EOL;
$this->echoFlush();
}
/**
* echo configuration
*/
private function echoConfig(){
echo 'config ───────────────────────────────────────────────────────────────────────────────────────────────────────' . PHP_EOL;
echo 'max_execution_time : ' . ini_get('max_execution_time') . PHP_EOL;
echo 'memory_limit : ' . ini_get('memory_limit') . PHP_EOL;
$this->echoFlush();
}
/**
* echo information
* @param int $total
* @param int $offset
* @param int $importCount
* @param array $ids
*/
private function echoInfo(int $total, int $offset, int $importCount, array $ids){
echo 'info ─────────────────────────────────────────────────────────────────────────────────────────────────────────' . PHP_EOL;
echo 'all data : ' . $total . PHP_EOL;
echo 'import offset : ' . $offset . PHP_EOL;
echo 'import count : ' . $importCount . PHP_EOL;
echo 'import chunk : ' . implode(',', $ids) . PHP_EOL;
$this->echoFlush();
}
/**
* echo start
*/
private function echoStart(){
echo 'start ────────────────────────────────────────────────────────────────────────────────────────────────────────' . PHP_EOL;
$this->echoFlush();
}
/**
* echo loop start information
* @param int $count
* @param int $importCount
* @param int $id
*/
private function echoLoading(int $count, int $importCount, int $id){
echo '[' . date('H:i:s') . '] loading... ' . $this->formatCounterValue($count) . '/' . $importCount . ' id: ' . $this->formatIdValue($id) . PHP_EOL;
$this->echoFlush();
}
/**
* echo loop finish information
* @param int $importCount
* @param int $id
* @param float $timeLoopStart
* @param float $timeTotalStart
*/
private function echoLoaded(int $importCount, int $id, float $timeLoopStart, float $timeTotalStart){
echo '[' . date('H:i:s') . '] loaded ' . str_pad('', strlen($importCount), ' ') . ' id: ' . $this->formatIdValue($id) .
' memory: ' . $this->formatMemoryValue(memory_get_usage()) .
' time: ' . $this->formatSeconds(microtime(true) - $timeLoopStart) .
' total: ' . $this->formatSeconds(microtime(true) - $timeTotalStart) . PHP_EOL;
$this->echoFlush();
}
/**
* echo finish information
* @param int $count
* @param int $importCount
* @param float $timeTotalStart
*/
private function echoFinish(int $count, int $importCount, float $timeTotalStart){
echo 'finished ─────────────────────────────────────────────────────────────────────────────────────────────────────' . PHP_EOL;
echo '[' . date('H:i:s') . '] ' . $this->formatCounterValue($count) . '/' . $importCount .
' peak: ' . $this->formatMemoryValue(memory_get_peak_usage ()) .
' total: ' . $this->formatSeconds(microtime(true) - $timeTotalStart) . PHP_EOL;
$this->echoFlush();
}
/**
* imports static universe data from ESI
* >> php index.php "/cron/setup?model=system&offset=0&length=5"
* @param \Base $f3
* @throws \Exception
*/
function setup(\Base $f3){
$params = (array)$f3->get('GET');
$type = (string)$params['model'];
$paramOffset = (int)$params['offset'];
$paramLength = (int)$params['length'];
$timeTotalStart = microtime(true);
$msg = '';
$ids = [];
$count = 0;
$importCount = [];
$modelClass = '';
$setupModel = function(Model\Universe\BasicUniverseModel &$model, int $id){};
switch($type){
case 'system':
// load systems + dependencies (planets, star, types,...)
$ids = $f3->ccpClient->getUniverseSystems();
$modelClass = 'SystemModel';
$setupModel = function(Model\Universe\SystemModel &$model, int $id){
$model->loadById($id);
$model->loadPlanetsData();
};
break;
case 'stargate':
// load all stargates. Systems must be present first!
$ids = $f3->ccpClient->getUniverseSystems();
$modelClass = 'SystemModel';
$setupModel = function(Model\Universe\SystemModel &$model, int $id){
$model->loadById($id);
$model->loadStargatesData();
};
break;
case 'index_system':
// setup system index, Systems must be present first!
$ids = $f3->ccpClient->getUniverseSystems();
$modelClass = 'SystemModel';
$setupModel = function(Model\Universe\SystemModel &$model, int $id){
$model->getById($id); // no loadById() here! would take "forever" when system not exists and build up first...
$model->buildIndex();
};
break;
default:
$msg = 'Model is not valid';
}
if($modelClass){
$this->echoParams($type, $paramOffset, $paramLength);
$this->echoConfig();
$total = count($ids);
$offset = ($paramOffset < 0) ? 0 : (($paramOffset >= $total) ? $total : $paramOffset);
$length = ($paramLength < 0) ? 0 : $paramLength;
sort($ids, SORT_NUMERIC);
$ids = array_slice($ids, $offset, $length);
$importCount = count($ids);
$this->echoInfo($total, $offset, $importCount, $ids);
$this->echoStart();
/**
* @var $model Model\Universe\SystemModel
*/
$model = Model\Universe\BasicUniverseModel::getNew($modelClass);
foreach($ids as $id){
$timeLoopStart = microtime(true);
$this->echoLoading(++$count, $importCount, $id);
$setupModel($model, $id);
$model->reset();
$this->echoLoaded($importCount, $id, $timeLoopStart, $timeTotalStart);
}
$this->echoFinish($count, $importCount, $timeTotalStart);
}
// Log --------------------------------------------------------------------------------------------------------
$log = new \Log('cron_' . __FUNCTION__ . '.log');
$log->write( sprintf(self::LOG_TEXT, __FUNCTION__, $type,
$this->formatCounterValue($count), $importCount, $this->formatMemoryValue(memory_get_peak_usage ()),
$this->formatSeconds(microtime(true) - $timeTotalStart), $msg) );
}
}

View File

@@ -35,6 +35,7 @@ class CcpClient extends \Prefab {
$client->setDatasource( Config::getEnvironmentData('CCP_ESI_DATASOURCE') );
$client->setUserAgent($this->getUserAgent());
$client->setDebugLevel($f3->get('DEBUG'));
//$client->setDebugLogRequests(true);
}else{
LogController::getLogger('ERROR')->write(sprintf(Config::ERROR_CLASS_NOT_EXISTS_COMPOSER, ApiClient::class));
}

View File

@@ -47,32 +47,6 @@ abstract class AbstractMapTrackingModel extends BasicModel implements LogModelIn
return array_merge(parent::getStaticFieldConf(), $this->trackingFieldConf);
}
/**
* validates a model field to be a valid relational model
* @param $key
* @param $val
* @return bool
* @throws \Exception\ValidationException
*/
protected function validate_notDry($key, $val): bool {
$valid = true;
if($colConf = $this->fieldConf[$key]){
if(isset($colConf['belongs-to-one'])){
if( (is_int($val) || ctype_digit($val)) && (int)$val > 0){
$valid = true;
}elseif( is_a($val, $colConf['belongs-to-one']) && !$val->dry() ){
$valid = true;
}else{
$valid = false;
$msg = 'Validation failed: "' . get_class($this) . '->' . $key . '" must be a valid instance of ' . $colConf['belongs-to-one'];
$this->throwValidationException($key, $msg);
}
}
}
return $valid;
}
/**
* log character activity create/update/delete events
* @param string $action

View File

@@ -111,7 +111,7 @@ abstract class BasicModel extends \DB\Cortex {
const ERROR_INVALID_MODEL_CLASS = 'Model class (%s) not found';
public function __construct($db = NULL, $table = NULL, $fluid = NULL, $ttl = 0){
public function __construct($db = NULL, $table = NULL, $fluid = NULL, $ttl = self::DEFAULT_TTL){
$this->addStaticFieldConfig();
@@ -318,6 +318,32 @@ abstract class BasicModel extends \DB\Cortex {
return $valid;
}
/**
* validates a model field to be a valid relational model
* @param $key
* @param $val
* @return bool
* @throws \Exception\ValidationException
*/
protected function validate_notDry($key, $val): bool {
$valid = true;
if($colConf = $this->fieldConf[$key]){
if(isset($colConf['belongs-to-one'])){
if( (is_int($val) || ctype_digit($val)) && (int)$val > 0){
$valid = true;
}elseif( is_a($val, $colConf['belongs-to-one']) && !$val->dry() ){
$valid = true;
}else{
$valid = false;
$msg = 'Validation failed: "' . get_class($this) . '->' . $key . '" must be a valid instance of ' . $colConf['belongs-to-one'];
$this->throwValidationException($key, $msg);
}
}
}
return $valid;
}
/**
* get key for for all objects in this table
* @return string
@@ -619,15 +645,21 @@ abstract class BasicModel extends \DB\Cortex {
/**
* export and download table data as *.csv
* this is primarily used for static tables
* @param array $fields
* @return bool
*/
public function exportData(){
public function exportData(array $fields = []){
$status = false;
if(static::$enableDataExport){
$tableModifier = static::getTableModifier();
$headers = $tableModifier->getCols();
if($fields){
// columns to export -> reIndex keys
$headers = array_values(array_intersect($headers, $fields));
}
// just get the records with existing columns
// -> no "virtual" fields or "new" columns
$this->fields($headers);
@@ -659,14 +691,16 @@ abstract class BasicModel extends \DB\Cortex {
/**
* import table data from a *.csv file
* @return bool
* @return array|bool
*/
public function importData(){
$status = false;
// rtrim(); for arrays (removes empty values) from the end
$rtrim = function($array = []){
return array_slice($array, 0, key(array_reverse($array, 1))+1);
$rtrim = function($array = [], $lengthMin = false){
$length = key(array_reverse(array_diff($array, ['']), 1))+1;
$length = $length < $lengthMin ? $lengthMin : $length;
return array_slice($array, 0, $length);
};
if(static::$enableDataImport){
@@ -680,7 +714,7 @@ abstract class BasicModel extends \DB\Cortex {
if(count($keys) > 0){
$tableData = [];
while (!feof($handle)) {
$tableData[] = array_combine($keys, $rtrim(fgetcsv($handle, 0, ';')));
$tableData[] = array_combine($keys, $rtrim(fgetcsv($handle, 0, ';'), count($keys)));
}
// import row data
$status = $this->importStaticData($tableData);
@@ -704,20 +738,22 @@ abstract class BasicModel extends \DB\Cortex {
*/
protected function importStaticData($tableData = []){
$rowIDs = [];
$columnNames = array_merge(['id'], array_keys($this->fieldConf));
$addedCount = 0;
$updatedCount = 0;
$deletedCount = 0;
$tableModifier = static::getTableModifier();
$fields = $tableModifier->getCols();
foreach($tableData as $rowData){
// search for existing record and update columns
$this->getById($rowData['id']);
$this->getById($rowData['id'], 0);
if($this->dry()){
$addedCount++;
}else{
$updatedCount++;
}
$this->copyfrom($rowData, $columnNames);
$this->copyfrom($rowData, $fields);
$this->save();
$rowIDs[] = $this->id;
$this->reset();
@@ -845,6 +881,21 @@ abstract class BasicModel extends \DB\Cortex {
return \Base::instance();
}
/**
* stores data direct into the Cache backend (e.g. Redis)
* $f3->set() used the same code. The difference is, that $f3->set()
* also loads data into the Hive.
* This can result in high RAM usage if a great number of key->values should be stored in Cache
* (like the search index for system data)
* @param string $key
* @param $data
* @param int $ttl
*/
public static function setCacheValue(string $key, $data, int $ttl = 0){
$cache = \Cache::instance();
$cache->set(self::getF3()->hash($key).'.var', $data, $ttl);
}
/**
* debug log function
* @param string $text

View File

@@ -197,8 +197,8 @@ class MapModel extends AbstractMapTrackingModel {
}
/**
* get map data
* -> this includes system and connection data as well!
* get data
* -> this includes system and connection data as well
* @return \stdClass
* @throws PathfinderException
* @throws \Exception

View File

@@ -110,9 +110,7 @@ class StructureModel extends BasicModel {
*/
public function set_structureId($structureId){
$structureId = (int)$structureId;
$structureId = $structureId ? : null;
return $structureId;
return $structureId ? : null;
}
/**

View File

@@ -242,13 +242,13 @@ class SystemModel extends AbstractMapTrackingModel {
$systemData->created = (object) [];
$systemData->created->created = strtotime($this->created);
if( is_object($this->createdCharacterId) ){
if(is_object($this->createdCharacterId)){
$systemData->created->character = $this->createdCharacterId->getData();
}
$systemData->updated = (object) [];
$systemData->updated->updated = strtotime($this->updated);
if( is_object($this->updatedCharacterId) ){
if(is_object($this->updatedCharacterId)){
$systemData->updated->character = $this->updatedCharacterId->getData();
}
@@ -701,11 +701,11 @@ class SystemModel extends AbstractMapTrackingModel {
}
/**
* overwrites parent
* @param null $db
* @param null $table
* @param null $fields
* @return bool
* @throws \Exception
*/
public static function setup($db=null, $table=null, $fields=null){
$status = parent::setup($db,$table,$fields);

View File

@@ -18,10 +18,26 @@ abstract class BasicUniverseModel extends BasicModel {
* data from Universe tables is static and does not change frequently
* -> refresh static data after X days
*/
const CACHE_MAX_DAYS = 7;
const CACHE_MAX_DAYS = 60;
const CACHE_KEY_PREFIX = 'index_universe_';
/**
* cache key for model data -> should "never" expire
* -> until manual remove and or global cache clear
*/
const CACHE_INDEX_EXPIRE_KEY = 86400 * 356 * 5;
protected $db = 'DB_UNIVERSE';
/**
* get model data -> should be overwritten
* @return null
*/
public function getData(){
return null;
}
/**
* Event "Hook" function
* return false will stop any further action
@@ -37,6 +53,78 @@ abstract class BasicUniverseModel extends BasicModel {
return parent::beforeUpdateEvent($self, $pkeys);
}
/**
* get hashKey for search index build
* -> used by the cache backend
* @param string $column
* @return bool|string
*/
public function getHashKey(string $column = '_id'){
$key = false;
if( !$this->dry() && $this->exists($column) ){
$key = self::generateHashKeyRow($this->getTable(), $this->$column);
}
return $key;
}
/**
* calculate time period (in seconds) from now on, until data get expired
* @return int
*/
/*
public function calcTtl() : int {
$ttl = 0;
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
);
// add expire period to last updated timestamp
$updateTime->modify('+' . self::CACHE_MAX_DAYS . ' day');
$seconds = $updateTime->getTimestamp() - $currentTime->getTimestamp();
if($seconds > 0){
$ttl = $seconds;
}
}
return $ttl;
}
*/
/**
* build up a "search" index for this model
* -> stores getData() result into Cache (RAM) for faster access
*/
public function buildIndex(){
$hashKeyId = $this->getHashKey();
$hashKeyName = $this->getHashKey('name');
if($hashKeyId && $hashKeyName){
$f3 = self::getF3();
$hashKeyTable = self::generateHashKeyTable($this->getTable());
if( !$f3->exists($hashKeyTable, $cachedData) ){
$cachedData = [];
}
if( !in_array($hashKeyName, $cachedData) ){
$cachedData[] = $hashKeyName;
}
// value update does not update ttl -> delete key from cache and add again
$f3->clear($hashKeyId);
$f3->clear($hashKeyName);
$f3->clear($hashKeyTable);
// straight into cache (no $f->set() ), no sync with hive here -> save ram
self::setCacheValue($hashKeyId, $this->getData(), self::CACHE_INDEX_EXPIRE_KEY);
self::setCacheValue($hashKeyName, $hashKeyId, self::CACHE_INDEX_EXPIRE_KEY);
self::setCacheValue($hashKeyTable, $cachedData, self::CACHE_INDEX_EXPIRE_KEY);
}
}
/**
* load object by $id
* -> if $id not exists in DB -> query API
@@ -54,6 +142,21 @@ abstract class BasicUniverseModel extends BasicModel {
}
}
/**
* load data by foreign key or other column than "id"
* @param string $key
* @param $value
*/
public function loadByKey(string $key, $value){
/**
* @var $model self
*/
$model = $this->getByForeignKey($key, $value, ['limit' => 1]);
if($model->isOutdated()){
$model->loadDataByKey($key, $value);
}
}
/**
* load data from API into $this and save $this
* @param int $id
@@ -62,6 +165,28 @@ abstract class BasicUniverseModel extends BasicModel {
*/
abstract protected function loadData(int $id, string $accessToken = '', array $additionalOptions = []);
protected function loadDataByKey(string $key, $value){}
/**
* generate hashKey for a table row data for search index build
* @param string $table
* @param $value
* @return string
*/
public static function generateHashKeyRow(string $table, $value){
return self::generateHashKeyTable($table) . '_' . md5(strtolower((string)$value));
}
/**
* generate hashKey for a complete table
* -> should hold hashKeys for multiple rows
* @param string $table
* @return string
*/
public static function generateHashKeyTable(string $table){
return self::CACHE_KEY_PREFIX . strtolower($table);
}
/**
* factory for all UniverseModels
* @param string $model

View File

@@ -49,14 +49,17 @@ class CategoryModel extends BasicUniverseModel {
/**
* get all groups for this category
* @param bool $published
* @return array|mixed
*/
protected function getGroups(){
protected function getGroups(bool $published = true){
$groups = [];
$this->filter('groups', [
'published = :published',
':published' => 1
]);
if($published){
$this->filter('groups', [
'published = :published',
':published' => 1
]);
}
if($this->groups){
$groups = $this->groups;
@@ -79,6 +82,24 @@ class CategoryModel extends BasicUniverseModel {
return $groupsData;
}
/**
* count all types that belong to groups in this category
* @param bool $published
* @return int
*/
public function getTypesCount(bool $published = true) : int {
$count = 0;
if( !$this->dry() ){
/**
* @var $group GroupModel
*/
foreach($groups = $this->getGroups($published) as $group){
$count += $group->getTypesCount($published);
}
}
return $count;
}
/**
* load data from API into $this and save $this
* @param int $id
@@ -95,17 +116,30 @@ class CategoryModel extends BasicUniverseModel {
/**
* load groups data for this category
* @param int $offset
* @param int $length 0 -> all groups
* @return array
*/
public function loadGroupsData(){
public function loadGroupsData(int $offset = 0, int $length = 0) : array {
$groupIds = [];
if( !$this->dry() ){
$data = self::getF3()->ccpClient->getUniverseCategoryData($this->_id);
if(!empty($data)){
foreach((array)$data['groups'] as $groupId){
array_multisort($data['groups'], SORT_ASC, SORT_NUMERIC);
if($length){
$data['groups'] = array_slice($data['groups'], $offset, $length);
}
foreach($data['groups'] as $groupId){
/**
* @var $group GroupModel
*/
$group = $this->rel('groups');
$group->loadById($groupId);
$groupIds[] = $groupId;
$group->reset();
}
}
}
return $groupIds;
}
}

View File

@@ -14,12 +14,6 @@ class ConstellationModel extends BasicUniverseModel {
protected $table = 'constellation';
/**
* No static columns added
* @var bool
*/
protected $addStaticFields = false;
protected $fieldConf = [
'name' => [
'type' => Schema::DT_VARCHAR128,
@@ -35,32 +29,94 @@ class ConstellationModel extends BasicUniverseModel {
'table' => 'region',
'on-delete' => 'CASCADE'
]
]
],
'validate' => 'validate_notDry'
],
'x' => [
'type' => Schema::DT_INT8,
'type' => Schema::DT_BIGINT,
'nullable' => false,
'default' => 0
],
'y' => [
'type' => Schema::DT_INT8,
'type' => Schema::DT_BIGINT,
'nullable' => false,
'default' => 0
],
'z' => [
'type' => Schema::DT_INT8,
'type' => Schema::DT_BIGINT,
'nullable' => false,
'default' => 0
],
'systems' => [
'has-many' => ['Model\Universe\SystemModel', 'constellationId']
]
];
/**
* get data
* @return \stdClass
*/
public function getData(){
$constellationData = (object) [];
$constellationData->id = $this->_id;
$constellationData->name = $this->name;
$constellationData->region = $this->regionId->getData();
return $constellationData;
}
/**
* setter for positions array (x/y/z)
* @param $position
* @return null
*/
public function set_position($position){
$position = (array)$position;
if(count($position) === 3){
$this->x = $position['x'];
$this->y = $position['y'];
$this->z = $position['z'];
}
return null;
}
/**
* @param int $id
* @param string $accessToken
* @param array $additionalOptions
*/
protected function loadData(int $id, string $accessToken = '', array $additionalOptions = []){
$data = self::getF3()->ccpClient->getUniverseConstellationData($id);
if(!empty($data)){
/**
* @var $region RegionModel
*/
$region = $this->rel('regionId');
$region->loadById($data['regionId'], $accessToken, $additionalOptions);
$data['regionId'] = $region;
$this->copyfrom($data, ['id', 'name', 'regionId', 'position']);
$this->save();
}
}
/**
* load systems data for this constellation
*/
public function loadSystemsData(){
if( !$this->dry() ){
$data = self::getF3()->ccpClient->getUniverseConstellationData($this->_id);
if(!empty($data)){
foreach((array)$data['systems'] as $systemId){
/**
* @var $system SystemModel
*/
$system = $this->rel('systems');
$system->loadById($systemId);
$system->reset();
}
}
}
}
}

View File

@@ -35,7 +35,8 @@ class GroupModel extends BasicUniverseModel {
'table' => 'category',
'on-delete' => 'CASCADE'
]
]
],
'validate' => 'validate_notDry'
],
'types' => [
'has-many' => ['Model\Universe\TypeModel', 'groupId']
@@ -61,14 +62,17 @@ class GroupModel extends BasicUniverseModel {
/**
* get all types for this group
* @param bool $published
* @return array|mixed
*/
protected function getTypes(){
protected function getTypes(bool $published = true){
$types = [];
$this->filter('types', [
'published = :published',
':published' => 1
]);
if($published){
$this->filter('types', [
'published = :published',
':published' => 1
]);
}
if($this->types){
$types = $this->types;
@@ -91,9 +95,26 @@ class GroupModel extends BasicUniverseModel {
return $typesData;
}
/**
* count all types in this group
* @param bool $published
* @return int
*/
public function getTypesCount(bool $published = true) : int {
return $this->dry() ? 0 : count($this->getTypes($published));
}
/**
* @param int $id
* @param string $accessToken
* @param array $additionalOptions
*/
protected function loadData(int $id, string $accessToken = '', array $additionalOptions = []){
$data = self::getF3()->ccpClient->getUniverseGroupData($id);
if(!empty($data)){
/**
* @var $category CategoryModel
*/
$category = $this->rel('categoryId');
$category->loadById($data['categoryId'], $accessToken, $additionalOptions);
$data['categoryId'] = $category;
@@ -105,17 +126,24 @@ class GroupModel extends BasicUniverseModel {
/**
* load types data for this group
* @return int
*/
public function loadTypesData(){
$count = 0;
if( !$this->dry() ){
$data = self::getF3()->ccpClient->getUniverseGroupData($this->_id);
if(!empty($data)){
foreach((array)$data['types'] as $typeId){
/**
* @var $type TypeModel
*/
$type = $this->rel('types');
$type->loadById($typeId);
$type->reset();
$count++;
}
}
}
return $count;
}
}

View File

@@ -0,0 +1,123 @@
<?php
/**
* Created by PhpStorm.
* User: exodu
* Date: 19.05.2018
* Time: 01:12
*/
namespace Model\Universe;
use DB\SQL\Schema;
class PlanetModel extends BasicUniverseModel {
protected $table = 'planet';
protected $fieldConf = [
'name' => [
'type' => Schema::DT_VARCHAR128,
'nullable' => false,
'default' => ''
],
'systemId' => [
'type' => Schema::DT_INT,
'index' => true,
'belongs-to-one' => 'Model\Universe\SystemModel',
'constraint' => [
[
'table' => 'system',
'on-delete' => 'CASCADE'
]
],
'validate' => 'validate_notDry'
],
'typeId' => [
'type' => Schema::DT_INT,
'index' => true,
'belongs-to-one' => 'Model\Universe\TypeModel',
'constraint' => [
[
'table' => 'type',
'on-delete' => 'SET NULL'
]
],
'validate' => 'validate_notDry'
],
'x' => [
'type' => Schema::DT_BIGINT,
'nullable' => false,
'default' => 0
],
'y' => [
'type' => Schema::DT_BIGINT,
'nullable' => false,
'default' => 0
],
'z' => [
'type' => Schema::DT_BIGINT,
'nullable' => false,
'default' => 0
]
];
/**
* get data
* @return \stdClass
*/
public function getData(){
$planetData = (object) [];
$planetData->id = $this->_id;
$planetData->name = $this->name;
$planetData->position = (object) [];
$planetData->position->x = $this->x;
$planetData->position->y = $this->y;
$planetData->position->z = $this->z;
return $planetData;
}
/**
* setter for positions array (x/y/z)
* @param $position
* @return null
*/
public function set_position($position){
$position = (array)$position;
if(count($position) === 3){
$this->x = $position['x'];
$this->y = $position['y'];
$this->z = $position['z'];
}
return null;
}
/**
* @param int $id
* @param string $accessToken
* @param array $additionalOptions
*/
protected function loadData(int $id, string $accessToken = '', array $additionalOptions = []){
$data = self::getF3()->ccpClient->getUniversePlanetData($id);
if(!empty($data)){
/**
* @var $system SystemModel
*/
$system = $this->rel('systemId');
$system->loadById($data['systemId'], $accessToken, $additionalOptions);
$data['systemId'] = $system;
/**
* @var $type TypeModel
*/
$type = $this->rel('typeId');
$type->loadById($data['typeId'], $accessToken, $additionalOptions);
$data['typeId'] = $type;
$this->copyfrom($data, ['id', 'name', 'systemId', 'typeId', 'position']);
$this->save();
}
}
}

View File

@@ -14,12 +14,6 @@ class RegionModel extends BasicUniverseModel {
protected $table = 'region';
/**
* No static columns added
* @var bool
*/
protected $addStaticFields = false;
protected $fieldConf = [
'name' => [
'type' => Schema::DT_VARCHAR128,
@@ -34,12 +28,47 @@ class RegionModel extends BasicUniverseModel {
],
];
/**
* get data
* @return \stdClass
*/
public function getData(){
$regionData = (object) [];
$regionData->id = $this->_id;
$regionData->name = $this->name;
return $regionData;
}
/**
* @param int $id
* @param string $accessToken
* @param array $additionalOptions
*/
protected function loadData(int $id, string $accessToken = '', array $additionalOptions = []){
$data = self::getF3()->ccpClient->getUniverseRegionData($id);
if(!empty($data)){
$this->copyfrom($data, ['id', 'name', 'description']);
$this->save();
}
}
/**
* load constellations data for this region
*/
public function loadConstellationsData(){
if( !$this->dry() ){
$data = self::getF3()->ccpClient->getUniverseRegionData($this->_id);
if(!empty($data)){
foreach((array)$data['constellations'] as $constellationsId){
/**
* @var $constellation ConstellationModel
*/
$constellation = $this->rel('constellations');
$constellation->loadById($constellationsId);
$constellation->reset();
}
}
}
}
}

View File

@@ -0,0 +1,164 @@
<?php
/**
* Created by PhpStorm.
* User: exodu
* Date: 19.05.2018
* Time: 04:30
*/
namespace Model\Universe;
use DB\SQL\Schema;
class StargateModel extends BasicUniverseModel {
protected $table = 'stargate';
protected $fieldConf = [
'name' => [
'type' => Schema::DT_VARCHAR128,
'nullable' => false,
'default' => ''
],
'systemId' => [
'type' => Schema::DT_INT,
'index' => true,
'belongs-to-one' => 'Model\Universe\SystemModel',
'constraint' => [
[
'table' => 'system',
'on-delete' => 'CASCADE'
]
],
'validate' => 'validate_notDry'
],
'typeId' => [
'type' => Schema::DT_INT,
'index' => true,
'belongs-to-one' => 'Model\Universe\TypeModel',
'constraint' => [
[
'table' => 'type',
'on-delete' => 'SET NULL'
]
],
'validate' => 'validate_notDry'
],
'destinationSystemId' => [
'type' => Schema::DT_INT,
'index' => true,
'belongs-to-one' => 'Model\Universe\SystemModel',
'constraint' => [
[
'table' => 'system',
'on-delete' => 'CASCADE'
]
],
'validate' => 'validate_notDry'
],
'x' => [
'type' => Schema::DT_BIGINT,
'nullable' => false,
'default' => 0
],
'y' => [
'type' => Schema::DT_BIGINT,
'nullable' => false,
'default' => 0
],
'z' => [
'type' => Schema::DT_BIGINT,
'nullable' => false,
'default' => 0
]
];
public function getData(){
$stargateData = (object) [];
$stargateData->id = $this->_id;
$stargateData->type = $this->typeId->name;
$stargateData->destination = $this->destinationSystemId->name;
return $stargateData;
}
/**
* setter for positions array (x/y/z)
* @param $position
* @return null
*/
public function set_position($position){
$position = (array)$position;
if(count($position) === 3){
$this->x = $position['x'];
$this->y = $position['y'];
$this->z = $position['z'];
}
return null;
}
/**
* @param int $id
* @param string $accessToken
* @param array $additionalOptions
*/
protected function loadData(int $id, string $accessToken = '', array $additionalOptions = []){
$data = self::getF3()->ccpClient->getUniverseStargateData($id);
if(!empty($data)){
if($this->get('systemId', true) !== $data['systemId']){
// new stargate or system changed
/**
* @var $system SystemModel
*/
$system = $this->rel('systemId');
$system->loadById($data['systemId'], $accessToken, $additionalOptions);
$data['systemId'] = $system;
}
if($this->get('typeId', true) !== $data['typeId']){
/**
* @var $type TypeModel
*/
$type = $this->rel('typeId');
$type->loadById($data['typeId'], $accessToken, $additionalOptions);
$data['typeId'] = $type;
}
if($this->get('destinationSystemId', true) !== $data['destination']->system_id){
// new stargate or destinationSystem changed
/**
* @var $destinationSystem SystemModel
*/
$destinationSystem = $this->rel('destinationSystemId');
// no loadById() here! we don´t want to insert/update systems that do not exist yet
$destinationSystem->getById($data['destination']->system_id, 0);
if( !$destinationSystem->dry() ){
$data['destinationSystemId'] = $destinationSystem;
$this->copyfrom($data, ['id', 'name', 'position', 'systemId', 'typeId', 'destinationSystemId']);
$this->save();
}
}
}
}
/**
* @param null $db
* @param null $table
* @param null $fields
* @return bool
* @throws \Exception
*/
public static function setup($db=null, $table=null, $fields=null){
$status = parent::setup($db,$table,$fields);
if($status === true){
$status = parent::setMultiColumnIndex(['systemId', 'destinationSystemId'], true);
}
return $status;
}
}

View File

@@ -0,0 +1,96 @@
<?php
/**
* Created by PhpStorm.
* User: exodu
* Date: 18.05.2018
* Time: 23:52
*/
namespace Model\Universe;
use DB\SQL\Schema;
class StarModel extends BasicUniverseModel {
protected $table = 'star';
protected $fieldConf = [
'name' => [
'type' => Schema::DT_VARCHAR128,
'nullable' => false,
'default' => ''
],
'typeId' => [
'type' => Schema::DT_INT,
'index' => true,
'belongs-to-one' => 'Model\Universe\TypeModel',
'constraint' => [
[
'table' => 'type',
'on-delete' => 'SET NULL'
]
],
'validate' => 'validate_notDry'
],
'age' => [
'type' => Schema::DT_BIGINT,
'nullable' => true,
'default' => null
],
'radius' => [
'type' => Schema::DT_BIGINT,
'nullable' => true,
'default' => null
],
'temperature' => [
'type' => Schema::DT_INT,
'nullable' => true,
'default' => null
],
'luminosity' => [
'type' => Schema::DT_FLOAT,
'nullable' => true,
'default' => null
],
'spectralClass' => [
'type' => Schema::DT_VARCHAR128,
'nullable' => true,
'default' => null
],
'system' => [
'has-one' => ['Model\Universe\SystemModel', 'starId']
]
];
/**
* get data
* @return \stdClass
*/
public function getData(){
$starData = (object) [];
$starData->id = $this->_id;
$starData->name = $this->typeId->name;
return $starData;
}
/**
* @param int $id
* @param string $accessToken
* @param array $additionalOptions
*/
protected function loadData(int $id, string $accessToken = '', array $additionalOptions = []){
$data = self::getF3()->ccpClient->getUniverseStarData($id);
if(!empty($data)){
/**
* @var $type TypeModel
*/
$type = $this->rel('typeId');
$type->loadById($data['typeId'], $accessToken, $additionalOptions);
$data['typeId'] = $type;
$this->copyfrom($data, ['id', 'name', 'typeId', 'age', 'radius', 'temperature', 'luminosity', 'spectralClass']);
$this->save();
}
}
}

View File

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

View File

@@ -0,0 +1,358 @@
<?php
/**
* Created by PhpStorm.
* User: exodu
* Date: 14.05.2018
* Time: 19:29
*/
namespace Model\Universe;
use DB\SQL\Schema;
class SystemModel extends BasicUniverseModel {
protected $table = 'system';
const ERROR_INVALID_WORMHOLE = 'Invalid wormhole name "%s" for system: "%s"';
protected $fieldConf = [
'name' => [
'type' => Schema::DT_VARCHAR128,
'nullable' => false,
'default' => ''
],
'constellationId' => [
'type' => Schema::DT_INT,
'index' => true,
'belongs-to-one' => 'Model\Universe\ConstellationModel',
'constraint' => [
[
'table' => 'constellation',
'on-delete' => 'CASCADE'
]
],
'validate' => 'validate_notDry'
],
'starId' => [
'type' => Schema::DT_INT,
'index' => true,
'belongs-to-one' => 'Model\Universe\StarModel',
'constraint' => [
[
'table' => 'star',
'on-delete' => 'CASCADE'
]
],
'validate' => 'validate_notDry'
],
'security' => [
'type' => Schema::DT_VARCHAR128
],
'trueSec' => [
'type' => Schema::DT_FLOAT,
'nullable' => false,
'default' => 1
],
'securityStatus' => [
'type' => Schema::DT_DOUBLE,
'nullable' => false,
'default' => 1
],
'securityClass' => [
'type' => Schema::DT_VARCHAR128,
],
'effect' => [
'type' => Schema::DT_VARCHAR128
],
'shattered' => [
'type' => Schema::DT_BOOL,
'nullable' => false,
'default' => 0
],
'x' => [
'type' => Schema::DT_BIGINT,
'nullable' => false,
'default' => 0
],
'y' => [
'type' => Schema::DT_BIGINT,
'nullable' => false,
'default' => 0
],
'z' => [
'type' => Schema::DT_BIGINT,
'nullable' => false,
'default' => 0
],
'planets' => [
'has-many' => ['Model\Universe\PlanetModel', 'systemId']
],
'statics' => [
'has-many' => ['Model\Universe\SystemStaticModel', 'systemId']
],
'stargates' => [
'has-many' => ['Model\Universe\StargateModel', 'systemId']
]
];
/**
* get system data
* -> this includes constellation, region, star, planets as well
* @return \stdClass
*/
public function getData(){
$systemData = (object) [];
$systemData->id = $this->_id;
$systemData->name = $this->name;
$systemData->constellation = $this->constellationId->getData();
$systemData->star = $this->starId->getData();
$systemData->security = $this->security;
$systemData->trueSec = $this->trueSec;
$systemData->effect = $this->effect;
$systemData->shattered = $this->shattered;
if( !empty($planetsData = $this->getPlanetsData()) ){
$systemData->planets = $planetsData;
}
if( !empty($staticsData = $this->getStaticsData()) ){
$systemData->statics = $staticsData;
}
if( !empty($stargatesData = $this->getStargatesData()) ){
$systemData->stargates = $stargatesData;
}
return $systemData;
}
/**
* setter for row (un-formatted) trueSec
* @param $secStatus
* @return double
*/
public function set_securityStatus($secStatus){
$secStatus = (double)$secStatus;
// round for trueSec
$positive = ($secStatus > 0);
$trueSec = round($secStatus, 1, PHP_ROUND_HALF_DOWN);
if($positive && $trueSec <= 0){
$trueSec = 0.1;
}
$this->trueSec = $trueSec;
// set 'security' for NON wormhole systems! -> those get updated from csv import
if(!preg_match('/^j\d+$/i', $this->name)){
if($trueSec <= 0){
$security = '0.0';
}elseif($trueSec < 0.5){
$security = 'L';
}else{
$security = 'H';
}
$this->security = $security;
}
return $secStatus;
}
/**
* setter for wormhole effect name
* @param $effect
* @return string|null
*/
public function set_effect($effect){
$effect = (string)$effect;
return $effect ? : null;
}
/**
* setter for positions array (x/y/z)
* @param $position
* @return null
*/
public function set_position($position){
$position = (array)$position;
if(count($position) === 3){
$this->x = $position['x'];
$this->y = $position['y'];
$this->z = $position['z'];
}
return null;
}
/**
* setter for static systems (wormholes)
* -> comma separated string or array
* @param $staticNames
* @return null
*/
public function set_staticNames($staticNames){
$staticNames = array_unique(is_string($staticNames) ? explode(',', $staticNames) : (array)$staticNames);
$this->virtual('staticNames', array_map('strtoupper', $staticNames));
return null;
}
/**
* Event "Hook" function
* return false will stop any further action
* @param self $self
* @param $pkeys
* @throws \Exception\ValidationException
*/
public function afterUpdateEvent($self, $pkeys){
$staticNames = (array)$self->staticNames;
if(
count($staticNames) > 0 && // make sure statics are set. In case a wh system get updated without statics are set
preg_match('/^c\d+$/i', $self->security) // make sure it is a wormhole
){
foreach((array)$self->statics as $static){
if(in_array($static->wormholeId->name, $staticNames)){
unset($staticNames[array_search($static->wormholeId->name, $staticNames)]);
}else{
$static->erase();
}
}
// add new statics
foreach($staticNames as $staticName){
$static = $self->rel('statics');
/**
* @var $wormhole WormholeModel
*/
$wormhole = $static->rel('wormholeId')->getByForeignKey('name', $staticName, ['limit' => 1]);
if( !$wormhole->dry() ){
$static->systemId = $self;
$static->wormholeId = $wormhole;
$static->save();
}
}
}
// build search index
$self->buildIndex();
return parent::afterUpdateEvent($self, $pkeys);
}
/**
* get data from all planets
* @return array
*/
protected function getPlanetsData(){
$planetsData = [];
if($this->planets){
foreach($this->planets as &$planet){
/**
* @var $planet PlanetModel
*/
$planetsData[] = $planet->getData();
}
}
return $planetsData;
}
/**
* get data from all static wormholes
* @return array
*/
protected function getStaticsData(){
$staticsData = [];
if($this->statics){
foreach($this->statics as &$static){
/**
* @var $static SystemStaticModel
*/
$staticsData[] = $static->getData();
}
}
return $staticsData;
}
/**
* get data from all stargates
* @return array
*/
protected function getStargatesData(){
$stargatesData = [];
if($this->stargates){
foreach($this->stargates as &$stargate){
/**
* @var $stargate StargateModel
*/
$stargatesData[] = $stargate->getData();
}
}
return $stargatesData;
}
/**
* @param int $id
* @param string $accessToken
* @param array $additionalOptions
*/
protected function loadData(int $id, string $accessToken = '', array $additionalOptions = []){
$data = self::getF3()->ccpClient->getUniverseSystemData($id);
if(!empty($data)){
/**
* @var $constellation ConstellationModel
*/
$constellation = $this->rel('constellationId');
$constellation->loadById($data['constellationId'], $accessToken, $additionalOptions);
$data['constellationId'] = $constellation;
/**
* @var $star StarModel
*/
$star = $this->rel('starId');
$star->loadById($data['starId'], $accessToken, $additionalOptions);
$data['starId'] = $star;
$this->copyfrom($data, ['id', 'name', 'constellationId', 'starId', 'securityStatus', 'securityClass', 'position']);
$this->save();
}
}
/**
* load planets data for this system
*/
public function loadPlanetsData(){
if( !$this->dry() ){
$data = self::getF3()->ccpClient->getUniverseSystemData($this->_id);
if(!empty($data)){
foreach((array)$data['planets'] as $planetData){
/**
* @var $planet PlanetModel
*/
$planet = $this->rel('planets');
$planet->loadById($planetData->planet_id);
$planet->reset();
}
}
}
}
/**
* load stargates for this system
* -> stargates to destination system which is not in DB get ignored
*/
public function loadStargatesData(){
if( !$this->dry() ){
$data = self::getF3()->ccpClient->getUniverseSystemData($this->_id);
if(!empty($data)){
foreach((array)$data['stargates'] as $stargateId){
/**
* @var $stargate StargateModel
*/
$stargate = $this->rel('stargates');
$stargate->loadById($stargateId);
$stargate->reset();
}
}
}
}
}

View File

@@ -0,0 +1,76 @@
<?php
/**
* Created by PhpStorm.
* User: exodu
* Date: 18.05.2018
* Time: 17:50
*/
namespace Model\Universe;
use DB\SQL\Schema;
class SystemStaticModel extends BasicUniverseModel {
protected $table = 'system_static';
protected $fieldConf = [
'systemId' => [
'type' => Schema::DT_INT,
'index' => true,
'belongs-to-one' => 'Model\Universe\SystemModel',
'constraint' => [
[
'table' => 'system',
'on-delete' => 'CASCADE'
]
],
'validate' => 'validate_notDry'
],
'wormholeId' => [
'type' => Schema::DT_INT,
'index' => true,
'belongs-to-one' => 'Model\Universe\WormholeModel',
'constraint' => [
[
'table' => 'wormhole',
'on-delete' => 'CASCADE'
]
],
'validate' => 'validate_notDry'
]
];
/**
* No static columns added
* @var bool
*/
protected $addStaticFields = false;
/**
* get static data
* @return null|string
*/
public function getData(){
return $this->wormholeId ? $this->wormholeId->name : null;
}
protected function loadData(int $id, string $accessToken = '', array $additionalOptions = []){}
/**
* overwrites parent
* @param null $db
* @param null $table
* @param null $fields
* @return bool
* @throws \Exception
*/
public static function setup($db=null, $table=null, $fields=null){
$status = parent::setup($db,$table,$fields);
if($status === true){
$status = parent::setMultiColumnIndex(['systemId', 'wormholeId'], true);
}
return $status;
}
}

View File

@@ -8,7 +8,6 @@
namespace Model\Universe;
use DB\SQL\Schema;
class TypeModel extends BasicUniverseModel {
@@ -59,7 +58,8 @@ class TypeModel extends BasicUniverseModel {
'table' => 'group',
'on-delete' => 'CASCADE'
]
]
],
'validate' => 'validate_notDry',
],
'marketGroupId' => [
'type' => Schema::DT_INT,
@@ -85,6 +85,15 @@ class TypeModel extends BasicUniverseModel {
],
'structures' => [
'has-many' => ['Model\Universe\StructureModel', 'typeId']
],
'planets' => [
'has-many' => ['Model\Universe\PlanetModel', 'typeId']
],
'stars' => [
'has-many' => ['Model\Universe\StarModel', 'typeId']
],
'wormholes' => [
'has-many' => ['Model\Universe\WormholeModel', 'typeId']
]
];

View File

@@ -0,0 +1,179 @@
<?php
/**
* Created by PhpStorm.
* User: exodu
* Date: 13.05.2018
* Time: 18:36
*/
namespace Model\Universe;
use DB\SQL\Schema;
class WormholeModel extends BasicUniverseModel {
protected $table = 'wormhole';
public static $enableDataExport = true;
public static $enableDataImport = true;
protected $fieldConf = [
'name' => [
'type' => Schema::DT_VARCHAR128,
'nullable' => false,
'default' => '',
'index' => true,
'unique' => true
],
'typeId' => [
'type' => Schema::DT_INT,
'index' => true,
'belongs-to-one' => 'Model\Universe\TypeModel',
'constraint' => [
[
'table' => 'type',
'on-delete' => 'SET NULL'
]
],
'validate' => 'validate_notDry'
],
'static' => [
'type' => Schema::DT_BOOL,
'nullable' => false,
'default' => 0
],
'security' => [
'type' => Schema::DT_VARCHAR128,
'nullable' => false,
'default' => ''
],
'massTotal' => [
'type' => Schema::DT_BIGINT,
'nullable' => true,
'default' => null
],
'massIndividual' => [
'type' => Schema::DT_BIGINT,
'nullable' => true,
'default' => null
],
'massRegeneration' => [
'type' => Schema::DT_BIGINT,
'nullable' => true,
'default' => null
],
'maxStableTime' => [
'type' => Schema::DT_TINYINT,
'nullable' => true,
'default' => null
],
'signatureStrength' => [
'type' => Schema::DT_FLOAT,
'nullable' => true,
'default' => null
],
'systems' => [
'has-many' => ['Model\Universe\SystemStaticModel', 'wormholeId']
]
];
/**
* setter for typeId
* @param string $typeId
* @return string|int|null
*/
public function set_typeId($typeId){
if(!is_object($typeId)){
/**
* @var $type TypeModel
*/
$type = $this->rel('typeId');
$type->loadById((int)$typeId);
$typeId = $type->dry() ? null : $type->_id;
}
return $typeId;
}
/**
* setter for massTotal
* @param string $mass
* @return int|null
*/
public function set_massTotal($mass){
$mass = (int)$mass;
return $mass ? : null;
}
/**
* setter for massIndividual
* @param string $mass
* @return string|null
*/
public function set_massIndividual($mass){
$mass = (int)$mass;
return $mass ? : null;
}
/**
* setter for massRegeneration
* @param $mass
* @return int|null
*/
public function set_massRegeneration($mass){
$mass = (int)$mass;
return $mass ? : null;
}
/**
* setter for maxStableTime
* @param string $hours
* @return int|null
*/
public function set_maxStableTime($hours){
$hours = (int)$hours;
return $hours ? : null;
}
/**
* setter for signatureStrength
* @param string $strength
* @return float|null
*/
public function set_signatureStrength($strength){
$strength = (float)$strength;
return $strength ? : null;
}
/**
* @param array $fields
* @return bool
*/
public function exportData(array $fields = [
'id', 'name', 'typeId', 'static', 'security', 'massTotal', 'massIndividual',
'massRegeneration', 'maxStableTime', 'signatureStrength']
){
return parent::exportData($fields);
}
protected function loadData(int $id, string $accessToken = '', array $additionalOptions = []){
var_dump('loadData');
var_dump($id);
/*
$data = self::getF3()->ccpClient->getUniverseTypesData($id, $additionalOptions);
if(!empty($data)){
$group = $this->rel('groupId');
$group->loadById($data['groupId'], $accessToken, $additionalOptions);
$data['groupId'] = $group;
$this->copyfrom($data);
$this->save();
} */
}
protected function loadDataByKey(string $key, $value){
var_dump('loadData');
var_dump($key);
var_dump($value);
}
}

View File

@@ -72,3 +72,6 @@ FOREIGN_KEY_CHECKS = ON
NODE = 6.0
NPM = 3.10.0
[REQUIREMENTS.DATA]
STRUCTURES = 33
SHIPS = 490

View File

@@ -14,24 +14,71 @@ define([
splashOverlayClass: 'pf-splash' // class for "splash" overlay
};
/**
* send ajax request for index build
* @param url
* @param requestData
* @param context
* @param callback
*/
let sendRequest = (url, requestData, context, callback) => {
if(requestData.count === 0){
// first iteration
context.target.button('loading');
}
$.ajax({
url: url,
type: 'POST',
dataType: 'json',
data: requestData,
context: context
}).done(function(data){
callback(this, data);
}).fail(function( jqXHR, status, error) {
let reason = status + ' ' + error;
Util.showNotify({title: jqXHR.status + ': Failed. Please retry', text: reason, type: 'warning'});
this.target.button('reset');
});
};
/**
* set page observer
*/
let setPageObserver = () => {
let body = $('body');
// collapse ---------------------------------------
// collapse ---------------------------------------------------------------------------------------------------
body.find('[data-toggle="collapse"]').css({cursor: 'pointer'}).on('click', function(){
$(this).find('.pf-animate-rotate').toggleClass('right');
});
// buttons ----------------------------------------
// buttons ----------------------------------------------------------------------------------------------------
// exclude "download" && "navigation" buttons
body.find('.btn').not('.navbar-fixed-bottom .btn').not('[href^="?export"]').on('click', function(e){
body.find('.btn')
.not('.navbar-fixed-bottom .btn')
.not('[data-action="clearIndex"]')
.not('[data-action="buildIndex"]')
.not('[href^="?export"]').on('click', function(e){
$('.' + config.splashOverlayClass).showSplashOverlay();
});
// tooltips ---------------------------------------
// build/clear index buttons ----------------------------------------------------------------------------------
// clear index buttons ----------------------------------------------------------------------------------------
body.find('.btn[data-action="buildIndex"], .btn[data-action="clearIndex"]').on('click', function(e){
e.preventDefault();
let element = $(this);
let url = '/api/setup/' + element.attr('data-action');
sendRequest(url, {
type: element.attr('data-type'),
count: 0
}, {
target: element,
url: url
}, updateIndexCount);
});
// tooltips ---------------------------------------------------------------------------------------------------
body.initTooltips();
// change url (remove logout parameter)
@@ -40,6 +87,42 @@ define([
}
};
/**
* update data count label for "indexed data"
* @param context
* @param responseData
*/
let updateIndexCount = (context, responseData) => {
let countElement = context.target.closest('.row').children().eq(1).find('kbd');
countElement.text(responseData.countBuildAll + '/' + responseData.countAll);
countElement.removeClass('txt-color-success txt-color-danger txt-color-warning');
if(responseData.countBuildAll >=responseData.countAll){
countElement.addClass('txt-color-success');
}else if(responseData.countBuildAll > 0){
countElement.addClass('txt-color-warning');
}else{
countElement.addClass('txt-color-danger');
}
context.target.find('.btn-progress').html('&nbsp;&nbsp;' + responseData.progress + '%').css('width', responseData.progress + '%');
// send next chunk of rows -> import only
if(
context.target.attr('data-action') === 'buildIndex' &&
responseData.countBuildAll < responseData.countAll
){
sendRequest(context.url, {
type: responseData.type,
count: responseData.count
}, {
target: context.target,
url: context.url
}, updateIndexCount);
}else{
context.target.button('reset');
}
};
/**
* perform a basic check if Clients (browser) can connect to the webSocket server
*/
@@ -165,10 +248,10 @@ define([
*/
$(function(){
// show app information in browser console --------
// show app information in browser console --------------------------------------------------------------------
Util.showVersionInfo();
// hide splash loading animation ------------------
// hide splash loading animation ------------------------------------------------------------------------------
$('.' + config.splashOverlayClass).hideSplashOverlay();
setPageObserver();

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -101,7 +101,7 @@ define(['jquery'], function($) {
4: 'M609 - C4',
5: 'L614 - C5',
6: 'S804 - C6',
7: 'F135 - Thera'
7: 'F353 - Thera'
},
6: { // ORE
1: 'Ordinary Perimeter Deposit', //*

View File

@@ -14,24 +14,71 @@ define([
splashOverlayClass: 'pf-splash' // class for "splash" overlay
};
/**
* send ajax request for index build
* @param url
* @param requestData
* @param context
* @param callback
*/
let sendRequest = (url, requestData, context, callback) => {
if(requestData.count === 0){
// first iteration
context.target.button('loading');
}
$.ajax({
url: url,
type: 'POST',
dataType: 'json',
data: requestData,
context: context
}).done(function(data){
callback(this, data);
}).fail(function( jqXHR, status, error) {
let reason = status + ' ' + error;
Util.showNotify({title: jqXHR.status + ': Failed. Please retry', text: reason, type: 'warning'});
this.target.button('reset');
});
};
/**
* set page observer
*/
let setPageObserver = () => {
let body = $('body');
// collapse ---------------------------------------
// collapse ---------------------------------------------------------------------------------------------------
body.find('[data-toggle="collapse"]').css({cursor: 'pointer'}).on('click', function(){
$(this).find('.pf-animate-rotate').toggleClass('right');
});
// buttons ----------------------------------------
// buttons ----------------------------------------------------------------------------------------------------
// exclude "download" && "navigation" buttons
body.find('.btn').not('.navbar-fixed-bottom .btn').not('[href^="?export"]').on('click', function(e){
body.find('.btn')
.not('.navbar-fixed-bottom .btn')
.not('[data-action="clearIndex"]')
.not('[data-action="buildIndex"]')
.not('[href^="?export"]').on('click', function(e){
$('.' + config.splashOverlayClass).showSplashOverlay();
});
// tooltips ---------------------------------------
// build/clear index buttons ----------------------------------------------------------------------------------
// clear index buttons ----------------------------------------------------------------------------------------
body.find('.btn[data-action="buildIndex"], .btn[data-action="clearIndex"]').on('click', function(e){
e.preventDefault();
let element = $(this);
let url = '/api/setup/' + element.attr('data-action');
sendRequest(url, {
type: element.attr('data-type'),
count: 0
}, {
target: element,
url: url
}, updateIndexCount);
});
// tooltips ---------------------------------------------------------------------------------------------------
body.initTooltips();
// change url (remove logout parameter)
@@ -40,6 +87,42 @@ define([
}
};
/**
* update data count label for "indexed data"
* @param context
* @param responseData
*/
let updateIndexCount = (context, responseData) => {
let countElement = context.target.closest('.row').children().eq(1).find('kbd');
countElement.text(responseData.countBuildAll + '/' + responseData.countAll);
countElement.removeClass('txt-color-success txt-color-danger txt-color-warning');
if(responseData.countBuildAll >=responseData.countAll){
countElement.addClass('txt-color-success');
}else if(responseData.countBuildAll > 0){
countElement.addClass('txt-color-warning');
}else{
countElement.addClass('txt-color-danger');
}
context.target.find('.btn-progress').html('&nbsp;&nbsp;' + responseData.progress + '%').css('width', responseData.progress + '%');
// send next chunk of rows -> import only
if(
context.target.attr('data-action') === 'buildIndex' &&
responseData.countBuildAll < responseData.countAll
){
sendRequest(context.url, {
type: responseData.type,
count: responseData.count
}, {
target: context.target,
url: context.url
}, updateIndexCount);
}else{
context.target.button('reset');
}
};
/**
* perform a basic check if Clients (browser) can connect to the webSocket server
*/
@@ -165,10 +248,10 @@ define([
*/
$(function(){
// show app information in browser console --------
// show app information in browser console --------------------------------------------------------------------
Util.showVersionInfo();
// hide splash loading animation ------------------
// hide splash loading animation ------------------------------------------------------------------------------
$('.' + config.splashOverlayClass).hideSplashOverlay();
setPageObserver();

View File

@@ -591,7 +591,7 @@
<tr>
<check if="{{ @tableData.fieldConf }}">
<true>
<td class="text-center" data-target=".{{ @tableName }}_col" data-toggle="collapse" aria-expanded="false" aria-controls="{{ @tableName }}_col">
<td class="text-center" data-target=".{{ @dbInformation.info.name }}_{{ @tableName }}_col" data-toggle="collapse" aria-expanded="false" aria-controls="{{ @dbInformation.info.name }}_{{ @tableName }}_col">
<i class="fas fa-fw fa-chevron-right pf-animate-rotate"></i>
</td>
</true>
@@ -613,12 +613,12 @@
</td>
<td></td>
<td class="text-center">
<check if="{{ @tableData.empty === true }}">
<check if="{{ @tableData.rows <= 0 }}">
<true>
<i class="fas fa-fw fa-battery-empty txt-color txt-color-warning" title="table empty"></i>
</true>
<false>
<i class="fas fa-fw fa-battery-full" title="table filled"></i>
<i class="fas fa-fw fa-battery-full" title="{{ @tableData.rows }} rows"></i>
</false>
</check>
</td>
@@ -638,7 +638,7 @@
{* Show Columns *}
<check if="{{ @tableData.fieldConf }}">
<tr class="{{ @tableName }}_col collapse">
<tr class="{{ @dbInformation.info.name }}_{{ @tableName }}_col collapse">
<td class="text-center bg-color bg-color-tealDarkest">
<i class="fas fa-fw fa-hashtag"></i>
</td>
@@ -652,7 +652,7 @@
</tr>
<repeat group="{{ @tableData.fieldConf }}" key="{{ @columnName }}" value="{{ @columnData }}" counter="{{ @countCol }}">
<check if="{{ @columnData.type }}">
<tr class="{{ @tableName }}_col collapse">
<tr class="{{ @dbInformation.info.name }}_{{ @tableName }}_col collapse">
<td class="text-right bg-color bg-color-tealDarker">{{@countCol}}.</td>
<td class="bg-color bg-color-tealDarker">{{ @columnName }}</td>
<td class="bg-color bg-color-tealDarker text-center">
@@ -750,7 +750,7 @@
{* Show Foreign Keys *}
<check if="{{ @tableData.foreignKeys }}">
<tr class="{{ @tableName }}_col collapse">
<tr class="{{ @dbInformation.info.name }}_{{ @tableName }}_col collapse">
<td class="text-center bg-color bg-color-tealDarkest">
<i class="fas fa-fw fa-hashtag"></i>
</td>
@@ -763,7 +763,7 @@
<td class="bg-color bg-color-tealDarkest"></td>
</tr>
<repeat group="{{ @tableData.foreignKeys }}" value="{{ @keyData }}" counter="{{ @countForeignKey }}">
<tr class="{{ @tableName }}_col collapse">
<tr class="{{ @dbInformation.info.name }}_{{ @tableName }}_col collapse">
<td class="text-center bg-color bg-color-tealDarker">{{@countForeignKey}}.</td>
<td class="bg-color bg-color-tealDarker" colspan="6">{{ @keyData.keyName }}</td>
<td class="bg-color bg-color-tealDarker text-center">
@@ -921,29 +921,33 @@
</div>
<div class="panel-body no-padding text-left">
<repeat group="{{ @indexInformation }}" key="{{ @model }}" value="{{ @indexData }}">
<repeat group="{{ @indexInformation }}" key="{{ @type }}" value="{{ @indexData }}">
<div class="row">
<div class="col-xs-7">
<span class="btn disabled btn-fake">
{{ @indexData.table }}
</span>
<span class="btn disabled btn-fake pull-right">
<check if="{{ @indexData.count }}">
<true>
<kbd class="txt-color txt-color-success">{{ @indexData.count }} rows</kbd>
</true>
<false>
<kbd class="txt-color txt-color-danger">0 rows</kbd>
</false>
</check>
</span>
<div class="col-xs-4">
<span class="btn disabled btn-fake">{{ @indexData.label }}</span>
</div>
<div class="col-xs-3 text-right">
<check if="{{ @indexData.countBuild >= @indexData.countAll }}">
<kbd class="txt-color txt-color-success">{{ @indexData.countBuild }}/{{ @indexData.countAll }}</kbd>
</check>
<check if="{{ @indexData.countBuild > 0 && @indexData.countBuild < @indexData.countAll}}">
<kbd class="txt-color txt-color-warning">{{ @indexData.countBuild }}/{{ @indexData.countAll }}</kbd>
</check>
<check if="{{ @indexData.countBuild <= 0 }}">
<kbd class="txt-color txt-color-danger">{{ @indexData.countBuild }}/{{ @indexData.countAll }}</kbd>
</check>
<check if="{{ @indexData.tooltip }}">
<i class="fas fa-fw fa-sm fa-question-circle pf-help-light" title="{{ @indexData.tooltip }}"></i>
</check>
</div>
<div class="col-xs-5">
<div class="btn-group btn-group-justified">
<repeat group="{{ @indexData.task }}" key="{{ @taskKey }}" value="{{ @taskData }}">
<a href="?action={{ @taskData.action }}&model={{ @model }}#pf-setup-administration" class="btn {{ @taskData.btn }} {{ @taskData.btn=='btn-default' && !@indexData.count ?'disabled':'' }} {{ @dbWarnings ?'disabled':'' }}" role="button">
<i class="fas fa-fw {{ @taskData.icon }}"></i> {{ @taskData.label }}
<a href="?action={{ @taskData.action }}&model={{ @type }}#pf-setup-administration"
data-type="{{ @type }}" data-action="{{ @taskData.action }}"
data-loading-text="<i class='fas fa-sync fa-spin'></i>&nbsp;&nbsp;wait&hellip;<div class='btn-progress'></div>" autocomplete="off"
class="btn {{ @taskData.btn }} {{ @taskData.btn=='btn-default' && !@indexData.countBuild ?'disabled':'' }} {{ @dbWarnings ?'disabled':'' }}" role="button">
<i class="fas {{ @taskData.icon }}"></i>&nbsp;&nbsp;{{ @taskData.label }}
</a>
</repeat>
</div>
@@ -980,7 +984,7 @@
<span class="btn btn-default disabled btn-fake">
Redis cache
</span>
<a href="?action=clearCache#pf-setup-administration" class="btn btn-warning" role="button">
<a href="?action=clearCache#pf-setup-administration" class="btn btn-danger" role="button">
<i class="fas fa-fw fa-times"></i> Clear all keys
</a>
</div>
@@ -1000,7 +1004,7 @@
</check>
</kbd>
</span>
<a href="?action=clearCache#pf-setup-administration" class="btn btn-warning {{ (@cacheSize.data) ?'':'disabled' }}" role="button">
<a href="?action=clearCache#pf-setup-administration" class="btn btn-danger {{ (@cacheSize.data) ?'':'disabled' }}" role="button">
<i class="fas fa-fw fa-times"></i> Delete files
</a>
</div>
@@ -1037,7 +1041,7 @@
<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="?action=invalidateCookies#pf-setup-administration" class="btn btn-warning" role="button">
<a href="?action=invalidateCookies#pf-setup-administration" class="btn btn-danger" role="button">
<i class="fas fa-fw fa-times"></i> Clear authentication data
</a>
</div>

View File

@@ -68,7 +68,7 @@ fieldset[disabled]{
}
// form fields with icons groups (stacked icons) ==================================================
// form fields with icons groups (stacked icons) ======================================================================
.input-icon-left:not(.input-icon-right){
.fa-stack:first-child{
left: 14px;
@@ -105,16 +105,38 @@ fieldset[disabled]{
}
}
// "fake" button (no user interaction) ============================================================
.btn-fake{
border: none;
text-align: left;
cursor: default;
opacity: 1 !important;
color: $gray-light !important;
background-color: $gray !important;
// buttons ============================================================================================================
.btn{
// "fake" button (no user interaction)
&.btn-fake{
border: none;
text-align: left;
cursor: default;
opacity: 1 !important;
color: $gray-light !important;
background-color: $gray !important;
}
// progress bar inside button
.btn-progress{
position: absolute;
display: block;
height: 100%;
background-color: rgba($green, .2 );
width: 0;
top: 0;
left: 0;
overflow: hidden;
line-height: 30px;
color: $orange-light;
font-size: 10px;
text-align: left;
@include transition( width 0.1s linear);
}
}
// drag&drop zone
.pf-form-dropzone{
border: 2px dashed $gray-darker;