- new UI option for "delete expired connections", #219
- new cronjob for "delete expired connections", #219 - fixed "not updating" map changes, closed #357 - improved caching strategy for DB models (file cache) - improved "map sharing"
This commit is contained in:
@@ -1,5 +1,8 @@
|
||||
; Global Framework Config
|
||||
|
||||
[SERVER]
|
||||
SERVER_NAME = PATHFINDER
|
||||
|
||||
[globals]
|
||||
; Default Verbosity level of the stack trace.
|
||||
; Assign values between 0 to 3 for increasing verbosity levels. Check (environment.ini) config for overwriting
|
||||
|
||||
18
app/cron.ini
18
app/cron.ini
@@ -5,20 +5,26 @@ web = TRUE
|
||||
|
||||
[CRON.presets]
|
||||
; run every minute
|
||||
instant = * * * * *
|
||||
instant = * * * * *
|
||||
|
||||
; run on EVE downtime 11:00 GMT/UTC
|
||||
downtime = 0 11 * * *
|
||||
; 12 times per hour (each 5min)
|
||||
fiveMinutes = */5 * * * *
|
||||
|
||||
; 6 times per hour (each 10min)
|
||||
sixthHour = */10 * * * *
|
||||
tenMinutes = */10 * * * *
|
||||
|
||||
; 2 times per hour (each 30min)
|
||||
halfHour = */30 * * * *
|
||||
halfHour = */30 * * * *
|
||||
|
||||
; run on EVE downtime 11:00 GMT/UTC
|
||||
downtime = 0 11 * * *
|
||||
|
||||
[CRON.jobs]
|
||||
; delete expired connections (e.g. EOL)
|
||||
deleteConnections = Cron\MapUpdate->deleteConnections, @fiveMinutes
|
||||
|
||||
; delete character log data
|
||||
deleteLogData = Cron\CharacterUpdate->deleteLogData, @sixthHour
|
||||
deleteLogData = Cron\CharacterUpdate->deleteLogData, @tenMinutes
|
||||
|
||||
; delete expired signatures
|
||||
deleteSignatures = Cron\MapUpdate->deleteSignatures, @halfHour
|
||||
|
||||
@@ -1074,11 +1074,16 @@ class Cortex extends Cursor {
|
||||
if ($this->emit('beforeerase')===false)
|
||||
return false;
|
||||
if ($this->fieldConf) {
|
||||
foreach($this->fieldConf as $field => $conf)
|
||||
if (isset($conf['has-many']) &&
|
||||
$conf['has-many']['hasRel']=='has-many')
|
||||
$this->set($field,null);
|
||||
$this->save();
|
||||
$changed = false;
|
||||
foreach($this->fieldConf as $field => $conf){
|
||||
if (isset($conf['has-many']) && $conf['has-many']['hasRel']=='has-many'){
|
||||
$this->set($field,null);
|
||||
$changed = true;
|
||||
}
|
||||
}
|
||||
if($changed){
|
||||
$this->save();
|
||||
}
|
||||
}
|
||||
$this->mapper->erase();
|
||||
$this->emit('aftererase');
|
||||
|
||||
@@ -374,28 +374,34 @@ class Map extends Controller\AccessController {
|
||||
|
||||
// share map between characters -> set access
|
||||
if(isset($formData['mapCharacters'])){
|
||||
// remove character corporation (re-add later)
|
||||
$accessCharacters = array_diff($formData['mapCharacters'], [$activeCharacter->_id]);
|
||||
|
||||
// avoid abuse -> respect share limits
|
||||
$accessCharacters = array_slice( $formData['mapCharacters'], 0, $f3->get('PATHFINDER.MAP.PRIVATE.MAX_SHARED') );
|
||||
$maxShared = max($f3->get('PATHFINDER.MAP.PRIVATE.MAX_SHARED') - 1, 0);
|
||||
$accessCharacters = array_slice($accessCharacters, 0, $maxShared);
|
||||
|
||||
// clear map access. In case something has removed from access list
|
||||
$map->clearAccess();
|
||||
if($accessCharacters){
|
||||
// clear map access. In case something has removed from access list
|
||||
$map->clearAccess();
|
||||
|
||||
/**
|
||||
* @var $tempCharacter Model\CharacterModel
|
||||
*/
|
||||
$tempCharacter = Model\BasicModel::getNew('CharacterModel');
|
||||
/**
|
||||
* @var $tempCharacter Model\CharacterModel
|
||||
*/
|
||||
$tempCharacter = Model\BasicModel::getNew('CharacterModel');
|
||||
|
||||
foreach($accessCharacters as $characterId){
|
||||
$tempCharacter->getById( (int)$characterId );
|
||||
foreach($accessCharacters as $characterId){
|
||||
$tempCharacter->getById( (int)$characterId );
|
||||
|
||||
if(
|
||||
!$tempCharacter->dry() &&
|
||||
$tempCharacter->shared == 1 // check if map shared is enabled
|
||||
){
|
||||
$map->setAccess($tempCharacter);
|
||||
if(
|
||||
!$tempCharacter->dry() &&
|
||||
$tempCharacter->shared == 1 // check if map shared is enabled
|
||||
){
|
||||
$map->setAccess($tempCharacter);
|
||||
}
|
||||
|
||||
$tempCharacter->reset();
|
||||
}
|
||||
|
||||
$tempCharacter->reset();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -411,28 +417,34 @@ class Map extends Controller\AccessController {
|
||||
|
||||
// share map between corporations -> set access
|
||||
if(isset($formData['mapCorporations'])){
|
||||
// remove character corporation (re-add later)
|
||||
$accessCorporations = array_diff($formData['mapCorporations'], [$corporation->_id]);
|
||||
|
||||
// avoid abuse -> respect share limits
|
||||
$accessCorporations = array_slice( $formData['mapCorporations'], 0, $f3->get('PATHFINDER.MAP.CORPORATION.MAX_SHARED') );
|
||||
$maxShared = max($f3->get('PATHFINDER.MAP.CORPORATION.MAX_SHARED') - 1, 0);
|
||||
$accessCorporations = array_slice($accessCorporations, 0, $maxShared);
|
||||
|
||||
// clear map access. In case something has removed from access list
|
||||
$map->clearAccess();
|
||||
if($accessCorporations){
|
||||
// clear map access. In case something has removed from access list
|
||||
$map->clearAccess();
|
||||
|
||||
/**
|
||||
* @var $tempCorporation Model\CorporationModel
|
||||
*/
|
||||
$tempCorporation = Model\BasicModel::getNew('CorporationModel');
|
||||
/**
|
||||
* @var $tempCorporation Model\CorporationModel
|
||||
*/
|
||||
$tempCorporation = Model\BasicModel::getNew('CorporationModel');
|
||||
|
||||
foreach($accessCorporations as $corporationId){
|
||||
$tempCorporation->getById( (int)$corporationId );
|
||||
foreach($accessCorporations as $corporationId){
|
||||
$tempCorporation->getById( (int)$corporationId );
|
||||
|
||||
if(
|
||||
!$tempCorporation->dry() &&
|
||||
$tempCorporation->shared == 1 // check if map shared is enabled
|
||||
){
|
||||
$map->setAccess($tempCorporation);
|
||||
if(
|
||||
!$tempCorporation->dry() &&
|
||||
$tempCorporation->shared == 1 // check if map shared is enabled
|
||||
){
|
||||
$map->setAccess($tempCorporation);
|
||||
}
|
||||
|
||||
$tempCorporation->reset();
|
||||
}
|
||||
|
||||
$tempCorporation->reset();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -448,30 +460,35 @@ class Map extends Controller\AccessController {
|
||||
|
||||
// share map between alliances -> set access
|
||||
if(isset($formData['mapAlliances'])){
|
||||
// remove character alliance (re-add later)
|
||||
$accessAlliances = array_diff($formData['mapAlliances'], [$alliance->_id]);
|
||||
|
||||
// avoid abuse -> respect share limits
|
||||
$accessAlliances = array_slice( $formData['mapAlliances'], 0, $f3->get('PATHFINDER.MAP.ALLIANCE.MAX_SHARED') );
|
||||
$maxShared = max($f3->get('PATHFINDER.MAP.ALLIANCE.MAX_SHARED') - 1, 0);
|
||||
$accessAlliances = array_slice($accessAlliances, 0, $maxShared);
|
||||
|
||||
// clear map access. In case something has removed from access list
|
||||
$map->clearAccess();
|
||||
if($accessAlliances){
|
||||
// clear map access. In case something has removed from access list
|
||||
$map->clearAccess();
|
||||
|
||||
/**
|
||||
* @var $tempAlliance Model\AllianceModel
|
||||
*/
|
||||
$tempAlliance = Model\BasicModel::getNew('AllianceModel');
|
||||
/**
|
||||
* @var $tempAlliance Model\AllianceModel
|
||||
*/
|
||||
$tempAlliance = Model\BasicModel::getNew('AllianceModel');
|
||||
|
||||
foreach($accessAlliances as $allianceId){
|
||||
$tempAlliance->getById( (int)$allianceId );
|
||||
foreach($accessAlliances as $allianceId){
|
||||
$tempAlliance->getById( (int)$allianceId );
|
||||
|
||||
if(
|
||||
!$tempAlliance->dry() &&
|
||||
$tempAlliance->shared == 1 // check if map shared is enabled
|
||||
){
|
||||
$map->setAccess($tempAlliance);
|
||||
if(
|
||||
!$tempAlliance->dry() &&
|
||||
$tempAlliance->shared == 1 // check if map shared is enabled
|
||||
){
|
||||
$map->setAccess($tempAlliance);
|
||||
}
|
||||
|
||||
$tempAlliance->reset();
|
||||
}
|
||||
|
||||
$tempAlliance->reset();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// the alliance of the current user should always have access
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
|
||||
namespace cron;
|
||||
use DB;
|
||||
use Model;
|
||||
|
||||
class MapUpdate {
|
||||
|
||||
@@ -62,6 +63,48 @@ class MapUpdate {
|
||||
$log->write( sprintf(self::LOG_TEXT_MAPS, __FUNCTION__, $deletedMapsCount) );
|
||||
}
|
||||
|
||||
/**
|
||||
* delete expired connections (EOL connections)
|
||||
* >> php index.php "/cron/deleteConnections"
|
||||
* @param \Base $f3
|
||||
*/
|
||||
function deleteConnections(\Base $f3){
|
||||
$eolExpire = (int)$f3->get('PATHFINDER.CACHE.EXPIRE_CONNECTIONS_EOL');
|
||||
|
||||
if($eolExpire > 0){
|
||||
$pfDB = DB\Database::instance()->getDB('PF');
|
||||
|
||||
$sql = "SELECT
|
||||
`con`.`id`
|
||||
FROM
|
||||
`connection` `con` INNER JOIN
|
||||
`map` ON
|
||||
`map`.`id` = `con`.`mapId`
|
||||
WHERE
|
||||
`map`.`deleteExpiredConnections` = :deleteExpiredConnections AND
|
||||
TIMESTAMPDIFF(SECOND, `con`.`eolUpdated`, NOW() ) > :expire_time
|
||||
";
|
||||
|
||||
$connectionsData = $pfDB->exec($sql, [
|
||||
'deleteExpiredConnections' => 1,
|
||||
'expire_time' => $eolExpire
|
||||
]);
|
||||
|
||||
if($connectionsData){
|
||||
/**
|
||||
* @var $connection Model\ConnectionModel
|
||||
*/
|
||||
$connection = Model\BasicModel::getNew('ConnectionModel');
|
||||
foreach($connectionsData as $data){
|
||||
$connection->getById( (int)$data['id'] );
|
||||
if( !$connection->dry() ){
|
||||
$connection->erase();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* delete all expired signatures on "inactive" systems
|
||||
* >> php index.php "/cron/deleteSignatures"
|
||||
@@ -84,7 +127,6 @@ class MapUpdate {
|
||||
|
||||
$pfDB->exec($sqlDeleteExpiredSignatures, ['lifetime' => $signatureExpire]);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -49,9 +49,7 @@ class AllianceMapModel extends BasicModel {
|
||||
* see parent
|
||||
*/
|
||||
public function clearCacheData(){
|
||||
parent::clearCacheData();
|
||||
|
||||
// clear map cache as well
|
||||
// clear map cache
|
||||
$this->mapId->clearCacheData();
|
||||
}
|
||||
|
||||
|
||||
@@ -41,7 +41,7 @@ class AllianceModel extends BasicModel {
|
||||
|
||||
/**
|
||||
* get all alliance data
|
||||
* @return array
|
||||
* @return \stdClass
|
||||
*/
|
||||
public function getData(){
|
||||
$allianceData = (object) [];
|
||||
|
||||
@@ -92,6 +92,11 @@ abstract class BasicModel extends \DB\Cortex {
|
||||
*/
|
||||
protected $fieldChanges = [];
|
||||
|
||||
/**
|
||||
* default TTL for getData(); cache
|
||||
*/
|
||||
const DEFAULT_CACHE_TTL = 120;
|
||||
|
||||
public function __construct($db = NULL, $table = NULL, $fluid = NULL, $ttl = 0){
|
||||
|
||||
$this->addStaticFieldConfig();
|
||||
@@ -105,7 +110,6 @@ abstract class BasicModel extends \DB\Cortex {
|
||||
|
||||
$this->afterinsert(function($self, $pkeys){
|
||||
$self->afterInsertEvent($self, $pkeys);
|
||||
$self->clearCacheData();
|
||||
});
|
||||
|
||||
// update events ------------------------------------------------------------------------------------
|
||||
@@ -115,7 +119,6 @@ abstract class BasicModel extends \DB\Cortex {
|
||||
|
||||
$this->afterupdate( function($self, $pkeys){
|
||||
$self->afterUpdateEvent($self, $pkeys);
|
||||
$self->clearCacheData();
|
||||
});
|
||||
|
||||
// erase events -------------------------------------------------------------------------------------
|
||||
@@ -318,6 +321,14 @@ abstract class BasicModel extends \DB\Cortex {
|
||||
return $valid;
|
||||
}
|
||||
|
||||
/**
|
||||
* get key for for all objects in this table
|
||||
* @return string
|
||||
*/
|
||||
private function getTableCacheKey(){
|
||||
return $this->dataCacheKeyPrefix .'.' . strtoupper($this->table);
|
||||
}
|
||||
|
||||
/**
|
||||
* get the cache key for this model
|
||||
* ->do not set a key if the model is not saved!
|
||||
@@ -329,14 +340,12 @@ abstract class BasicModel extends \DB\Cortex {
|
||||
|
||||
// set a model unique cache key if the model is saved
|
||||
if( $this->id > 0){
|
||||
$cacheKey = $this->getTableCacheKey();
|
||||
|
||||
// check if there is a given key prefix
|
||||
// -> if not, use the standard key.
|
||||
// this is useful for caching multiple data sets according to one row entry
|
||||
|
||||
$cacheKey = $this->dataCacheKeyPrefix;
|
||||
$cacheKey .= '.' . strtoupper($this->table);
|
||||
|
||||
if($dataCacheTableKeyPrefix){
|
||||
if( !empty($dataCacheTableKeyPrefix) ){
|
||||
$cacheKey .= '.' . $dataCacheTableKeyPrefix . '_';
|
||||
}else{
|
||||
$cacheKey .= '.ID_';
|
||||
@@ -353,13 +362,14 @@ abstract class BasicModel extends \DB\Cortex {
|
||||
* @return \stdClass|null
|
||||
*/
|
||||
protected function getCacheData($dataCacheKeyPrefix = ''){
|
||||
|
||||
$cacheKey = $this->getCacheKey($dataCacheKeyPrefix);
|
||||
$cacheData = null;
|
||||
|
||||
// table cache exists
|
||||
// -> check cache for this row data
|
||||
$cacheKey = $this->getCacheKey($dataCacheKeyPrefix);
|
||||
|
||||
if( !is_null($cacheKey) ){
|
||||
$f3 = self::getF3();
|
||||
|
||||
if( $f3->exists($cacheKey) ){
|
||||
$cacheData = $f3->get( $cacheKey );
|
||||
}
|
||||
@@ -374,15 +384,14 @@ abstract class BasicModel extends \DB\Cortex {
|
||||
* @param string $dataCacheKeyPrefix
|
||||
* @param int $data_ttl
|
||||
*/
|
||||
public function updateCacheData($cacheData, $dataCacheKeyPrefix = '', $data_ttl = 300){
|
||||
|
||||
public function updateCacheData($cacheData, $dataCacheKeyPrefix = '', $data_ttl = self::DEFAULT_CACHE_TTL){
|
||||
$cacheDataTmp = (array)$cacheData;
|
||||
|
||||
// check if data should be cached
|
||||
// and cacheData is not empty
|
||||
if(
|
||||
$data_ttl > 0 &&
|
||||
!empty( $cacheDataTmp )
|
||||
!empty($cacheDataTmp)
|
||||
){
|
||||
$cacheKey = $this->getCacheKey($dataCacheKeyPrefix);
|
||||
|
||||
@@ -394,13 +403,30 @@ abstract class BasicModel extends \DB\Cortex {
|
||||
|
||||
/**
|
||||
* unset the getData() cache for this object
|
||||
* -> see also clearCacheDataWithPrefix(), for more information
|
||||
*/
|
||||
public function clearCacheData(){
|
||||
$cacheKey = $this->getCacheKey();
|
||||
$this->clearCache($cacheKey);
|
||||
}
|
||||
|
||||
if( !is_null($cacheKey) ){
|
||||
/**
|
||||
* unset object cached data by prefix
|
||||
* -> primarily used by object cache with multiple data caches
|
||||
* @param string $dataCacheKeyPrefix
|
||||
*/
|
||||
public function clearCacheDataWithPrefix($dataCacheKeyPrefix = ''){
|
||||
$cacheKey = $this->getCacheKey($dataCacheKeyPrefix);
|
||||
$this->clearCache($cacheKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* unset object cached data (if exists)
|
||||
* @param $cacheKey
|
||||
*/
|
||||
private function clearCache($cacheKey){
|
||||
if( !empty($cacheKey) ){
|
||||
$f3 = self::getF3();
|
||||
|
||||
if( $f3->exists($cacheKey) ){
|
||||
$f3->clear($cacheKey);
|
||||
}
|
||||
|
||||
@@ -16,14 +16,6 @@ class CharacterLogModel extends BasicModel {
|
||||
|
||||
protected $table = 'character_log';
|
||||
|
||||
/**
|
||||
* caching for relational data
|
||||
* -> 5s matches REST API - Expire: Header-Data
|
||||
* for "Location" calls
|
||||
* @var int
|
||||
*/
|
||||
protected $rel_ttl = 5;
|
||||
|
||||
protected $fieldConf = [
|
||||
'active' => [
|
||||
'type' => Schema::DT_BOOL,
|
||||
@@ -203,6 +195,45 @@ class CharacterLogModel extends BasicModel {
|
||||
return $systemId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Event "Hook" function
|
||||
* return false will stop any further action
|
||||
* @param self $self
|
||||
* @param $pkeys
|
||||
*/
|
||||
public function afterInsertEvent($self, $pkeys){
|
||||
$self->clearCacheData();
|
||||
}
|
||||
|
||||
/**
|
||||
* Event "Hook" function
|
||||
* return false will stop any further action
|
||||
* @param self $self
|
||||
* @param $pkeys
|
||||
*/
|
||||
public function afterUpdateEvent($self, $pkeys){
|
||||
$self->clearCacheData();
|
||||
}
|
||||
|
||||
/**
|
||||
* Event "Hook" function
|
||||
* can be overwritten
|
||||
* @param self $self
|
||||
* @param $pkeys
|
||||
*/
|
||||
public function afterEraseEvent($self, $pkeys){
|
||||
$self->clearCacheData();
|
||||
}
|
||||
|
||||
/**
|
||||
* see parent
|
||||
*/
|
||||
public function clearCacheData(){
|
||||
// clear character "LOG" cache
|
||||
// -> character data without "LOG" has not changed!
|
||||
$this->characterId->clearCacheDataWithPrefix(CharacterModel::DATA_CACHE_KEY_LOG);
|
||||
}
|
||||
|
||||
/**
|
||||
* update session data for active character
|
||||
* @param int $systemId
|
||||
|
||||
@@ -49,9 +49,7 @@ class CharacterMapModel extends BasicModel {
|
||||
* see parent
|
||||
*/
|
||||
public function clearCacheData(){
|
||||
parent::clearCacheData();
|
||||
|
||||
// clear map cache as well
|
||||
// clear map cache
|
||||
$this->mapId->clearCacheData();
|
||||
}
|
||||
|
||||
|
||||
@@ -16,6 +16,11 @@ class CharacterModel extends BasicModel {
|
||||
|
||||
protected $table = 'character';
|
||||
|
||||
/**
|
||||
* cache key prefix for getData(); result WITH log data
|
||||
*/
|
||||
const DATA_CACHE_KEY_LOG = 'LOG';
|
||||
|
||||
protected $fieldConf = [
|
||||
'lastLogin' => [
|
||||
'type' => Schema::DT_TIMESTAMP,
|
||||
@@ -107,15 +112,15 @@ class CharacterModel extends BasicModel {
|
||||
$cacheKeyModifier = '';
|
||||
|
||||
// check if there is cached data
|
||||
// -> IMPORTANT: $addCharacterLogData is optional! -> therefore we need 2 cache keys!
|
||||
if($addCharacterLogData){
|
||||
$cacheKeyModifier = strtoupper($this->table) . '_LOG';
|
||||
$cacheKeyModifier = self::DATA_CACHE_KEY_LOG;
|
||||
}
|
||||
|
||||
$characterData = $this->getCacheData($cacheKeyModifier);
|
||||
|
||||
if(is_null($characterData)){
|
||||
|
||||
// no cached character data found
|
||||
|
||||
$characterData = (object) [];
|
||||
$characterData->id = $this->id;
|
||||
$characterData->name = $this->name;
|
||||
@@ -140,7 +145,7 @@ class CharacterModel extends BasicModel {
|
||||
// max caching time for a system
|
||||
// the cached date has to be cleared manually on any change
|
||||
// this includes system, connection,... changes (all dependencies)
|
||||
$this->updateCacheData($characterData, $cacheKeyModifier, 10);
|
||||
$this->updateCacheData($characterData, $cacheKeyModifier);
|
||||
}
|
||||
|
||||
return $characterData;
|
||||
@@ -180,6 +185,43 @@ class CharacterModel extends BasicModel {
|
||||
return $accessToken;
|
||||
}
|
||||
|
||||
/**
|
||||
* Event "Hook" function
|
||||
* @param self $self
|
||||
* @param $pkeys
|
||||
*/
|
||||
public function afterInsertEvent($self, $pkeys){
|
||||
$self->clearCacheData();
|
||||
}
|
||||
|
||||
/**
|
||||
* Event "Hook" function
|
||||
* @param self $self
|
||||
* @param $pkeys
|
||||
*/
|
||||
public function afterUpdateEvent($self, $pkeys){
|
||||
$self->clearCacheData();
|
||||
}
|
||||
|
||||
/**
|
||||
* Event "Hook" function
|
||||
* @param self $self
|
||||
* @param $pkeys
|
||||
*/
|
||||
public function afterEraseEvent($self, $pkeys){
|
||||
$self->clearCacheData();
|
||||
}
|
||||
|
||||
/**
|
||||
* see parent
|
||||
*/
|
||||
public function clearCacheData(){
|
||||
parent::clearCacheData();
|
||||
|
||||
// clear data with "log" as well!
|
||||
parent::clearCacheDataWithPrefix(self::DATA_CACHE_KEY_LOG);
|
||||
}
|
||||
|
||||
/**
|
||||
* check whether this character has already a user assigned to it
|
||||
* @return bool
|
||||
|
||||
@@ -224,8 +224,7 @@ class ConnectionModel extends BasicModel{
|
||||
* @param $pkeys
|
||||
*/
|
||||
public function afterInsertEvent($self, $pkeys){
|
||||
parent::afterInsertEvent($self, $pkeys);
|
||||
|
||||
$self->clearCacheData();
|
||||
$self->logActivity('connectionCreate');
|
||||
}
|
||||
|
||||
@@ -236,8 +235,7 @@ class ConnectionModel extends BasicModel{
|
||||
* @param $pkeys
|
||||
*/
|
||||
public function afterUpdateEvent($self, $pkeys){
|
||||
parent::afterUpdateEvent($self, $pkeys);
|
||||
|
||||
$self->clearCacheData();
|
||||
$self->logActivity('connectionUpdate');
|
||||
}
|
||||
|
||||
@@ -248,8 +246,7 @@ class ConnectionModel extends BasicModel{
|
||||
* @param $pkeys
|
||||
*/
|
||||
public function afterEraseEvent($self, $pkeys){
|
||||
parent::afterUpdateEvent($self, $pkeys);
|
||||
|
||||
$self->clearCacheData();
|
||||
$self->logActivity('connectionDelete');
|
||||
}
|
||||
|
||||
@@ -302,9 +299,6 @@ class ConnectionModel extends BasicModel{
|
||||
* see parent
|
||||
*/
|
||||
public function clearCacheData(){
|
||||
parent::clearCacheData();
|
||||
|
||||
// clear map cache as well
|
||||
$this->mapId->clearCacheData();
|
||||
}
|
||||
|
||||
|
||||
@@ -49,9 +49,7 @@ class CorporationMapModel extends BasicModel {
|
||||
* see parent
|
||||
*/
|
||||
public function clearCacheData(){
|
||||
parent::clearCacheData();
|
||||
|
||||
// clear map cache as well
|
||||
// clear map cache
|
||||
$this->mapId->clearCacheData();
|
||||
}
|
||||
|
||||
|
||||
@@ -46,7 +46,7 @@ class CorporationModel extends BasicModel {
|
||||
|
||||
/**
|
||||
* get all cooperation data
|
||||
* @return array
|
||||
* @return \stdClass
|
||||
*/
|
||||
public function getData(){
|
||||
$cooperationData = (object) [];
|
||||
|
||||
@@ -55,6 +55,11 @@ class MapModel extends BasicModel {
|
||||
'nullable' => false,
|
||||
'default' => ''
|
||||
],
|
||||
'deleteExpiredConnections' => [
|
||||
'type' => Schema::DT_BOOL,
|
||||
'nullable' => false,
|
||||
'default' => 1
|
||||
],
|
||||
'systems' => [
|
||||
'has-many' => ['Model\SystemModel', 'mapId']
|
||||
],
|
||||
@@ -134,6 +139,7 @@ class MapModel extends BasicModel {
|
||||
$mapData->id = $this->id;
|
||||
$mapData->name = $this->name;
|
||||
$mapData->icon = $this->icon;
|
||||
$mapData->deleteExpiredConnections = $this->deleteExpiredConnections;
|
||||
$mapData->created = strtotime($this->created);
|
||||
$mapData->updated = strtotime($this->updated);
|
||||
|
||||
@@ -194,12 +200,39 @@ class MapModel extends BasicModel {
|
||||
// max caching time for a map
|
||||
// the cached date has to be cleared manually on any change
|
||||
// this includes system, connection,... changes (all dependencies)
|
||||
$this->updateCacheData($mapDataAll, '', 300);
|
||||
$this->updateCacheData($mapDataAll);
|
||||
}
|
||||
|
||||
return $mapDataAll;
|
||||
}
|
||||
|
||||
/**
|
||||
* Event "Hook" function
|
||||
* @param self $self
|
||||
* @param $pkeys
|
||||
*/
|
||||
public function afterInsertEvent($self, $pkeys){
|
||||
$self->clearCacheData();
|
||||
}
|
||||
|
||||
/**
|
||||
* Event "Hook" function
|
||||
* @param self $self
|
||||
* @param $pkeys
|
||||
*/
|
||||
public function afterUpdateEvent($self, $pkeys){
|
||||
$self->clearCacheData();
|
||||
}
|
||||
|
||||
/**
|
||||
* Event "Hook" function
|
||||
* @param self $self
|
||||
* @param $pkeys
|
||||
*/
|
||||
public function afterEraseEvent($self, $pkeys){
|
||||
$self->clearCacheData();
|
||||
}
|
||||
|
||||
/**
|
||||
* get blank system model pre-filled with default SDE data
|
||||
* -> check for "inactive" systems on this map first!
|
||||
@@ -531,7 +564,7 @@ class MapModel extends BasicModel {
|
||||
|
||||
/**
|
||||
* get data for all characters that are currently online "viewing" this map
|
||||
* -> the result of this function is cached!
|
||||
* -> The result of this function is cached!
|
||||
* @return \stdClass[]
|
||||
*/
|
||||
private function getCharactersData(){
|
||||
|
||||
@@ -277,7 +277,7 @@ class SystemModel extends BasicModel {
|
||||
// max caching time for a system
|
||||
// the cached date has to be cleared manually on any change
|
||||
// this includes system, connection,... changes (all dependencies)
|
||||
$this->updateCacheData($systemData, '', 300);
|
||||
$this->updateCacheData($systemData);
|
||||
}
|
||||
|
||||
return $systemData;
|
||||
@@ -358,19 +358,16 @@ class SystemModel extends BasicModel {
|
||||
|
||||
/**
|
||||
* Event "Hook" function
|
||||
* return false will stop any further action
|
||||
* @param self $self
|
||||
* @param $pkeys
|
||||
*/
|
||||
public function afterInsertEvent($self, $pkeys){
|
||||
parent::afterInsertEvent($self, $pkeys);
|
||||
|
||||
$self->clearCacheData();
|
||||
$self->logActivity('systemCreate');
|
||||
}
|
||||
|
||||
/**
|
||||
* Event "Hook" function
|
||||
* can be overwritten
|
||||
* return false will stop any further action
|
||||
* @param self $self
|
||||
* @param $pkeys
|
||||
@@ -397,12 +394,11 @@ class SystemModel extends BasicModel {
|
||||
|
||||
/**
|
||||
* Event "Hook" function
|
||||
* return false will stop any further action
|
||||
* @param self $self
|
||||
* @param $pkeys
|
||||
*/
|
||||
public function afterUpdateEvent($self, $pkeys){
|
||||
parent::afterUpdateEvent($self, $pkeys);
|
||||
$self->clearCacheData();
|
||||
|
||||
// check if rally point mail should be send
|
||||
if(
|
||||
@@ -418,13 +414,11 @@ class SystemModel extends BasicModel {
|
||||
|
||||
/**
|
||||
* Event "Hook" function
|
||||
* can be overwritten
|
||||
* @param self $self
|
||||
* @param $pkeys
|
||||
*/
|
||||
public function afterEraseEvent($self, $pkeys){
|
||||
parent::afterUpdateEvent($self, $pkeys);
|
||||
|
||||
$self->clearCacheData();
|
||||
$self->logActivity('systemDelete');
|
||||
}
|
||||
|
||||
|
||||
@@ -183,8 +183,6 @@ class SystemSignatureModel extends BasicModel {
|
||||
* @param $pkeys
|
||||
*/
|
||||
public function afterInsertEvent($self, $pkeys){
|
||||
parent::afterInsertEvent($self, $pkeys);
|
||||
|
||||
$self->logActivity('signatureCreate');
|
||||
}
|
||||
|
||||
@@ -195,8 +193,6 @@ class SystemSignatureModel extends BasicModel {
|
||||
* @param $pkeys
|
||||
*/
|
||||
public function afterUpdateEvent($self, $pkeys){
|
||||
parent::afterUpdateEvent($self, $pkeys);
|
||||
|
||||
$self->logActivity('signatureUpdate');
|
||||
}
|
||||
|
||||
@@ -207,8 +203,6 @@ class SystemSignatureModel extends BasicModel {
|
||||
* @param $pkeys
|
||||
*/
|
||||
public function afterEraseEvent($self, $pkeys){
|
||||
parent::afterUpdateEvent($self, $pkeys);
|
||||
|
||||
$self->logActivity('signatureDelete');
|
||||
}
|
||||
|
||||
|
||||
@@ -119,6 +119,8 @@ CHARACTER_LOG = 300
|
||||
CONSTELLATION_SYSTEMS = 1728000
|
||||
; max expire time. Expired cache files will be deleted by cronjob (seconds) (default: 10d)
|
||||
EXPIRE_MAX = 864000
|
||||
; expire time for EOL (end of life) connections (seconds) (default: 4h + 15min)
|
||||
EXPIRE_CONNECTIONS_EOL = 15300
|
||||
; expire time for signatures (inactive systems) (seconds) (default 3d)
|
||||
EXPIRE_SIGNATURES = 259200
|
||||
|
||||
|
||||
@@ -1406,7 +1406,7 @@ define([
|
||||
var moduleData = {
|
||||
id: config.mapContextMenuId,
|
||||
items: [
|
||||
{icon: 'fa-info', action: 'info', text: 'info'},
|
||||
{icon: 'fa-street-view', action: 'info', text: 'information'},
|
||||
{icon: 'fa-plus', action: 'add_system', text: 'add system'},
|
||||
{icon: 'fa-object-ungroup', action: 'select_all', text: 'select all'},
|
||||
{icon: 'fa-filter', action: 'filter_scope', text: 'filter scope', subitems: [
|
||||
|
||||
@@ -279,9 +279,9 @@ define([
|
||||
$('<a>', {
|
||||
class: 'list-group-item',
|
||||
href: '#'
|
||||
}).html(' Status').prepend(
|
||||
}).html(' Information').prepend(
|
||||
$('<i>',{
|
||||
class: 'fa fa-info fa-fw'
|
||||
class: 'fa fa-street-view fa-fw'
|
||||
})
|
||||
).on('click', function(){
|
||||
$(document).triggerMenuEvent('ShowMapInfo');
|
||||
@@ -292,7 +292,7 @@ define([
|
||||
$('<a>', {
|
||||
class: 'list-group-item',
|
||||
href: '#'
|
||||
}).html(' Map config').prepend(
|
||||
}).html(' Configuration').prepend(
|
||||
$('<i>',{
|
||||
class: 'fa fa-gears fa-fw'
|
||||
})
|
||||
|
||||
@@ -20,6 +20,8 @@ define([
|
||||
dialogMapSettingsContainerId: 'pf-map-dialog-settings', // id for the "settings" container
|
||||
dialogMapDownloadContainerId: 'pf-map-dialog-download', // id for the "download" container
|
||||
|
||||
deleteExpiredConnectionsId: 'pf-map-dialog-delete-connections', // id for "deleteExpiredConnections" checkbox
|
||||
|
||||
characterSelectId: 'pf-map-dialog-character-select', // id for "character" select
|
||||
corporationSelectId: 'pf-map-dialog-corporation-select', // id for "corporation" select
|
||||
allianceSelectId: 'pf-map-dialog-alliance-select', // id for "alliance" select
|
||||
@@ -103,6 +105,7 @@ define([
|
||||
var accessCharacter = [];
|
||||
var accessCorporation = [];
|
||||
var accessAlliance = [];
|
||||
var deleteExpiredConnections = true;
|
||||
|
||||
if(mapData !== false){
|
||||
// set current map information
|
||||
@@ -115,6 +118,8 @@ define([
|
||||
accessCharacter = mapData.config.access.character;
|
||||
accessCorporation = mapData.config.access.corporation;
|
||||
accessAlliance = mapData.config.access.alliance;
|
||||
|
||||
deleteExpiredConnections = mapData.config.deleteExpiredConnections;
|
||||
}
|
||||
|
||||
// render main dialog -----------------------------------------------------
|
||||
@@ -144,6 +149,9 @@ define([
|
||||
hideDownloadTab: hideDownloadTab,
|
||||
|
||||
// settings tab --------------
|
||||
deleteExpiredConnectionsId : config.deleteExpiredConnectionsId,
|
||||
deleteExpiredConnections: deleteExpiredConnections,
|
||||
|
||||
characterSelectId: config.characterSelectId,
|
||||
corporationSelectId: config.corporationSelectId,
|
||||
allianceSelectId: config.allianceSelectId,
|
||||
@@ -207,10 +215,10 @@ define([
|
||||
var selectField = $(this);
|
||||
var selectValues = selectField.val();
|
||||
|
||||
if(selectValues === null){
|
||||
selectField.parents('.form-group').addClass('has-error');
|
||||
}else{
|
||||
if(selectValues.length > 0){
|
||||
selectField.parents('.form-group').removeClass('has-error');
|
||||
}else{
|
||||
selectField.parents('.form-group').addClass('has-error');
|
||||
}
|
||||
});
|
||||
|
||||
@@ -223,12 +231,20 @@ define([
|
||||
var dialogContent = mapInfoDialog.find('.modal-content');
|
||||
dialogContent.showLoadingAnimation();
|
||||
|
||||
var newMapData = {formData: form.getFormValues()};
|
||||
// get form data
|
||||
var formData = form.getFormValues();
|
||||
|
||||
// checkbox fix -> settings tab
|
||||
if( form.find('#' + config.deleteExpiredConnectionsId).length ){
|
||||
formData.deleteExpiredConnections = formData.hasOwnProperty('deleteExpiredConnections') ? parseInt( formData.deleteExpiredConnections ) : 0;
|
||||
}
|
||||
|
||||
var requestData = {formData: formData};
|
||||
|
||||
$.ajax({
|
||||
type: 'POST',
|
||||
url: Init.path.saveMap,
|
||||
data: newMapData,
|
||||
data: requestData,
|
||||
dataType: 'json'
|
||||
}).done(function(responseData){
|
||||
|
||||
|
||||
@@ -139,7 +139,7 @@ define([
|
||||
placeholder: 'System name',
|
||||
allowClear: true,
|
||||
maximumSelectionLength: options.maxSelectionLength,
|
||||
escapeMarkup: function (markup) {
|
||||
escapeMarkup: function(markup){
|
||||
// let our custom formatter work
|
||||
return markup;
|
||||
}
|
||||
@@ -257,12 +257,12 @@ define([
|
||||
dropdownParent: selectElement.parents('.modal-body'),
|
||||
theme: 'pathfinder',
|
||||
minimumInputLength: 3,
|
||||
placeholder: '',
|
||||
placeholder: options.type + ' names',
|
||||
allowClear: false,
|
||||
maximumSelectionLength: options.maxSelectionLength,
|
||||
templateResult: formatResultData,
|
||||
templateSelection: formatSelectionData,
|
||||
escapeMarkup: function (markup) {
|
||||
escapeMarkup: function(markup){
|
||||
// let our custom formatter work
|
||||
return markup;
|
||||
}
|
||||
|
||||
@@ -1469,7 +1469,10 @@ define([
|
||||
|
||||
var currentMapUserData = false;
|
||||
|
||||
if( mapId === parseInt(mapId, 10) ){
|
||||
if(
|
||||
mapId === parseInt(mapId, 10) &&
|
||||
Init.currentMapUserData
|
||||
){
|
||||
// search for a specific map
|
||||
for(var i = 0; i < Init.currentMapUserData.length; i++){
|
||||
if(Init.currentMapUserData[i].config.id === mapId){
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -1406,7 +1406,7 @@ define([
|
||||
var moduleData = {
|
||||
id: config.mapContextMenuId,
|
||||
items: [
|
||||
{icon: 'fa-info', action: 'info', text: 'info'},
|
||||
{icon: 'fa-street-view', action: 'info', text: 'information'},
|
||||
{icon: 'fa-plus', action: 'add_system', text: 'add system'},
|
||||
{icon: 'fa-object-ungroup', action: 'select_all', text: 'select all'},
|
||||
{icon: 'fa-filter', action: 'filter_scope', text: 'filter scope', subitems: [
|
||||
|
||||
@@ -279,9 +279,9 @@ define([
|
||||
$('<a>', {
|
||||
class: 'list-group-item',
|
||||
href: '#'
|
||||
}).html(' Status').prepend(
|
||||
}).html(' Information').prepend(
|
||||
$('<i>',{
|
||||
class: 'fa fa-info fa-fw'
|
||||
class: 'fa fa-street-view fa-fw'
|
||||
})
|
||||
).on('click', function(){
|
||||
$(document).triggerMenuEvent('ShowMapInfo');
|
||||
@@ -292,7 +292,7 @@ define([
|
||||
$('<a>', {
|
||||
class: 'list-group-item',
|
||||
href: '#'
|
||||
}).html(' Map config').prepend(
|
||||
}).html(' Configuration').prepend(
|
||||
$('<i>',{
|
||||
class: 'fa fa-gears fa-fw'
|
||||
})
|
||||
|
||||
@@ -20,6 +20,8 @@ define([
|
||||
dialogMapSettingsContainerId: 'pf-map-dialog-settings', // id for the "settings" container
|
||||
dialogMapDownloadContainerId: 'pf-map-dialog-download', // id for the "download" container
|
||||
|
||||
deleteExpiredConnectionsId: 'pf-map-dialog-delete-connections', // id for "deleteExpiredConnections" checkbox
|
||||
|
||||
characterSelectId: 'pf-map-dialog-character-select', // id for "character" select
|
||||
corporationSelectId: 'pf-map-dialog-corporation-select', // id for "corporation" select
|
||||
allianceSelectId: 'pf-map-dialog-alliance-select', // id for "alliance" select
|
||||
@@ -103,6 +105,7 @@ define([
|
||||
var accessCharacter = [];
|
||||
var accessCorporation = [];
|
||||
var accessAlliance = [];
|
||||
var deleteExpiredConnections = true;
|
||||
|
||||
if(mapData !== false){
|
||||
// set current map information
|
||||
@@ -115,6 +118,8 @@ define([
|
||||
accessCharacter = mapData.config.access.character;
|
||||
accessCorporation = mapData.config.access.corporation;
|
||||
accessAlliance = mapData.config.access.alliance;
|
||||
|
||||
deleteExpiredConnections = mapData.config.deleteExpiredConnections;
|
||||
}
|
||||
|
||||
// render main dialog -----------------------------------------------------
|
||||
@@ -144,6 +149,9 @@ define([
|
||||
hideDownloadTab: hideDownloadTab,
|
||||
|
||||
// settings tab --------------
|
||||
deleteExpiredConnectionsId : config.deleteExpiredConnectionsId,
|
||||
deleteExpiredConnections: deleteExpiredConnections,
|
||||
|
||||
characterSelectId: config.characterSelectId,
|
||||
corporationSelectId: config.corporationSelectId,
|
||||
allianceSelectId: config.allianceSelectId,
|
||||
@@ -207,10 +215,10 @@ define([
|
||||
var selectField = $(this);
|
||||
var selectValues = selectField.val();
|
||||
|
||||
if(selectValues === null){
|
||||
selectField.parents('.form-group').addClass('has-error');
|
||||
}else{
|
||||
if(selectValues.length > 0){
|
||||
selectField.parents('.form-group').removeClass('has-error');
|
||||
}else{
|
||||
selectField.parents('.form-group').addClass('has-error');
|
||||
}
|
||||
});
|
||||
|
||||
@@ -223,12 +231,20 @@ define([
|
||||
var dialogContent = mapInfoDialog.find('.modal-content');
|
||||
dialogContent.showLoadingAnimation();
|
||||
|
||||
var newMapData = {formData: form.getFormValues()};
|
||||
// get form data
|
||||
var formData = form.getFormValues();
|
||||
|
||||
// checkbox fix -> settings tab
|
||||
if( form.find('#' + config.deleteExpiredConnectionsId).length ){
|
||||
formData.deleteExpiredConnections = formData.hasOwnProperty('deleteExpiredConnections') ? parseInt( formData.deleteExpiredConnections ) : 0;
|
||||
}
|
||||
|
||||
var requestData = {formData: formData};
|
||||
|
||||
$.ajax({
|
||||
type: 'POST',
|
||||
url: Init.path.saveMap,
|
||||
data: newMapData,
|
||||
data: requestData,
|
||||
dataType: 'json'
|
||||
}).done(function(responseData){
|
||||
|
||||
|
||||
@@ -139,7 +139,7 @@ define([
|
||||
placeholder: 'System name',
|
||||
allowClear: true,
|
||||
maximumSelectionLength: options.maxSelectionLength,
|
||||
escapeMarkup: function (markup) {
|
||||
escapeMarkup: function(markup){
|
||||
// let our custom formatter work
|
||||
return markup;
|
||||
}
|
||||
@@ -257,12 +257,12 @@ define([
|
||||
dropdownParent: selectElement.parents('.modal-body'),
|
||||
theme: 'pathfinder',
|
||||
minimumInputLength: 3,
|
||||
placeholder: '',
|
||||
placeholder: options.type + ' names',
|
||||
allowClear: false,
|
||||
maximumSelectionLength: options.maxSelectionLength,
|
||||
templateResult: formatResultData,
|
||||
templateSelection: formatSelectionData,
|
||||
escapeMarkup: function (markup) {
|
||||
escapeMarkup: function(markup){
|
||||
// let our custom formatter work
|
||||
return markup;
|
||||
}
|
||||
|
||||
@@ -1469,7 +1469,10 @@ define([
|
||||
|
||||
var currentMapUserData = false;
|
||||
|
||||
if( mapId === parseInt(mapId, 10) ){
|
||||
if(
|
||||
mapId === parseInt(mapId, 10) &&
|
||||
Init.currentMapUserData
|
||||
){
|
||||
// search for a specific map
|
||||
for(var i = 0; i < Init.currentMapUserData.length; i++){
|
||||
if(Init.currentMapUserData[i].config.id === mapId){
|
||||
|
||||
@@ -40,7 +40,24 @@
|
||||
{{^hideSettingsTab}}
|
||||
<div role="tabpanel" class="tab-pane fade {{#openTabSettings}}in active{{/openTabSettings}}" id="{{dialogMapSettingsContainerId}}">
|
||||
<form role="form" class="form-horizontal">
|
||||
<h4><i class="fa fa-share-alt fa-fw"></i> Share settings</h4>
|
||||
|
||||
<h4 class="pf-dynamic-area">Configuration</h4>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-sm-6">
|
||||
<div class="form-group">
|
||||
<div class="col-sm-offset-1 col-sm-11">
|
||||
<div class="col-sm-12 col-xs-6 checkbox checkbox-primary" title="remove expired EOL connections">
|
||||
<input id="{{deleteExpiredConnectionsId}}" name="deleteExpiredConnections" value="1" type="checkbox" {{#deleteExpiredConnections}}checked{{/deleteExpiredConnections}}>
|
||||
<label for="{{deleteExpiredConnectionsId}}">Auto delete expired connections</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-6"></div>
|
||||
</div>
|
||||
|
||||
<h4 class="pf-dynamic-area">Share settings</h4>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-sm-11">
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<ul class="nav navbar-nav {{dialogNavigationClass}}" role="tablist">
|
||||
<li class="active">
|
||||
<a role="tab" data-toggle="tab" data-name="infoSummary" href="#{{dialogSummaryContainerId}}">
|
||||
<i class="fa fa-info fa-fw"></i> Info
|
||||
<i class="fa fa-street-view fa-fw"></i> Information
|
||||
</a>
|
||||
</li>
|
||||
<li class="">
|
||||
|
||||
@@ -54,7 +54,7 @@
|
||||
<kbd>right click</kbd> somewhere on the map to open the context menu.
|
||||
</p>
|
||||
<ul class="list-unstyled well" style=" margin-left: 10px;">
|
||||
<li><i class="fa fa-info fa-fw"></i> Show some basic map information</li>
|
||||
<li><i class="fa fa-street-view fa-fw"></i> Show some basic map information</li>
|
||||
<li><i class="fa fa-plus fa-fw"></i> Add a new system at the position, you clicked at</li>
|
||||
<li><i class="fa fa-object-ungroup fa-fw"></i> Select all (unlocked) systems on the map</li>
|
||||
<li><i class="fa fa-filter fa-fw"></i> Filter map connections by a scope <small><a href="#" data-target="#pf-manual-scrollspy-anchor-connection-scope">more</a></small></li>
|
||||
@@ -407,7 +407,7 @@
|
||||
<h4 id="pf-manual-scrollspy-anchor-notification-desktop">Desktop push notifications</h4>
|
||||
<p>
|
||||
<em class="pf-brand">pathfinder</em> supports desktop push notifications. The first time this kind of notification is send to a client, a Browser security question must be confirmed.
|
||||
In order to test desktop push notifications <kbd>click</kbd> the "Notification test" <small>(<i class="fa fa-bullhorn fa-fw"></i>)</small> option in the main menu <small>(<i class="fa fa-bars fa-fw"></i>)</small>.<br>
|
||||
In order to test desktop push notifications <kbd>click</kbd> the "Notification test" <small>(<i class="fa fa-bullhorn fa-fw"></i>)</small> option in the main menu.<br>
|
||||
Events that trigger desktop notification.
|
||||
</p>
|
||||
<ul>
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
.select2-search--inline {
|
||||
.select2-search__field {
|
||||
outline: 0;
|
||||
@include placeholder();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user