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

This commit is contained in:
Exodus4D
2016-04-05 21:31:03 +02:00
parent 7e94ec4889
commit 95119fcd3d
86 changed files with 1940 additions and 3642 deletions

View File

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

View File

@@ -40,7 +40,6 @@ class Connection extends Controller\AccessController{
$activeCharacter = $this->getCharacter();
if($activeCharacter){
$user = $activeCharacter->getUser();
// get map model and check map access
/**
@@ -49,7 +48,7 @@ class Connection extends Controller\AccessController{
$map = Model\BasicModel::getNew('MapModel');
$map->getById( (int)$mapData['id'] );
if( $map->hasAccess($user) ){
if( $map->hasAccess($activeCharacter) ){
$source = $map->getSystem( (int)$connectionData['source'] );
$target = $map->getSystem( (int)$connectionData['target'] );
@@ -57,6 +56,9 @@ class Connection extends Controller\AccessController{
!is_null($source) &&
!is_null($target)
){
/**
* @var $connection Model\ConnectionModel
*/
$connection = Model\BasicModel::getNew('ConnectionModel');
$connection->getById( (int)$connectionData['id'] );
@@ -103,17 +105,20 @@ class Connection extends Controller\AccessController{
$connectionIds = $f3->get('POST.connectionIds');
$activeCharacter = $this->getCharacter();
/**
* @var Model\ConnectionModel $connection
*/
$connection = Model\BasicModel::getNew('ConnectionModel');
foreach($connectionIds as $connectionId){
$connection->getById($connectionId);
$connection->delete( $activeCharacter->getUser() );
if($activeCharacter = $this->getCharacter()){
/**
* @var Model\ConnectionModel $connection
*/
$connection = Model\BasicModel::getNew('ConnectionModel');
foreach($connectionIds as $connectionId){
$connection->getById($connectionId);
$connection->delete( $activeCharacter );
$connection->reset();
$connection->reset();
}
}
echo json_encode([]);
}

View File

@@ -39,10 +39,11 @@ class Map extends Controller\AccessController {
$f3->expire($expireTimeHead);
$initData = [];
$return = (object) [];
$return->error = [];
// static program data ------------------------------------------------
$initData['timer'] = $f3->get('PATHFINDER.TIMER');
$return->timer = $f3->get('PATHFINDER.TIMER');
// get all available map types ----------------------------------------
$mapType = Model\BasicModel::getNew('MapTypeModel');
@@ -59,7 +60,7 @@ class Map extends Controller\AccessController {
$mapTypeData[$rowData->name] = $data;
}
$initData['mapTypes'] = $mapTypeData;
$return->mapTypes = $mapTypeData;
// get all available map scopes ---------------------------------------
$mapScope = Model\BasicModel::getNew('MapScopeModel');
@@ -72,7 +73,7 @@ class Map extends Controller\AccessController {
];
$mapScopeData[$rowData->name] = $data;
}
$initData['mapScopes'] = $mapScopeData;
$return->mapScopes = $mapScopeData;
// get all available system status ------------------------------------
$systemStatus = Model\BasicModel::getNew('SystemStatusModel');
@@ -86,7 +87,7 @@ class Map extends Controller\AccessController {
];
$systemScopeData[$rowData->name] = $data;
}
$initData['systemStatus'] = $systemScopeData;
$return->systemStatus = $systemScopeData;
// get all available system types -------------------------------------
$systemType = Model\BasicModel::getNew('SystemTypeModel');
@@ -99,7 +100,7 @@ class Map extends Controller\AccessController {
];
$systemTypeData[$rowData->name] = $data;
}
$initData['systemType'] = $systemTypeData;
$return->systemType = $systemTypeData;
// get available connection scopes ------------------------------------
$connectionScope = Model\BasicModel::getNew('ConnectionScopeModel');
@@ -113,7 +114,7 @@ class Map extends Controller\AccessController {
];
$connectionScopeData[$rowData->name] = $data;
}
$initData['connectionScopes'] = $connectionScopeData;
$return->connectionScopes = $connectionScopeData;
// get available character status -------------------------------------
$characterStatus = Model\BasicModel::getNew('CharacterStatusModel');
@@ -127,17 +128,33 @@ class Map extends Controller\AccessController {
];
$characterStatusData[$rowData->name] = $data;
}
$initData['characterStatus'] = $characterStatusData;
$return->characterStatus = $characterStatusData;
// get max number of shared entities per map --------------------------
$maxSharedCount = [
'user' => $f3->get('PATHFINDER.MAX_SHARED_USER'),
'character' => $f3->get('PATHFINDER.MAX_SHARED_CHARACTER'),
'corporation' => $f3->get('PATHFINDER.MAX_SHARED_CORPORATION'),
'alliance' => $f3->get('PATHFINDER.MAX_SHARED_ALLIANCE'),
];
$initData['maxSharedCount'] = $maxSharedCount;
$return->maxSharedCount = $maxSharedCount;
echo json_encode($initData);
// get program routes -------------------------------------------------
$return->routes = [
'ssoLogin' => $this->getF3()->alias( 'sso', ['action' => 'requestAuthorization'] )
];
// get SSO error messages that should be shown immediately ------------
// -> e.g. errors while character switch from previous HTTP requests
if( $f3->exists(Controller\Ccp\Sso::SESSION_KEY_SSO_ERROR) ){
$ssoError = (object) [];
$ssoError->type = 'error';
$ssoError->title = 'Login failed';
$ssoError->message = $f3->get(Controller\Ccp\Sso::SESSION_KEY_SSO_ERROR);
$return->error[] = $ssoError;
$f3->clear(Controller\Ccp\Sso::SESSION_KEY_SSO_ERROR);
}
echo json_encode($return);
}
/**
@@ -213,7 +230,6 @@ class Map extends Controller\AccessController {
}
}
foreach($mapData['data']['connections'] as $connectionData){
// check if source and target IDs match with new system ID
if(
@@ -296,7 +312,6 @@ class Map extends Controller\AccessController {
$activeCharacter = $this->getCharacter(0);
if($activeCharacter){
$user = $activeCharacter->getUser();
/**
* @var $map Model\MapModel
@@ -306,7 +321,7 @@ class Map extends Controller\AccessController {
if(
$map->dry() ||
$map->hasAccess($user)
$map->hasAccess($activeCharacter)
){
// new map
$map->setData($formData);
@@ -315,36 +330,36 @@ class Map extends Controller\AccessController {
// save global map access. Depends on map "type"
if($map->isPrivate()){
// share map between users -> set access
if(isset($formData['mapUsers'])){
// share map between characters -> set access
if(isset($formData['mapCharacters'])){
// avoid abuse -> respect share limits
$accessUsers = array_slice( $formData['mapUsers'], 0, $f3->get('PATHFINDER.MAX_SHARED_USER') );
$accessCharacters = array_slice( $formData['mapCharacters'], 0, $f3->get('PATHFINDER.MAX_SHARED_CHARACTER') );
// clear map access. In case something has removed from access list
$map->clearAccess();
/**
* @var $tempUser Model\UserModel
* @var $tempCharacter Model\CharacterModel
*/
$tempUser = Model\BasicModel::getNew('UserModel');
$tempCharacter = Model\BasicModel::getNew('CharacterModel');
foreach($accessUsers as $userId){
$tempUser->getById( (int)$userId );
foreach($accessCharacters as $characterId){
$tempCharacter->getById( (int)$characterId );
if(
!$tempUser->dry() &&
$tempUser->shared == 1 // check if map shared is enabled
!$tempCharacter->dry() &&
$tempCharacter->shared == 1 // check if map shared is enabled
){
$map->setAccess($tempUser);
$map->setAccess($tempCharacter);
}
$tempUser->reset();
$tempCharacter->reset();
}
}
// the current user itself should always have access
// the current character itself should always have access
// just in case he removed himself :)
$map->setAccess($user);
$map->setAccess($activeCharacter);
}elseif($map->isCorporation()){
$corporation = $activeCharacter->getCorporation();
@@ -459,7 +474,7 @@ class Map extends Controller\AccessController {
*/
$map = Model\BasicModel::getNew('MapModel');
$map->getById($mapData['id']);
$map->delete( $activeCharacter->getUser() );
$map->delete( $activeCharacter );
}
echo json_encode([]);
@@ -480,15 +495,15 @@ class Map extends Controller\AccessController {
if($activeCharacter){
$cacheKey = 'user_map_data_' . $activeCharacter->id;
$cacheKey = 'user_map_data_' . $activeCharacter->_id;
// if there is any system/connection change data submitted -> save new data
if(
!$f3->exists($cacheKey) ||
!empty($mapData)
!empty($mapData) ||
!$f3->exists($cacheKey)
){
// get current map data ========================================================
$maps = $activeCharacter->getUser()->getMaps();
$maps = $activeCharacter->getMaps();
// loop all submitted map data that should be saved
// -> currently there will only be ONE map data change submitted -> single loop
@@ -583,7 +598,7 @@ class Map extends Controller\AccessController {
// cache time(s) per user should be equal or less than this function is called
// prevent request flooding
$responseTTL = $f3->get('PATHFINDER.TIMER.UPDATE_SERVER_MAP.DELAY') / 1000;
$responseTTL = (int)$f3->get('PATHFINDER.TIMER.UPDATE_SERVER_MAP.DELAY') / 1000;
$f3->set($cacheKey, $return, $responseTTL);
}else{
@@ -631,17 +646,16 @@ class Map extends Controller\AccessController {
public function updateUserData(\Base $f3){
$return = (object) [];
$return->error = [];
$activeCharacter = $this->getCharacter();
$activeCharacter = $this->getCharacter(0);
if($activeCharacter){
$user = $activeCharacter->getUser();
if( !empty($f3->get('POST.mapIds')) ){
$mapIds = (array)$f3->get('POST.mapIds');
// check if data for specific system is requested
$systemData = (array)$f3->get('POST.systemData');
// update current location
$activeCharacter->updateLog();
$activeCharacter = $activeCharacter->updateLog();
// if data is requested extend the cache key in order to get new data
$requestSystemData = (object) [];
@@ -657,7 +671,7 @@ class Map extends Controller\AccessController {
$cacheKey = 'user_data_' . $tempId . '_' . $requestSystemData->systemId;
if( !$f3->exists($cacheKey) ){
foreach($mapIds as $mapId){
$map = $user->getMap($mapId);
$map = $activeCharacter->getMap($mapId);
if( !is_null($map) ){
$return->mapUserData[] = $map->getUserData();
@@ -677,7 +691,7 @@ class Map extends Controller\AccessController {
// cache time (seconds) should be equal or less than request trigger time
// prevent request flooding
$responseTTL = $f3->get('PATHFINDER.TIMER.UPDATE_SERVER_USER_DATA.DELAY') / 1000;
$responseTTL = (int)$f3->get('PATHFINDER.TIMER.UPDATE_SERVER_USER_DATA.DELAY') / 1000;
// cache response
$f3->set($cacheKey, $return, $responseTTL);
@@ -688,9 +702,10 @@ class Map extends Controller\AccessController {
$return = $f3->get($cacheKey);
}
}
// get current user data -> this should not be cached because each user has different personal data
// even if they have multiple characters using the same map!
$return->userData = $user->getData();
$return->userData = $activeCharacter->getUser()->getData();
}else{
// user logged off
$return->error[] = $this->getLogoutError();

View File

@@ -40,7 +40,7 @@ class Signature extends \Controller\AccessController{
if(!$system->dry()){
// check access
if( $system->hasAccess($activeCharacter->getUser()) ){
if( $system->hasAccess($activeCharacter) ){
$signatureData = $system->getSignaturesData();
}
}
@@ -75,7 +75,6 @@ class Signature extends \Controller\AccessController{
$activeCharacter = $this->getCharacter();
if($activeCharacter){
$user = $activeCharacter->getUser();
/**
* @var Model\SystemModel $system
@@ -95,9 +94,9 @@ class Signature extends \Controller\AccessController{
$signature = null;
if( isset($data['pk']) ){
// try to get system by "primary key"
$signature = $system->getSignatureById($user, (int)$data['pk']);
$signature = $system->getSignatureById($activeCharacter, (int)$data['pk']);
}elseif( isset($data['name']) ){
$signature = $system->getSignatureByName($user, $data['name']);
$signature = $system->getSignatureByName($activeCharacter, $data['name']);
}
if( is_null($signature) ){
@@ -186,7 +185,7 @@ class Signature extends \Controller\AccessController{
$signature = Model\BasicModel::getNew('SystemSignatureModel');
foreach($signatureIds as $signatureId){
$signature->getById($signatureId);
$signature->delete( $activeCharacter->getUser() );
$signature->delete( $activeCharacter );
$signature->reset();
}

View File

@@ -192,7 +192,6 @@ class System extends \Controller\AccessController {
$activeCharacter = $this->getCharacter();
if($activeCharacter){
$user = $activeCharacter->getUser();
$systemData = (array)$postData['systemData'];
$mapData = (array)$postData['mapData'];
@@ -205,7 +204,7 @@ class System extends \Controller\AccessController {
$system = Model\BasicModel::getNew('SystemModel');
$system->getById($systemData['id']);
if( !$system->dry() ){
if( $system->hasAccess($user) ){
if( $system->hasAccess($activeCharacter) ){
// system model found
$systemModel = $system;
}
@@ -219,7 +218,7 @@ class System extends \Controller\AccessController {
$map = Model\BasicModel::getNew('MapModel');
$map->getById($mapData['id']);
if( !$map->dry() ){
if( $map->hasAccess($user) ){
if( $map->hasAccess($activeCharacter) ){
$systemData['mapId'] = $map;
@@ -270,9 +269,9 @@ class System extends \Controller\AccessController {
$systemLogModel = Model\BasicModel::getNew($ModelClass);
// 10min cache (could be up to 1h cache time)
$systemLogModel->getByForeignKey('systemId', $systemId, array(), 60 * 10);
$systemLogModel->getByForeignKey('systemId', $systemId, [], 60 * 10);
if(!$systemLogModel->dry()){
if( !$systemLogModel->dry() ){
$counter = 0;
for( $i = $logEntryCount; $i >= 1; $i--){
$column = 'value' . $i;
@@ -340,17 +339,15 @@ class System extends \Controller\AccessController {
*/
public function delete(\Base $f3){
$systemIds = $f3->get('POST.systemIds');
$activeCharacter = $this->getCharacter();
if($activeCharacter){
$user = $activeCharacter->getUser();
if($activeCharacter = $this->getCharacter()){
/**
* @var Model\SystemModel $system
*/
$system = Model\BasicModel::getNew('SystemModel');
foreach((array)$systemIds as $systemId){
$system->getById($systemId);
$system->delete($user);
$system->delete($activeCharacter);
$system->reset();
}
}

View File

@@ -15,6 +15,10 @@ use DB;
class User extends Controller\Controller{
// captcha specific session keys
const SESSION_CAPTCHA_ACCOUNT_UPDATE = 'SESSION.CAPTCHA.ACCOUNT.UPDATE';
const SESSION_CAPTCHA_ACCOUNT_DELETE = 'SESSION.CAPTCHA.ACCOUNT.DELETE';
// user specific session keys
const SESSION_KEY_USER = 'SESSION.USER';
const SESSION_KEY_USER_ID = 'SESSION.USER.ID';
@@ -36,7 +40,7 @@ class User extends Controller\Controller{
* valid reasons for captcha images
* @var string array
*/
private static $captchaReason = ['createAccount', 'deleteAccount'];
private static $captchaReason = [self::SESSION_CAPTCHA_ACCOUNT_UPDATE, self::SESSION_CAPTCHA_ACCOUNT_DELETE];
/**
* login a valid character
@@ -106,7 +110,7 @@ class User extends Controller\Controller{
'fonts/oxygen-bold-webfont.ttf',
14,
6,
'SESSION.' . $reason,
$reason,
'',
$colorText,
$colorBG
@@ -155,14 +159,12 @@ class User extends Controller\Controller{
$return = (object) [];
$privateSharing = 0;
$corporationSharing = 0;
$allianceSharing = 0;
$activeCharacter = $this->getCharacter();
if($activeCharacter){
$user = $activeCharacter->getUser();
$privateSharing = 0;
$corporationSharing = 0;
$allianceSharing = 0;
// form values
if(isset($data['formData'])){
@@ -181,8 +183,8 @@ class User extends Controller\Controller{
}
}
$user->shared = $privateSharing;
$user->save();
$activeCharacter->shared = $privateSharing;
$activeCharacter = $activeCharacter->save();
// update corp/ally ---------------------------------------------------------------
$corporation = $activeCharacter->getCorporation();
@@ -198,6 +200,7 @@ class User extends Controller\Controller{
$alliance->save();
}
$user = $activeCharacter->getUser();
$return->userData = $user->getData();
}
@@ -205,61 +208,9 @@ class User extends Controller\Controller{
}
/**
* search for a registration key model
* e.g. for new user registration with "invite" feature enabled
* @param $email
* @param $registrationKey
* @return bool|Model\RegistrationKeyModel
* @throws Exception
*/
protected function getRegistrationKey($email, $registrationKey){
$registrationKeyModel = Model\BasicModel::getNew('RegistrationKeyModel');
$registrationKeyModel->load([
'registrationKey = :registrationKey AND
email = :email AND
used = 0 AND
active = 1',
':registrationKey' => $registrationKey,
':email' => $email
]);
if( $registrationKeyModel->dry() ){
return false;
}else{
return $registrationKeyModel;
}
}
/**
* check if there is already an active Key for a mail
* @param $email
* @param bool|false $used
* @return bool|null
* @throws Exception
*/
protected function findRegistrationKey($email, $used = false){
$queryPart = 'email = :email AND active = 1';
if(is_int($used)){
$queryPart .= ' AND used = ' . $used;
}
$registrationKeyModel = Model\BasicModel::getNew('RegistrationKeyModel');
$registrationKeyModels = $registrationKeyModel->find([
$queryPart,
':email' => $email
]);
if( is_object($registrationKeyModels) ){
return $registrationKeyModels;
}else{
return false;
}
}
/**
* save/update user account data
* update user account data
* -> a fresh user automatically generated on first login with a new character
* -> see CREST SSO login
* @param \Base $f3
*/
public function saveAccount(\Base $f3){
@@ -268,183 +219,100 @@ class User extends Controller\Controller{
$return = (object) [];
$return->error = [];
$captcha = $f3->get('SESSION.createAccount');
$captcha = $f3->get(self::SESSION_CAPTCHA_ACCOUNT_UPDATE);
// reset captcha -> forces user to enter new one
$f3->clear('SESSION.createAccount');
$f3->clear(self::SESSION_CAPTCHA_ACCOUNT_UPDATE);
$newUserData = null;
// check for new user
$loginAfterSave = false;
// valid registration key Model is required for new registration
// if "invite" feature is enabled
$registrationKeyModel = false;
if( isset($data['settingsData']) ){
$settingsData = $data['settingsData'];
if( isset($data['formData']) ){
$formData = $data['formData'];
try{
$activeCharacter = $this->getCharacter(0);
$user = $activeCharacter->getUser();
if($activeCharacter = $this->getCharacter(0)){
$user = $activeCharacter->getUser();
// captcha is send -> check captcha
if(
isset($settingsData['captcha']) &&
!empty($settingsData['captcha'])
){
if($settingsData['captcha'] === $captcha){
// change/set sensitive user data requires captcha!
if(is_null($user)){
// check if registration key invite function is enabled
if($f3->get('PATHFINDER.REGISTRATION.INVITE') === 1 ){
$registrationKeyModel = $this->getRegistrationKey( $settingsData['email'], $settingsData['registrationKey'] );
if($registrationKeyModel === false){
throw new Exception\RegistrationException('Registration key invalid', 'registrationKey');
}
}
// new user registration
$user = Model\BasicModel::getNew('UserModel');
$loginAfterSave = true;
// captcha is send -> check captcha ---------------------------------
if(
isset($formData['captcha']) &&
!empty($formData['captcha'])
){
if($formData['captcha'] === $captcha){
// change/set sensitive user data requires captcha!
// set username
if(
isset($settingsData['name']) &&
!empty($settingsData['name'])
isset($formData['name']) &&
!empty($formData['name'])
){
$user->name = $settingsData['name'];
}
}
// change/set email
if(
isset($settingsData['email']) &&
isset($settingsData['email_confirm']) &&
!empty($settingsData['email']) &&
!empty($settingsData['email_confirm']) &&
$settingsData['email'] == $settingsData['email_confirm']
){
$user->email = $settingsData['email'];
}
// change/set password
if(
isset($settingsData['password']) &&
isset($settingsData['password_confirm']) &&
!empty($settingsData['password']) &&
!empty($settingsData['password_confirm']) &&
$settingsData['password'] == $settingsData['password_confirm']
){
$user->password = $settingsData['password'];
}
}else{
// captcha was send but not valid -> return error
$captchaError = (object) [];
$captchaError->type = 'error';
$captchaError->message = 'Captcha does not match';
$return->error[] = $captchaError;
}
}
// saving additional user info requires valid user object (no captcha required)
if($user){
// save API data
if(
isset($settingsData['keyId']) &&
isset($settingsData['vCode']) &&
is_array($settingsData['keyId']) &&
is_array($settingsData['vCode'])
){
// get all existing API models for this user
$apiModels = $user->getAPIs();
foreach($settingsData['keyId'] as $i => $keyId){
$api = null;
// search for existing API model
foreach($apiModels as $key => $apiModel){
if($apiModel->keyId == $keyId){
$api = $apiModel;
// make sure model is up2data -> cast()
$api->cast();
unset($apiModels[$key]);
break;
}
$user->name = $formData['name'];
}
if(is_null($api)){
// new API Key
$api = Model\BasicModel::getNew('UserApiModel');
$api->userId = $user;
// set email
if(
isset($formData['email']) &&
isset($formData['email_confirm']) &&
!empty($formData['email']) &&
!empty($formData['email_confirm']) &&
$formData['email'] == $formData['email_confirm']
){
$user->email = $formData['email'];
}
$api->keyId = $keyId;
$api->vCode = $settingsData['vCode'][$i];
$api->save();
// save/update user model
// this will fail if model validation fails!
$user->save();
$characterCount = $api->updateCharacters();
}else{
// captcha was send but not valid -> return error
$captchaError = (object) [];
$captchaError->type = 'error';
$captchaError->message = 'Captcha does not match';
$return->error[] = $captchaError;
}
}
if($characterCount == 0){
// no characters found -> return warning
$characterError = (object) [];
$characterError->type = 'warning';
$characterError->message = 'API verification failed. No Characters found for KeyId ' . $api->keyId;
$return->error[] = $characterError;
}
// sharing config ---------------------------------------------------
if(isset($formData['share'])){
$privateSharing = 0;
$corporationSharing = 0;
$allianceSharing = 0;
if(isset($formData['privateSharing'])){
$privateSharing = 1;
}
// delete API models that no longer exists
foreach($apiModels as $apiModel){
$apiModel->delete();
if(isset($formData['corporationSharing'])){
$corporationSharing = 1;
}
// get fresh updated user object (API info may have has changed)
//$user = $this->_getUser(0);
}
if(isset($formData['allianceSharing'])){
$allianceSharing = 1;
}
// set main character
if( isset($settingsData['mainCharacterId']) ){
$user->setMainCharacterId((int)$settingsData['mainCharacterId']);
}
// update private/corp/ally
$corporation = $activeCharacter->getCorporation();
$alliance = $activeCharacter->getAlliance();
// check if the user already has a main character
// if not -> save the next best character as main
$mainUserCharacter = $user->getMainUserCharacter();
if(is_object($corporation)){
$corporation->shared = $corporationSharing;
$corporation->save();
}
// set main character if no main character exists
if(is_null($mainUserCharacter)){
$user->setMainCharacterId();
}
if(is_object($alliance)){
$alliance->shared = $allianceSharing;
$alliance->save();
}
// save/update user model
// this will fail if model validation fails!
$user->save();
if(is_object($registrationKeyModel)){
$registrationKeyModel->used = 1;
$registrationKeyModel->save();
}
// log user in (in case he is new
if($loginAfterSave){
$this->logInByData( $user->name, $settingsData['password'] );
// return reroute path
$return->reroute = rtrim(self::getEnvironmentData('URL'), '/') . $this->f3->alias('map');
$activeCharacter->shared = $privateSharing;
$activeCharacter->save();
}
// get fresh updated user object
$newUserData = $user->getData();
}
}catch(Exception\ValidationException $e){
$validationError = (object) [];
$validationError->type = 'error';
@@ -461,109 +329,6 @@ class User extends Controller\Controller{
// return new/updated user data
$return->userData = $newUserData;
}
echo json_encode($return);
}
/**
* send mail with registration key
* -> check INVITE in pathfinder.ini
* @param \Base $f3
* @throws Exception
*/
public function sendInvite(\Base $f3){
$data = $f3->get('POST.settingsData');
$return = (object) [];
// check invite limit
// get handed out key count
$tempRegistrationKeyModel = Model\BasicModel::getNew('RegistrationKeyModel');
$tempRegistrationKeyModels = $tempRegistrationKeyModel->find([ '
email != "" AND
active = 1'
]);
$totalKeys = 0;
if(is_object($tempRegistrationKeyModels)){
$totalKeys = $tempRegistrationKeyModels->count();
}
if(
$f3->get('PATHFINDER.REGISTRATION.INVITE') == 1 &&
$totalKeys < $f3->get('PATHFINDER.REGISTRATION.INVITE_LIMIT')
){
// key limit not reached
if(
isset($data['email']) &&
!empty($data['email'])
){
$email = trim($data['email']);
// check if mail is valid
if( \Audit::instance()->email($email) ){
// new key for this mail is allowed
$registrationKeyModel = $this->findRegistrationKey($email, 0);
if($registrationKeyModel === false){
// check for total number of invites (active and inactive) -> prevent spamming
$allRegistrationKeysByMail = $this->findRegistrationKey($email);
if(
$allRegistrationKeysByMail == false ||
$allRegistrationKeysByMail->count() < 3
){
// get a fresh key
$registrationKeyModel = Model\BasicModel::getNew('RegistrationKeyModel');
$registrationKeyModel->load(['
used = 0 AND
active = 1 AND
email = "" ',
':email' => $email
], ['limit' => 1]);
}else{
$validationError = (object) [];
$validationError->type = 'warning';
$validationError->message = 'The number of keys is limited by Email. You can not get more keys';
$return->error[] = $validationError;
}
}else{
$registrationKeyModel = $registrationKeyModel[0];
}
// send "old" key again or send a new key
if( is_object($registrationKeyModel) ){
$msg = 'Your personal Registration Key: ' . $registrationKeyModel->registrationKey;
$mailController = new MailController();
$status = $mailController->sendInviteKey($email, $msg);
if( $status ){
$registrationKeyModel->email = $email;
$registrationKeyModel->ip = $this->f3->get('IP');
$registrationKeyModel->save();
}
}
}else{
$validationError = (object) [];
$validationError->type = 'error';
$validationError->field = 'email';
$validationError->message = 'Email is not valid';
$return->error[] = $validationError;
}
}
}else{
$validationError = (object) [];
$validationError->type = 'warning';
$validationError->message = 'The pool of beta keys has been exhausted, please try again in a few days/weeks';
$return->error[] = $validationError;
}
echo json_encode($return);
@@ -577,10 +342,10 @@ class User extends Controller\Controller{
$data = $f3->get('POST.formData');
$return = (object) [];
$captcha = $f3->get('SESSION.deleteAccount');
$captcha = $f3->get(self::SESSION_CAPTCHA_ACCOUNT_DELETE);
// reset captcha -> forces user to enter new one
$f3->clear('SESSION.deleteAccount');
$f3->clear(self::SESSION_CAPTCHA_ACCOUNT_DELETE);
if(
isset($data['captcha']) &&
@@ -589,13 +354,8 @@ class User extends Controller\Controller{
){
$activeCharacter = $this->getCharacter(0);
$user = $activeCharacter->getUser();
$validUser = $this->_verifyUser( $user->name, $data['password']);
if(
is_object($validUser) &&
is_object($user) &&
$user->id === $validUser->id
){
if($user){
// send delete account mail
$msg = 'Hello ' . $user->name . ',<br><br>';
$msg .= 'your account data has been successfully deleted.';
@@ -616,12 +376,6 @@ class User extends Controller\Controller{
$this->logOut($f3);
die();
}
}else{
// password does not match current user pw
$passwordError = (object) [];
$passwordError->type = 'error';
$passwordError->message = 'Invalid password';
$return->error[] = $passwordError;
}
}else{
// captcha not valid -> return error

View File

@@ -19,7 +19,9 @@ class AppController extends Controller {
parent::afterroute($f3);
// clear all SSO related temp data
$f3->clear(Ccp\Sso::SESSION_KEY_SSO);
if( $f3->exists(Ccp\Sso::SESSION_KEY_SSO) ){
$f3->clear(Ccp\Sso::SESSION_KEY_SSO);
}
}
/**

View File

@@ -34,6 +34,10 @@ class Sso extends Api\User{
const SESSION_KEY_SSO = 'SESSION.SSO';
const SESSION_KEY_SSO_ERROR = 'SESSION.SSO.ERROR';
const SESSION_KEY_SSO_STATE = 'SESSION.SSO.STATE';
const SESSION_KEY_SSO_CHARACTER_ID = 'SESSION.SSO.CHARACTER.ID';
// cache keys
const CACHE_KEY_LOCATION_DATA = 'CACHED.LOCATION.%s';
// error messages
const ERROR_CCP_SSO_URL = 'Invalid "ENVIRONMENT.[ENVIRONMENT].SSO_CCP_URL" url. %s';
@@ -44,6 +48,9 @@ class Sso extends Api\User{
const ERROR_GET_ENDPOINT = 'Unable to get endpoint data. $s';
const ERROR_FIND_ENDPOINT = 'Unable to find endpoint: %s';
const ERROR_LOGIN_FAILED = 'Failed authentication due to technical problems: %s';
const ERROR_CHARACTER_FORBIDDEN = 'Character "%s" is not authorized to log in';
const ERROR_CHARACTER_MISMATCH = 'The character "%s" you tried to log in, does not match';
const ERROR_SERVICE_TIMEOUT = 'CCP SSO service timeout (%ss). Try again later';
/**
* CREST "Scopes" are used by pathfinder
@@ -62,6 +69,12 @@ class Sso extends Api\User{
* @param \Base $f3
*/
public function requestAuthorization($f3){
$params = $f3->get('GET');
if(isset($params['characterId'])){
// restrict login to this characterId e.g. for character switch
$f3->set(self::SESSION_KEY_SSO_CHARACTER_ID, (int)trim($params['characterId']) );
}
// used for "state" check between request and callback
$state = bin2hex(mcrypt_create_iv(12, MCRYPT_DEV_URANDOM));
@@ -89,6 +102,10 @@ class Sso extends Api\User{
public function callbackAuthorization($f3){
$getParams = (array)$f3->get('GET');
// users can log in either from @login (new user) or @map (existing user) root alias
// -> in case login fails, users should be redirected differently
$authFromMapAlias = false;
if($f3->exists(self::SESSION_KEY_SSO_STATE)){
// check response and validate 'state'
if(
@@ -98,8 +115,15 @@ class Sso extends Api\User{
!empty($getParams['state']) &&
$f3->get(self::SESSION_KEY_SSO_STATE) === $getParams['state']
){
// clear 'state' for new next request
// $requestedCharacterId can be [-1 => add char, 0 => new user, >0 => specific user]
$requiredCharacterId = (int)$f3->get(self::SESSION_KEY_SSO_CHARACTER_ID);
if($requiredCharacterId !== 0){
$authFromMapAlias = true;
}
// clear 'state' for new next login request
$f3->clear(self::SESSION_KEY_SSO_STATE);
$f3->clear(self::SESSION_KEY_SSO_CHARACTER_ID);
$accessData = $this->getCrestAccessData($getParams['code']);
@@ -111,74 +135,91 @@ class Sso extends Api\User{
$verificationCharacterData = $this->verifyCharacterData($accessData->accessToken);
if( !is_null($verificationCharacterData)){
// verification data available. Data is needed for "ownerHash" check
// get character data from CREST
$characterData = $this->getCharacterData($accessData->accessToken);
// check if login is restricted to a characterID
if(
$requiredCharacterId <= 0 ||
$verificationCharacterData->CharacterID === $requiredCharacterId
){
// verification available data. Data is needed for "ownerHash" check
if(isset($characterData->character)){
// add "ownerHash" and CREST tokens
$characterData->character['ownerHash'] = $verificationCharacterData->CharacterOwnerHash;
$characterData->character['crestAccessToken'] = $accessData->accessToken;
$characterData->character['crestRefreshToken'] = $accessData->refreshToken;
// get character data from CREST
$characterData = $this->getCharacterData($accessData->accessToken);
// add/update static character data
$characterModel = $this->updateCharacter($characterData);
if(isset($characterData->character)){
// add "ownerHash" and CREST tokens
$characterData->character['ownerHash'] = $verificationCharacterData->CharacterOwnerHash;
$characterData->character['crestAccessToken'] = $accessData->accessToken;
$characterData->character['crestRefreshToken'] = $accessData->refreshToken;
if( !is_null($characterModel) ){
// check if character is authorized to log in
if($characterModel->isAuthorized()){
// add/update static character data
$characterModel = $this->updateCharacter($characterData);
// character is authorized to log in
// -> update character log (current location,...)
$characterModel = $characterModel->updateLog();
if( !is_null($characterModel) ){
// check if character is authorized to log in
if($characterModel->isAuthorized()){
// check if there is already a user created who owns this char
$user = $characterModel->getUser();
// character is authorized to log in
// -> update character log (current location,...)
$characterModel = $characterModel->updateLog();
if(is_null($user)){
// no user found -> create one and connect to character
/**
* @var Model\UserModel $user
*/
$user = Model\BasicModel::getNew('UserModel');
$user->name = $characterModel->name;
$user->save();
// check if there is already an active user logged in
if($activeCharacter = $this->getCharacter()){
// connect character with current user
$user = $activeCharacter->getUser();
}elseif( is_null( $user = $characterModel->getUser()) ){
// no user found (new character) -> create new user and connect to character
$user = Model\BasicModel::getNew('UserModel');
$user->name = $characterModel->name;
$user->save();
}
/**
* @var Model\UserCharacterModel $userCharactersModel
*/
$userCharactersModel = Model\BasicModel::getNew('UserCharacterModel');
if( is_null($userCharactersModel = $characterModel->userCharacter) ){
$userCharactersModel = Model\BasicModel::getNew('UserCharacterModel');
$userCharactersModel->characterId = $characterModel;
}
// user might have changed
$userCharactersModel->userId = $user;
$userCharactersModel->characterId = $characterModel;
$userCharactersModel->save();
// get updated character model
$characterModel = $userCharactersModel->getCharacter();
}
// login by character
$loginCheck = $this->loginByCharacter($characterModel);
// login by character
$loginCheck = $this->loginByCharacter($characterModel);
if($loginCheck){
// route to "map"
$f3->reroute('@map');
if($loginCheck){
// route to "map"
$f3->reroute('@map');
}else{
$f3->set(self::SESSION_KEY_SSO_ERROR, sprintf(self::ERROR_LOGIN_FAILED, $characterModel->name));
}
}else{
$f3->set(self::SESSION_KEY_SSO_ERROR, sprintf(self::ERROR_LOGIN_FAILED, $characterModel->name));
// character is not authorized to log in
$f3->set(self::SESSION_KEY_SSO_ERROR, sprintf(self::ERROR_CHARACTER_FORBIDDEN, $characterModel->name));
}
}else{
// character is not authorized to log in
$f3->set(self::SESSION_KEY_SSO_ERROR, 'Character "' . $characterModel->name . '" is not authorized to log in.');
}
}
}else{
// characterID is not allowed to login
$f3->set(self::SESSION_KEY_SSO_ERROR, sprintf(self::ERROR_CHARACTER_MISMATCH, $verificationCharacterData->CharacterName));
}
}
}else{
// CREST "accessData" missing (e.g. timeout)
$f3->set(self::SESSION_KEY_SSO_ERROR, sprintf(self::ERROR_SERVICE_TIMEOUT, self::CREST_TIMEOUT));
}
}
}
// on error -> route back to login form
$f3->reroute('@login');
if($authFromMapAlias){
// on error -> route back to map
$f3->reroute('@map');
}else{
// on error -> route back to login form
$f3->reroute('@login');
}
}
/**
@@ -266,16 +307,18 @@ class Sso extends Api\User{
$apiResponse = Lib\Web::instance()->request($verifyAuthCodeUrl, $requestOptions);
if($apiResponse['body']){
$authCodeRequestData = json_decode($apiResponse['body']);
$authCodeRequestData = json_decode($apiResponse['body'], true);
if(isset($authCodeRequestData->access_token)){
// this token is required for endpoints that require Auth
$accessData->accessToken = $authCodeRequestData->access_token;
}
if( !empty($authCodeRequestData) ){
if( isset($authCodeRequestData['access_token']) ){
// this token is required for endpoints that require Auth
$accessData->accessToken = $authCodeRequestData['access_token'];
}
if(isset($authCodeRequestData->refresh_token)){
// this token is used to refresh/get a new access_token when expires
$accessData->refreshToken = $authCodeRequestData->refresh_token;
if(isset($authCodeRequestData['refresh_token'])){
// this token is used to refresh/get a new access_token when expires
$accessData->refreshToken = $authCodeRequestData['refresh_token'];
}
}
}else{
self::getCrestLogger()->write(
@@ -450,25 +493,37 @@ class Sso extends Api\User{
}
/**
* get current character location data
* get current character location data (result is cached!)
* -> solarSystem data where character is currently active
* @param $accessToken
* @return object
* @param int $ttl
* @return array
*/
public function getCharacterLocationData($accessToken){
$endpoints = $this->getEndpoints($accessToken);
$locationData = (object) [];
public function getCharacterLocationData($accessToken, $ttl = 10){
$locationData = [];
$endpoint = $this->walkEndpoint($accessToken, $endpoints, [
'decode',
'character',
'location'
]);
// in addition to the cURL caching (based on cache-control headers,
// the final location data is cached additionally -> speed up
$cacheKey = sprintf(self::CACHE_KEY_LOCATION_DATA, 'TOKEN_' . hash('md5', $accessToken));
if( !empty($endpoint) ){
if(isset($endpoint['solarSystem'])){
$locationData->system = (new Mapper\CrestSystem($endpoint['solarSystem']))->getData();
if( !$this->getF3()->exists($cacheKey) ){
$endpoints = $this->getEndpoints($accessToken);
$endpoint = $this->walkEndpoint($accessToken, $endpoints, [
'decode',
'character',
'location'
]);
if( !empty($endpoint) ){
if(isset($endpoint['solarSystem'])){
$locationData['system'] = (new Mapper\CrestSystem($endpoint['solarSystem']))->getData();
}
}
$this->getF3()->set($cacheKey, $locationData, $ttl);
}else{
$locationData = $this->getF3()->get($cacheKey);
}
return $locationData;

View File

@@ -120,7 +120,7 @@ class Controller {
* @return Model\CharacterModel|null
* @throws \Exception
*/
public function getCharacter($ttl = 5){
public function getCharacter($ttl = 0){
$character = null;
if( $this->getF3()->exists(Api\User::SESSION_KEY_CHARACTER_ID) ){
@@ -170,33 +170,6 @@ class Controller {
}
}
/**
* verifies weather a given username and password is valid
* @param string $userName
* @param string $password
* @return Model\UserModel|null
*/
protected function _verifyUser($userName, $password) {
$validUser = null;
/**
* @var $user \Model\UserModel
*/
$user = Model\BasicModel::getNew('UserModel', 0);
$user->getByName($userName);
// check userName is valid
if( !$user->dry() ){
// check if password is valid
$isValid = $user->verify($password);
if($isValid === true){
$validUser = $user;
}
}
return $validUser;
}
/**
* check weather the page is IGB trusted or not
* @return boolean

View File

@@ -30,17 +30,14 @@ class Setup extends Controller {
'Model\SystemStatusModel',
'Model\SystemNeighbourModel',
'Model\WormholeModel',
'Model\RegistrationKeyModel',
'Model\CharacterStatusModel',
'Model\ConnectionScopeModel',
'Model\UserMapModel',
'Model\CharacterMapModel',
'Model\AllianceMapModel',
'Model\CorporationMapModel',
'Model\UserApiModel',
'Model\UserCharacterModel',
'Model\CharacterModel',
'Model\CharacterLogModel',

View File

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

View File

@@ -166,7 +166,7 @@ class CcpSystemsMapper extends AbstractIterator {
// just value change no key change
$removeOldEntry = false;
$iterator -> next();
$iterator->next();
}else{
// a -> b mapping
$iterator->offsetSet( self::$map[$iterator->key()], $iterator->current() );

View File

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

View File

@@ -8,8 +8,15 @@
namespace Lib;
use controller\LogController;
class Web extends \Web {
const ERROR_DEFAULT_MSG = 'method: \'%s\', url: \'%s\'';
const ERROR_STATUS_UNKNOWN = 'HTTP response - unknown HTTP status: \'%s\'. url: \'%s\'';
const ERROR_TIMEOUT = 'Request timeout \'%ss\'. url: \'%s\'';
/**
* end of line
* @var string
@@ -79,7 +86,7 @@ class Web extends \Web {
$options['method'] . ' '
. $url . ' '
. $headers
).'.url';
) . 'url';
}
/**
@@ -96,11 +103,42 @@ class Web extends \Web {
$result = parent::request($url, $options);
$statusCode = $this->getStatuscodeFromHeaders( $result['headers'] );
if($statusCode == 200){
// request succeeded -> check if response should be cached
if( $ttl = $this->getCacheTimeFromHeaders( $result['headers'] ) ){
$f3->set($hash, $result, $ttl);
}
switch($statusCode){
case 200:
// request succeeded -> check if response should be cached
$ttl = $this->getCacheTimeFromHeaders( $result['headers'] );
if(
$ttl > 0 &&
!empty( json_decode( $result['body'], true ) )
){
$f3->set($hash, $result, $ttl);
}
break;
case 500:
case 501:
case 502:
case 503:
case 504:
case 505:
$f3->error($statusCode, $this->getErrorMessageFromJsonResponse(
$options['method'],
$url,
json_decode($result['body'])
));
break;
case 0:
// timeout
LogController::getLogger('error')->write(
sprintf(self::ERROR_TIMEOUT, $options['timeout'], $url)
);
break;
default:
// unknown status
LogController::getLogger('error')->write(
sprintf(self::ERROR_STATUS_UNKNOWN, $statusCode, $url)
);
break;
}
}else{
$result = $f3->get($hash);
@@ -109,4 +147,21 @@ class Web extends \Web {
return $result;
}
/**
* get error message from response object
* @param string $method
* @param string $url
* @param \stdClass $responseBody
* @return string
*/
protected function getErrorMessageFromJsonResponse($method, $url, $responseBody){
if( empty($responseBody->message) ){
$message = sprintf(self::ERROR_DEFAULT_MSG, $method, $url);
}else{
$message = $responseBody->message . ', url: \'' . $url . '\'';
}
return $message;
}
}

View File

@@ -108,18 +108,27 @@ abstract class BasicModel extends \DB\Cortex {
return;
}
if($key != 'updated'){
if(
!$this->dry() &&
$key != 'updated'
){
if( $this->exists($key) ){
$currentVal = $this->get($key);
// if current value is not a relational object
// and value has changed -> update table col
if(
!is_object($currentVal) &&
$currentVal != $val
){
if(is_object($currentVal)){
if(
is_numeric($val) &&
is_subclass_of($currentVal, 'Model\BasicModel') &&
$currentVal->_id !== (int)$val
){
$this->touch('updated');
}
}elseif($currentVal != $val){
$this->touch('updated');
}
}
}
@@ -247,6 +256,67 @@ abstract class BasicModel extends \DB\Cortex {
return $cacheKey;
}
/**
* get cached data from this model
* @param string $dataCacheKeyPrefix - optional key prefix
* @return \stdClass|null
*/
protected function getCacheData($dataCacheKeyPrefix = ''){
$cacheKey = $this->getCacheKey($dataCacheKeyPrefix);
$cacheData = null;
if( !is_null($cacheKey) ){
$f3 = self::getF3();
if( $f3->exists($cacheKey) ){
$cacheData = $f3->get( $cacheKey );
}
}
return $cacheData;
}
/**
* update/set the getData() cache for this object
* @param $cacheData
* @param string $dataCacheKeyPrefix
* @param int $data_ttl
*/
public function updateCacheData($cacheData, $dataCacheKeyPrefix = '', $data_ttl = 300){
$cacheDataTmp = (array)$cacheData;
// check if data should be cached
// and cacheData is not empty
if(
$data_ttl > 0 &&
!empty( $cacheDataTmp )
){
$cacheKey = $this->getCacheKey($dataCacheKeyPrefix);
if( !is_null($cacheKey) ){
self::getF3()->set($cacheKey, $cacheData, $data_ttl);
}
}
}
/**
* unset the getData() cache for this object
*/
public function clearCacheData(){
$cacheKey = $this->getCacheKey();
if( !is_null($cacheKey) ){
$f3 = self::getF3();
if( $f3->exists($cacheKey) ){
$f3->clear($cacheKey);
}
}
}
/**
* Throws a validation error for a giben column
* @param $col
@@ -350,10 +420,10 @@ abstract class BasicModel extends \DB\Cortex {
/**
* function should be overwritten in child classes with access restriction
* @param UserModel $user
* @param CharacterModel $characterModel
* @return bool
*/
public function hasAccess(UserModel $user){
public function hasAccess(CharacterModel $characterModel){
return true;
}
@@ -365,113 +435,6 @@ abstract class BasicModel extends \DB\Cortex {
return true;
}
/**
* get cached data from this model
* @param string $dataCacheKeyPrefix - optional key prefix
* @return \stdClass|null
*/
protected function getCacheData($dataCacheKeyPrefix = ''){
$cacheKey = $this->getCacheKey($dataCacheKeyPrefix);
$cacheData = null;
if( !is_null($cacheKey) ){
$f3 = self::getF3();
if( $f3->exists($cacheKey) ){
$cacheData = $f3->get( $cacheKey );
}
}
return $cacheData;
}
/**
* update/set the getData() cache for this object
* @param $cacheData
* @param string $dataCacheKeyPrefix
* @param int $data_ttl
*/
public function updateCacheData($cacheData, $dataCacheKeyPrefix = '', $data_ttl = 300){
$cacheDataTmp = (array)$cacheData;
// check if data should be cached
// and cacheData is not empty
if(
$data_ttl > 0 &&
!empty( $cacheDataTmp )
){
$cacheKey = $this->getCacheKey($dataCacheKeyPrefix);
if( !is_null($cacheKey) ){
self::getF3()->set($cacheKey, $cacheData, $data_ttl);
}
}
}
/**
* unset the getData() cache for this object
*/
public function clearCacheData(){
$cacheKey = $this->getCacheKey();
if( !is_null($cacheKey) ){
$f3 = self::getF3();
if( $f3->exists($cacheKey) ){
$f3->clear($cacheKey);
}
}
}
/**
* get the current class name
* -> namespace not included
* @return string
*/
public static function getClassName(){
$parts = explode('\\', static::class);
return end($parts);
}
/**
* factory for all Models
* @param string $model
* @param int $ttl
* @return BasicModel
* @throws \Exception
*/
public static function getNew($model, $ttl = 86400){
$class = null;
$model = '\\' . __NAMESPACE__ . '\\' . $model;
if(class_exists($model)){
$class = new $model( null, null, null, $ttl );
}else{
throw new \Exception('No model class found');
}
return $class;
}
/**
* get the framework instance (singleton)
* @return \Base
*/
public static function getF3(){
return \Base::instance();
}
/**
* debug log function
* @param string $text
*/
public static function log($text){
Controller\LogController::getLogger('debug')->write($text);
}
/**
* export and download table data as *.csv
* this is primarily used for static tables
@@ -584,6 +547,52 @@ abstract class BasicModel extends \DB\Cortex {
return ['added' => $addedCount, 'updated' => $updatedCount, 'deleted' => $deletedCount];
}
/**
* get the current class name
* -> namespace not included
* @return string
*/
public static function getClassName(){
$parts = explode('\\', static::class);
return end($parts);
}
/**
* factory for all Models
* @param string $model
* @param int $ttl
* @return BasicModel
* @throws \Exception
*/
public static function getNew($model, $ttl = 86400){
$class = null;
$model = '\\' . __NAMESPACE__ . '\\' . $model;
if(class_exists($model)){
$class = new $model( null, null, null, $ttl );
}else{
throw new \Exception('No model class found');
}
return $class;
}
/**
* get the framework instance (singleton)
* @return \Base
*/
public static function getF3(){
return \Base::instance();
}
/**
* debug log function
* @param string $text
*/
public static function log($text){
Controller\LogController::getLogger('debug')->write($text);
}
/**
* get tableModifier class for this table
* @return bool|DB\SQL\TableModifier

View File

@@ -41,6 +41,9 @@ class CharacterLogModel extends BasicModel {
]
]
],
// --------------------------------------------------------------------
'systemId' => [
'type' => Schema::DT_INT,
'index' => true
@@ -50,6 +53,35 @@ class CharacterLogModel extends BasicModel {
'nullable' => false,
'default' => ''
],
'constellationId' => [
'type' => Schema::DT_INT,
'index' => true
],
'constellationName' => [
'type' => Schema::DT_VARCHAR128,
'nullable' => false,
'default' => ''
],
'regionId' => [
'type' => Schema::DT_INT,
'index' => true
],
'regionName' => [
'type' => Schema::DT_VARCHAR128,
'nullable' => false,
'default' => ''
],
// --------------------------------------------------------------------
'shipTypeId' => [
'type' => Schema::DT_INT,
'index' => true
],
'shipTypeName' => [
'type' => Schema::DT_VARCHAR128,
'nullable' => false,
'default' => ''
],
'shipId' => [
'type' => Schema::DT_INT,
'index' => true
@@ -59,32 +91,69 @@ class CharacterLogModel extends BasicModel {
'nullable' => false,
'default' => ''
],
'shipTypeName' => [
'stationId' => [
'type' => Schema::DT_INT,
'index' => true
],
'stationName' => [
'type' => Schema::DT_VARCHAR128,
'nullable' => false,
'default' => ''
]
];
public function __construct($db = NULL, $table = NULL, $fluid = NULL, $ttl = 0){
parent::__construct($db, $table, $fluid, $ttl);
// events -----------------------------------------
$this->beforeerase(function($self){
$self->clearCacheData();
});
}
/**
* set log data from object
* @param object $logData
* set log data from array
* @param array $logData
*/
public function setData($logData){
if( !empty($logData->system) ){
$this->systemId = $logData->system['id'];
$this->systemName = $logData->system['name'];
if( isset($logData['system']) ){
$this->systemId = (int)$logData['system']['id'];
$this->systemName = $logData['system']['name'];
}else{
$this->systemId = null;
$this->systemName = '';
}
if( isset($logData['constellation']) ){
$this->constellationId = (int)$logData['constellation']['id'];
$this->constellationName = $logData['constellation']['name'];
}else{
$this->constellationId = null;
$this->constellationName = '';
}
if( isset($logData['region']) ){
$this->regionId = (int)$logData['region']['id'];
$this->regionName = $logData['region']['name'];
}else{
$this->regionId = null;
$this->regionName = '';
}
// --------------------------------------------------------------------
if( isset($logData['ship']) ){
$this->shipTypeId = (int)$logData['ship']['typeId'];
$this->shipTypeName = $logData['ship']['typeName'];
$this->shipId = (int)$logData['ship']['id'];
$this->shipName = $logData['ship']['name'];
}else{
$this->shipTypeId = null;
$this->shipTypeName = '';
$this->shipId = null;
$this->shipName = '';
}
if( isset($logData['station']) ){
$this->stationId = (int)$logData['station']['id'];
$this->stationName = $logData['station']['name'];
}else{
$this->stationId = null;
$this->stationName = '';
}
}
/**
@@ -95,30 +164,30 @@ class CharacterLogModel extends BasicModel {
$logData = (object) [];
$logData->system = (object) [];
$logData->system->id = $this->systemId;
$logData->system->id = (int)$this->systemId;
$logData->system->name = $this->systemName;
$logData->constellation = (object) [];
$logData->constellation->id = (int)$this->constellationId;
$logData->constellation->name = $this->constellationName;
$logData->region = (object) [];
$logData->region->id = (int)$this->regionId;
$logData->region->name = $this->regionName;
// --------------------------------------------------------------------
$logData->ship = (object) [];
$logData->ship->typeId = (int)$this->shipTypeId;
$logData->ship->typeName = $this->shipTypeName;
$logData->ship->id = $this->shipId;
$logData->ship->name = $this->shipName;
$logData->ship->typeName = $this->shipTypeName;
$logData->station = (object) [];
$logData->station->id = (int)$this->stationId;
$logData->station->name = $this->stationName;
return $logData;
}
/**
* see parent
*/
public function clearCacheData(){
parent::clearCacheData();
// delete log cache key as well
$f3 = self::getF3();
$character = $this->characterId;
$character->clearCacheData();
$f3->clear('LOGGED.user.character.id_' . $character->id);
return true;
}
}

View File

@@ -8,8 +8,10 @@
namespace Model;
use Controller;
use Controller\Ccp;
use DB\SQL\Schema;
use Data\Mapper as Mapper;
class CharacterModel extends BasicModel {
@@ -78,11 +80,19 @@ class CharacterModel extends BasicModel {
'nullable' => false,
'default' => ''
],
'shared' => [
'type' => Schema::DT_BOOL,
'nullable' => false,
'default' => 0
],
'userCharacter' => [
'has-one' => ['Model\UserCharacterModel', 'characterId']
],
'characterLog' => [
'has-one' => ['Model\CharacterLogModel', 'characterId']
],
'characterMaps' => [
'has-many' => ['Model\CharacterMapModel', 'characterId']
]
];
@@ -92,16 +102,24 @@ class CharacterModel extends BasicModel {
* @return \stdClass
*/
public function getData($addCharacterLogData = false){
$characterData = null;
$cacheKeyModifier = '';
// check if there is cached data
// temporary disabled (performance test)
$characterData = $this->getCacheData();
if($addCharacterLogData){
$cacheKeyModifier = strtoupper($this->table) . '_LOG';
}
$characterData = $this->getCacheData($cacheKeyModifier);
if(is_null($characterData)){
// no cached character data found
$characterData = (object) [];
$characterData->id = $this->id;
$characterData->name = $this->name;
$characterData->shared = $this->shared;
if($addCharacterLogData){
if($logModel = $this->getLog()){
@@ -122,7 +140,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, '', 300);
$this->updateCacheData($characterData, $cacheKeyModifier, 10);
}
return $characterData;
@@ -314,37 +332,58 @@ class CharacterModel extends BasicModel {
/**
* update character log (active system, ...)
* -> CREST API request for character log data
* -> HTTP Header Data (if IGB)
* -> CREST API request for character log data (if not IGB)
* @return CharacterModel
*/
public function updateLog(){
$characterModel = $this;
$ssoController = new Ccp\Sso();
$logData = [];
$headerData = Controller\Controller::getIGBHeaderData();
$locationData = $ssoController->getCharacterLocationData($this->getAccessToken());
// check if IGB Data is available
if(
$headerData->trusted === true &&
!empty($headerData->values)
){
// format header data
$formattedHeaderData = (new Mapper\IgbHeader($headerData->values))->getData();
if( empty((array)$locationData) ){
// just for security -> check if Header Data matches THIS character
if(
isset($formattedHeaderData['character']) &&
$formattedHeaderData['character']['id'] == $this->_id
){
$logData = $formattedHeaderData;
}
}else{
// get Location Data from CREST endpoint
// user is NOT with IGB online OR has not jet set "trusted" page
$ssoController = new Ccp\Sso();
$logData = $ssoController->getCharacterLocationData($this->getAccessToken());
}
if( empty($logData) ){
// character is not in-game
if(is_object($this->characterLog)){
// delete existing log
$this->characterLog->erase();
$characterModel = $this->save();
$this->save();
}
}else{
// character is currently in-game
if( !$characterLog = $this->getLog() ){
// create new log
$characterLog = $this->rel('characterLog');
$characterLog->characterId = $this;
$characterLog->characterId = $this->_id;
}
$characterLog->setData($locationData);
$characterLog->setData($logData);
$characterLog->save();
$this->characterLog = $characterLog;
$characterModel = $this->save();
}
return $characterModel;
return $this;
}
/**
@@ -358,10 +397,65 @@ class CharacterModel extends BasicModel {
is_object($this->characterLog) &&
!$this->characterLog->dry()
){
$characterLog = $this->characterLog;
$characterLog = &$this->characterLog;
}
return $characterLog;
}
/**
* get mapModel by id and check if user has access
* @param int $mapId
* @return MapModel|null
*/
public function getMap(int $mapId){
/**
* @var $map MapModel
*/
$map = self::getNew('MapModel');
$map->getById( $mapId );
$returnMap = null;
if($map->hasAccess($this)){
$returnMap = $map;
}
return $returnMap;
}
/**
* get all accessible map models for this character
* @return MapModel[]
*/
public function getMaps(){
$this->filter(
'characterMaps',
['active = ?', 1],
['order' => 'created']
);
$maps = [];
if($this->characterMaps){
$mapCountPrivate = 0;
foreach($this->characterMaps as &$characterMap){
if($mapCountPrivate < self::getF3()->get('PATHFINDER.MAX_MAPS_PRIVATE')){
$maps[] = &$characterMap->mapId;
$mapCountPrivate++;
}
}
}
// get
if($alliance = $this->getAlliance()){
$maps = array_merge($maps, $alliance->getMaps());
}
if($corporation = $this->getCorporation()){
$maps = array_merge($maps, $corporation->getMaps());
}
return $maps;
}
}

View File

@@ -103,11 +103,11 @@ class ConnectionModel extends BasicModel{
/**
* check object for model access
* @param UserModel $user
* @param CharacterModel $characterModel
* @return mixed
*/
public function hasAccess(UserModel $user){
return $this->mapId->hasAccess($user);
public function hasAccess(CharacterModel $characterModel){
return $this->mapId->hasAccess($characterModel);
}
/**
@@ -127,13 +127,12 @@ class ConnectionModel extends BasicModel{
/**
* delete a connection
* @param UserModel $user
* @param CharacterModel $characterModel
*/
public function delete(UserModel $user){
if(!$this->dry()){
// check if editor has access
if($this->hasAccess($user)){
public function delete(CharacterModel $characterModel){
if( !$this->dry() ){
// check if character has access
if($this->hasAccess($characterModel)){
$this->erase();
}
}

View File

@@ -61,8 +61,8 @@ class MapModel extends BasicModel {
'connections' => [
'has-many' => ['Model\ConnectionModel', 'mapId']
],
'mapUsers' => [
'has-many' => ['Model\UserMapModel', 'mapId']
'mapCharacters' => [
'has-many' => ['Model\CharacterMapModel', 'mapId']
],
'mapCorporations' => [
'has-many' => ['Model\CorporationMapModel', 'mapId']
@@ -157,17 +157,23 @@ class MapModel extends BasicModel {
// get access object data -------------------------------------
if($this->isPrivate()){
$users = $this->getUsers();
$userData = [];
foreach($users as $user){
$userData[] = $user->getSimpleData();
$characters = $this->getCharacters();
$characterData = [];
foreach($characters as $character){
/**
* @var $character CharacterModel
*/
$characterData[] = $character->getData();
}
$mapData->access->user = $userData;
$mapData->access->character = $characterData;
} elseif($this->isCorporation()){
$corporations = $this->getCorporations();
$corporationData = [];
foreach($corporations as $corporation){
/**
* @var $corporation CorporationModel
*/
$corporationData[] = $corporation->getData();
}
$mapData->access->corporation = $corporationData;
@@ -176,6 +182,9 @@ class MapModel extends BasicModel {
$allianceData = [];
foreach($alliances as $alliance){
/**
* @var $alliance AllianceModel
*/
$allianceData[] = $alliance->getData();
}
$mapData->access->alliance = $allianceData;
@@ -230,8 +239,9 @@ class MapModel extends BasicModel {
$this->filter('systems',
['active = :active AND id > 0',
':active' => 1
],
['order' => 'posX']);
],
['order' => 'posX']
);
$systems = [];
if($this->systems){
@@ -251,6 +261,9 @@ class MapModel extends BasicModel {
$systemData = [];
foreach($systems as $system){
/**
* @var $system SystemModel
*/
$systemData[] = $system->getData();
}
@@ -284,6 +297,9 @@ class MapModel extends BasicModel {
$connectionData = [];
foreach($connections as $connection){
/**
* @var $connection ConnectionModel
*/
$connectionData[] = $connection->getData();
}
@@ -291,26 +307,26 @@ class MapModel extends BasicModel {
}
/**
* set map access for an object (user, corporation or alliance)
* set map access for an object (character, corporation or alliance)
* @param $obj
*/
public function setAccess($obj){
$newAccessGranted = false;
if($obj instanceof UserModel){
if($obj instanceof CharacterModel){
// private map
// check whether the user has already map access
$this->has('mapUsers', ['active = 1 AND userId = :userId', ':userId' => $obj->id]);
$this->has('mapCharacters', ['active = 1 AND characterId = :characterId', ':characterId' => $obj->id]);
$result = $this->findone(['id = :id', ':id' => $this->id]);
if($result === false){
// grant access for the user
$userMap = self::getNew('UserMapModel');
$userMap->userId = $obj;
$userMap->mapId = $this;
$userMap->save();
// grant access for the character
$characterMap = self::getNew('CharacterMapModel');
$characterMap-> characterId = $obj;
$characterMap->mapId = $this;
$characterMap->save();
$newAccessGranted = true;
}
@@ -356,16 +372,16 @@ class MapModel extends BasicModel {
* clear access for a given type of objects
* @param array $clearKeys
*/
public function clearAccess($clearKeys = ['user', 'corporation', 'alliance']){
public function clearAccess($clearKeys = ['character', 'corporation', 'alliance']){
foreach($clearKeys as $key){
switch($key){
case 'user':
foreach((array)$this->mapUsers as $userMapModel){
case 'character':
foreach((array)$this->mapCharacters as $characterMapModel){
/**
* @var UserMapModel $userMapModel
* @var CharacterMapModel $characterMapModel
*/
$userMapModel->erase();
$characterMapModel->erase();
};
break;
case 'corporation':
@@ -389,17 +405,17 @@ class MapModel extends BasicModel {
}
/**
* checks weather a user has access to this map or not
* @param UserModel $user
* checks weather a character has access to this map or not
* @param CharacterModel $characterModel
* @return bool
*/
public function hasAccess(UserModel $user){
public function hasAccess(CharacterModel $characterModel){
$hasAccess = false;
if( !$this->dry() ){
// get all maps the user has access to
// this includes corporation and alliance maps
$maps = $user->getMaps();
$maps = $characterModel->getMaps();
foreach($maps as $map){
if($map->id === $this->id){
$hasAccess = true;
@@ -412,42 +428,39 @@ class MapModel extends BasicModel {
}
/**
* get all user models that have access to this map
* note: This function is just for "private" maps
* @return UserModel[]
* get all (private) characters for this map
* @return CharacterModel array
*/
public function getUsers(){
$users = [];
private function getCharacters(){
$characters = [];
if($this->isPrivate()){
$this->filter('mapUsers', ['active = ?', 1]);
$this->filter('mapCharacters', ['active = ?', 1]);
if($this->mapUsers){
foreach($this->mapUsers as $mapUser){
$users[] = $mapUser->userId;
}
if($this->mapCharacters){
foreach($this->mapCharacters as $characterMapModel){
$characters[] = $characterMapModel->characterId;
}
}
return $users;
return $characters;
}
/**
* get all character models that are currently online "viewing" this map
* @return CharacterModel[]
*/
private function getCharacters(){
private function getActiveCharacters(){
$characters = [];
if($this->isPrivate()){
$users = $this->getUsers();
$activeCharacters = $this->getCharacters();
// add active character for each user
foreach($users as $user){
foreach($activeCharacters as $activeCharacter){
/**
* @var UserModel $user
*/
$characters = array_merge($characters, $user->getActiveCharacters());
$characters[] = $activeCharacter;
}
}elseif($this->isCorporation()){
$corporations = $this->getCorporations();
@@ -484,7 +497,7 @@ class MapModel extends BasicModel {
if(is_null($charactersData)){
$charactersData = [];
$characters = $this->getCharacters();
$characters = $this->getActiveCharacters();
foreach($characters as $character){
$charactersData[] = $character->getData(true);
@@ -492,7 +505,7 @@ class MapModel extends BasicModel {
// cache active characters (if found)
if(!empty($charactersData)){
$this->updateCacheData($charactersData, 'CHARACTERS', 10);
$this->updateCacheData($charactersData, 'CHARACTERS', 5);
}
}
@@ -541,16 +554,13 @@ class MapModel extends BasicModel {
/**
* delete this map and all dependencies
* @param UserModel $user
* @param CharacterModel $characterModel
*/
public function delete(UserModel $user){
if(!$this->dry()){
// check if user has access
if($this->hasAccess($user)){
public function delete(CharacterModel $characterModel){
if( !$this->dry() ){
// check if character has access
if($this->hasAccess($characterModel)){
// all map related tables will be deleted on cascade
// delete map
$this->erase();
}
}
@@ -663,9 +673,9 @@ class MapModel extends BasicModel {
if( $mapModel->isPrivate() ){
$mapModel->clearAccess(['corporation', 'alliance']);
}elseif( $mapModel->isCorporation() ){
$mapModel->clearAccess(['user', 'alliance']);
$mapModel->clearAccess(['character', 'alliance']);
}elseif( $mapModel->isAlliance() ){
$mapModel->clearAccess(['user', 'corporation']);
$mapModel->clearAccess(['character', 'corporation']);
}
}

View File

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

View File

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

View File

@@ -151,21 +151,21 @@ class SystemSignatureModel extends BasicModel {
/**
* check object for model access
* @param UserModel $user
* @param CharacterModel $characterModel
* @return bool
*/
public function hasAccess(UserModel $user){
return $this->systemId->hasAccess($user);
public function hasAccess(CharacterModel $characterModel){
return $this->systemId->hasAccess($characterModel);
}
/**
* delete signature
* @param UserModel $user
* @param CharacterModel $characterModel
*/
public function delete(UserModel $user){
if(!$this->dry()){
// check if editor has access
if($this->hasAccess($user)){
public function delete(CharacterModel $characterModel){
if( !$this->dry() ){
// check if character has access
if($this->hasAccess($characterModel)){
$this->erase();
}
}

View File

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

View File

@@ -32,17 +32,6 @@ class UserCharacterModel extends BasicModel {
]
]
],
'apiId' => [
'type' => Schema::DT_INT,
'index' => true,
'belongs-to-one' => 'Model\UserApiModel',
'constraint' => [
[
'table' => 'user_api',
'on-delete' => 'CASCADE'
]
]
],
'characterId' => [
'type' => Schema::DT_INT,
'index' => true,
@@ -54,12 +43,6 @@ class UserCharacterModel extends BasicModel {
'on-delete' => 'CASCADE'
]
]
],
'isMain' => [
'type' => Schema::DT_BOOLEAN,
'nullable' => false,
'default' => 0,
'index' => true
]
];
@@ -68,9 +51,7 @@ class UserCharacterModel extends BasicModel {
* @param $characterData
*/
public function setData($characterData){
foreach((array)$characterData as $key => $value){
if(!is_array($value)){
if($this->exists($key)){
$this->$key = $value;
@@ -95,27 +76,6 @@ class UserCharacterModel extends BasicModel {
return true;
}
/**
* check if this character is Main character or not
* @return bool
*/
public function isMain(){
$isMain = false;
if($this->isMain == 1){
$isMain = true;
}
return $isMain;
}
/**
* set this character as main character
*/
public function setMain($value = 0){
$this->isMain = $value;
}
/**
* get the character model of this character
* @return mixed
@@ -135,10 +95,7 @@ class UserCharacterModel extends BasicModel {
$status = parent::setup($db,$table,$fields);
if($status === true){
$status = parent::setMultiColumnIndex(['userId', 'apiId', 'characterId'], true);
if($status === true){
$status = parent::setMultiColumnIndex(['userId', 'apiId']);
}
$status = parent::setMultiColumnIndex(['userId', 'characterId'], true);
}
return $status;

View File

@@ -1,75 +0,0 @@
<?php
/**
* Created by PhpStorm.
* User: exodus4d
* Date: 28.02.15
* Time: 11:57
*/
namespace Model;
use DB\SQL\Schema;
class UserMapModel extends BasicModel {
protected $table = 'user_map';
protected $fieldConf = [
'active' => [
'type' => Schema::DT_BOOL,
'nullable' => false,
'default' => 1,
'index' => true
],
'userId' => [
'type' => Schema::DT_INT,
'index' => true,
'belongs-to-one' => 'Model\UserModel',
'constraint' => [
[
'table' => 'user',
'on-delete' => 'CASCADE'
]
]
],
'mapId' => [
'type' => Schema::DT_INT,
'index' => true,
'belongs-to-one' => 'Model\MapModel',
'constraint' => [
[
'table' => 'map',
'on-delete' => 'CASCADE'
]
]
]
];
/**
* see parent
*/
public function clearCacheData(){
parent::clearCacheData();
// clear map cache as well
$this->mapId->clearCacheData();
}
/**
* overwrites parent
* @param null $db
* @param null $table
* @param null $fields
* @return bool
*/
public static function setup($db=null, $table=null, $fields=null){
$status = parent::setup($db,$table,$fields);
if($status === true){
$status = parent::setMultiColumnIndex(['userId', 'mapId'], true);
}
return $status;
}
}

View File

@@ -35,24 +35,8 @@ class UserModel extends BasicModel {
'nullable' => false,
'default' => ''
],
'password' => [
'type' => Schema::DT_VARCHAR128,
'nullable' => false,
'default' => ''
],
'shared' => [
'type' => Schema::DT_BOOL,
'nullable' => false,
'default' => 0
],
'apis' => [
'has-many' => ['Model\UserApiModel', 'userId']
],
'userCharacters' => [
'has-many' => ['Model\UserCharacterModel', 'userId']
],
'userMaps' => [
'has-many' => ['Model\UserMapModel', 'userId']
]
];
@@ -62,11 +46,6 @@ class UserModel extends BasicModel {
'min' => 5,
'max' => 25
]
],
'password' => [
'length' => [
'min' => 6
]
]
];
@@ -84,9 +63,6 @@ class UserModel extends BasicModel {
// add sensitive user data
$userData->email = $this->email;
// user shared info
$userData->shared = $this->shared;
// all chars
$userData->characters = [];
$characters = $this->getCharacters();
@@ -97,7 +73,7 @@ class UserModel extends BasicModel {
$userData->characters[] = $character->getData();
}
// set active character with log data
// get active character with log data
$activeCharacter = $this->getActiveCharacter();
$userData->character = $activeCharacter->getData(true);
@@ -134,20 +110,6 @@ class UserModel extends BasicModel {
return $email;
}
/**
* set a password hash for this user
* @param string $password
* @return string
*/
public function set_password($password){
if(strlen($password) < 6){
$this->throwValidationError('password');
}
$salt = uniqid('', true);
return \Bcrypt::instance()->hash($password, $salt);
}
/**
* check if new user registration is allowed
* @return bool
@@ -179,133 +141,6 @@ class UserModel extends BasicModel {
return $this->getByForeignKey('name', $name, [], 0);
}
/**
* verify a user by his password
* @param $password
* @return bool
*/
public function verify($password){
$valid = false;
if(! $this->dry()){
$valid = (bool) \Bcrypt::instance()->verify($password, $this->password);
}
return $valid;
}
/**
* get all accessible map models for this user
* @return MapModel[]
*/
public function getMaps(){
$this->filter(
'userMaps',
['active = ?', 1],
['order' => 'created']
);
$maps = [];
if($this->userMaps){
$mapCountPrivate = 0;
foreach($this->userMaps as &$userMap){
if(
$userMap->mapId->isActive() &&
$mapCountPrivate < self::getF3()->get('PATHFINDER.MAX_MAPS_PRIVATE')
){
$maps[] = &$userMap->mapId;
$mapCountPrivate++;
}
}
}
// get current active character
$controller = new Controller\Controller();
$activeCharacter = $controller->getCharacter();
$corporation = $activeCharacter->getCorporation();
$alliance = $activeCharacter->getAlliance();
if($alliance){
$maps = array_merge($maps, $alliance->getMaps());
}
if($corporation){
$maps = array_merge($maps, $corporation->getMaps());
}
return $maps;
}
/**
* get mapModel by id and check if user has access
* @param int $mapId
* @return MapModel|null
* @throws Exception
*/
public function getMap(int $mapId){
/**
* @var $map MapModel
*/
$map = self::getNew('MapModel');
$map->getById( $mapId );
$returnMap = null;
if($map->hasAccess($this)){
$returnMap = $map;
}
return $returnMap;
}
/**
* get all API models for this user
* @return array|mixed
*/
public function getAPIs(){
$this->filter('apis', ['active = ?', 1]);
$apis = [];
if($this->apis){
$apis = $this->apis;
}
return $apis;
}
/**
* set main character ID for this user.
* If id does not match with his API chars -> select "random" main character
* @param int $characterId
*/
public function setMainCharacterId($characterId = 0){
if(is_int($characterId)){
$userCharacters = $this->getUserCharacters();
if(count($userCharacters) > 0){
$mainSet = false;
foreach($userCharacters as $userCharacter){
if($characterId == $userCharacter->getCharacter()->id){
$mainSet = true;
$userCharacter->setMain(1);
}else{
$userCharacter->setMain(0);
}
$userCharacter->save();
}
// set first character as "main"
if( !$mainSet ){
$userCharacter = reset($userCharacters);
$userCharacter->setMain(1);
$userCharacter->save();
}
}
}
}
/**
* get all userCharacters models for a user
* characters will be checked/updated on login by CCP API call
@@ -322,24 +157,6 @@ class UserModel extends BasicModel {
return $userCharacters;
}
/**
* Get the main user character for this user
* @return null
*/
public function getMainUserCharacter(){
$mainUserCharacter = null;
$userCharacters = $this->getUserCharacters();
foreach($userCharacters as $userCharacter){
if($userCharacter->isMain()){
$mainUserCharacter = $userCharacter;
break;
}
}
return $mainUserCharacter;
}
/**
* get the current active character for this user
* -> EITHER - the current active one for the current user
@@ -359,7 +176,7 @@ class UserModel extends BasicModel {
}else{
// set "first" found as active for this user
if($activeCharacters = $this->getActiveCharacters()){
$activeCharacter = &$activeCharacters[0];
$activeCharacter = $activeCharacters[0];
}
}
@@ -409,105 +226,4 @@ class UserModel extends BasicModel {
return $activeCharacters;
}
/**
* updated the character log entry for a user character by IGB Header data
* @param int $ttl cache time in seconds
* @throws \Exception
*/
/*
public function updateCharacterLog($ttl = 0){
$headerData = Controller\Controller::getIGBHeaderData();
// check if IGB Data is available
if( !empty($headerData->values) ){
$f3 = self::getF3();
// check if system has changed since the last call
// current location is stored (global) to avoid unnecessary DB calls
$sessionCharacterKey = 'LOGGED.user.character.id_' . $headerData->values['charid'];
if($f3->exists($sessionCharacterKey)){
// cache data exists
$cacheData = $f3->get($sessionCharacterKey);
}else{
// new cache data
$cacheData = [
'systemId' => 0,
'shipId' => 0
];
}
if(
$cacheData['systemId'] != $headerData->values['solarsystemid'] ||
$cacheData['shipId'] != $headerData->values['shiptypeid']
){
$cacheData['systemId'] = (int)$headerData->values['solarsystemid'];
$cacheData['shipId'] = (int)$headerData->values['shiptypeid'];
// character has changed system, or character just logged on
$character = self::getNew('CharacterModel');
$character->getById( (int)$headerData->values['charid'] );
if( $character->dry() ){
// this can happen if a valid user plays the game with a not registered character
// whose API is not registered -> save new character or update character data
$corporationId = array_key_exists('corpid', $headerData->values) ? $headerData->values['corpid'] : null;
$allianceId = array_key_exists('allianceid', $headerData->values) ? $headerData->values['allianceid'] : null;
// check if corp exists
if( !is_null($corporationId) ){
$corporation = self::getNew('CorporationModel');
$corporation->getById( (int)$corporationId );
if( $corporation->dry() ){
$corporation->id = $corporationId;
$corporation->name = $headerData->values['corpname'];
$corporation->save();
}
}
// check if ally exists
if( !is_null($allianceId) ){
$alliance = self::getNew('AllianceModel');
$alliance->getById( (int)$allianceId );
if( $alliance->dry() ){
$alliance->id = $allianceId;
$alliance->name = $headerData->values['alliancename'];
$alliance->save();
}
}
$character->id = (int) $headerData->values['charid'];
$character->name = $headerData->values['charname'];
$character->corporationId = $corporationId;
$character->allianceId = $allianceId;
$character->save();
}
// check if this character has an active log
if( !$characterLog = $character->getLog() ){
$characterLog = self::getNew('CharacterLogModel');
}
// set character log values
$characterLog->characterId = $character;
$characterLog->systemId = (int)$headerData->values['solarsystemid'];
$characterLog->systemName = $headerData->values['solarsystemname'];
$characterLog->shipId = (int)$headerData->values['shiptypeid'];
$characterLog->shipName = $headerData->values['shipname'];
$characterLog->shipTypeName = $headerData->values['shiptypename'];
$characterLog->save();
// clear cache for the characterModel as well
$character->clearCacheData();
// cache character log information
$f3->set($sessionCharacterKey, $cacheData, $ttl);
}
}
}
*/
}

View File

@@ -15,7 +15,7 @@ MAX_MAPS_CORPORATION = 3
MAX_MAPS_ALLIANCE = 3
; Max number of shared entities per map
MAX_SHARED_USER = 10
MAX_SHARED_CHARACTER = 10
MAX_SHARED_CORPORATION = 3
MAX_SHARED_ALLIANCE = 2
@@ -35,8 +35,8 @@ INVITE = 0
INVITE_LIMIT = 50
[PATHFINDER.LOGIN]
CORPORATION = 1000166
; restrict login to specific corporations/alliances by id (e.g. 1000166,1000080)
CORPORATION =
ALLIANCE =
; View ============================================================================================
@@ -83,7 +83,7 @@ EXECUTION_LIMIT = 50
; map user update ping (ajax) (ms)
[PATHFINDER.TIMER.UPDATE_SERVER_USER_DATA]
DELAY = 5000
EXECUTION_LIMIT = 200
EXECUTION_LIMIT = 300
; update client user data (ms)
[PATHFINDER.TIMER.UPDATE_CLIENT_USER_DATA]

View File

@@ -3,16 +3,15 @@
[routes]
; DB setup setup
; IMPORTANT: remove/comment this line after setup/update is finished!
GET @setup: /setup = Controller\Setup->init, 0
GET @setup: /setup [sync] = Controller\Setup->init, 0
; login (index) page
GET @login: / = Controller\AppController->init, 0
GET @login: / [sync] = Controller\AppController->init, 0
; CCP SSO redirect
GET @sso: /sso/@action = Controller\Ccp\Sso->@action, 0
GET @sso: /sso/@action [sync] = Controller\Ccp\Sso->@action, 0
; map page
GET @map: /map = Controller\MapController->init, 0
GET @map: /map [sync] = Controller\MapController->init, 0
; ajax wildcard APIs (throttled)
GET|POST /api/@controller/@action [ajax] = Controller\Api\@controller->@action, 0, 512
GET|POST /api/@controller/@action/@arg1 [ajax] = Controller\Api\@controller->@action, 0, 512
GET|POST /api/@controller/@action/@arg1/@arg2 [ajax] = Controller\Api\@controller->@action, 0, 512
GET|POST /api/@controller/@action [ajax] = Controller\Api\@controller->@action, 0, 512
GET|POST /api/@controller/@action/@arg1 [ajax] = Controller\Api\@controller->@action, 0, 512
GET|POST /api/@controller/@action/@arg1/@arg2 [ajax] = Controller\Api\@controller->@action, 0, 512

View File

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

View File

@@ -45,7 +45,7 @@ requirejs.config({
hoverIntent: 'lib/jquery.hoverIntent.minified', // v1.8.0 Hover intention - http://cherne.net/brian/resources/jquery.hoverIntent.html
fullScreen: 'lib/jquery.fullscreen.min', // v0.5.0 Full screen mode - https://github.com/private-face/jquery.fullscreen
select2: 'lib/select2.min', // v4.0.0 Drop Down customization - https://select2.github.io/
validator: 'lib/validator.min', // v0.7.2 Validator for Bootstrap 3 - https://github.com/1000hz/bootstrap-validator
validator: 'lib/validator.min', // v0.10.1 Validator for Bootstrap 3 - https://github.com/1000hz/bootstrap-validator
lazylinepainter: 'lib/jquery.lazylinepainter-1.5.1.min', // v1.5.1 SVG line animation plugin - http://lazylinepainter.info/
blueImpGallery: 'lib/blueimp-gallery', // v2.15.2 Image Gallery - https://github.com/blueimp/Gallery/
blueImpGalleryHelper: 'lib/blueimp-helper', // helper function for Blue Imp Gallery

View File

@@ -16,6 +16,7 @@ define(['jquery'], function($) {
logOut: 'api/user/logOut', // ajax URL - logout
deleteLog: 'api/user/deleteLog', // ajax URL - delete character log
saveUserConfig: 'api/user/saveAccount', // ajax URL - saves/update user account
getUserData: 'api/user/getData', // ajax URL - get user data
saveSharingConfig: 'api/user/saveSharingConfig', // ajax URL - save "sharing settings" dialog
deleteAccount: 'api/user/deleteAccount', // ajax URL - delete Account data
// access API

View File

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

View File

@@ -84,54 +84,6 @@ define([
});
});
// login buttons ------------------------------------------------
var loginForm = $('#' + config.loginFormId);
loginForm.on('submit', function(e){
e.preventDefault();
var loginFormMessageContainer = $('#' + config.loginMessageContainerId);
// validate form
loginForm.validator('validate');
// check weather the form is valid
var formValid = loginForm.isValidForm();
if(formValid === true){
// show splash overlay
$('.' + config.splashOverlayClass).showSplashOverlay(function(){
var loginData = {loginData: loginForm.getFormValues()};
$.ajax({
type: 'POST',
url: Init.path.logIn,
data: loginData,
dataType: 'json'
}).done(function(data){
// login error
if(data.error !== undefined){
$('.' + config.splashOverlayClass).hideSplashOverlay();
loginFormMessageContainer.showMessage({title: 'Login failed', text: ' Invalid username or password', type: 'error'});
}else if(data.reroute !== undefined){
window.location = data.reroute;
}
}).fail(function( jqXHR, status, error) {
$('.' + config.splashOverlayClass).hideSplashOverlay();
var reason = status + ' ' + error;
Util.showNotify({title: jqXHR.status + ': login', text: reason, type: 'error'});
// show Form message
loginFormMessageContainer.showMessage({title: 'Login failed', text: ' internal server error', type: 'error'});
});
});
}
});
// releases -----------------------------------------------------
$('.' + config.navigationVersionLinkClass).on('click', function(e){
$.fn.releasesDialog();

View File

@@ -602,12 +602,12 @@ define([
system.attr('data-mapid', parseInt(mapContainer.data('id')));
// locked system
if( Boolean( system.data( 'locked') ) !== Boolean( parseInt( data.locked ) )){
if( Boolean( system.data( 'locked') ) !== data.locked ){
system.toggleLockSystem(false, {hideNotification: true, hideCounter: true, map: map});
}
// rally system
if( Boolean( system.data( 'rally') ) !== Boolean( parseInt( data.rally ) )){
if( Boolean( system.data( 'rally') ) !== data.rally ){
system.toggleRallyPoint(false, {hideNotification: true, hideCounter: true});
}
@@ -3099,12 +3099,10 @@ define([
// this is restricted to IGB-usage! CharacterLog data is always set through the IGB
// ->this prevent adding the same system multiple times, if a user is online with IGB AND OOG
if(
CCP.isInGameBrowser() === true &&
currentUserOnMap === false &&
currentCharacterLog &&
mapTracking
){
// add new system to the map
var requestData = {
systemData: {
@@ -3344,6 +3342,15 @@ define([
return data;
};
/**
* removes a map instance from local cache
* @param mapId
*/
var clearMapInstance = function(mapId){
if(typeof activeInstances[mapId] === 'object'){
delete activeInstances[mapId];
}
};
/**
* get a new jsPlumb map instance or or get a cached one for update
@@ -3583,4 +3590,8 @@ define([
});
};
return {
clearMapInstance: clearMapInstance
};
});

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -131,7 +131,7 @@ define([
/**
* init a select element as an ajax based "select2" object for Access resources
* user (private map), corporation (corp map), alliance (ally map)
* character (private map), corporation (corp map), alliance (ally map)
* @param options
*/
$.fn.initAccessSelect = function(options){
@@ -162,8 +162,9 @@ define([
var previewContent = '';
switch(options.type){
case 'user':
previewContent = '<i class="fa fa-lg fa-user"></i>';
case 'character':
imagePath = Init.url.ccpImageServer + 'Character/' + data.id + '_32.jpg';
previewContent = '<img src="' + imagePath + '" style="max-width: 100%" />';
break;
case 'corporation':
imagePath = Init.url.ccpImageServer + 'Corporation/' + data.id + '_32.png';

View File

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

View File

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

View File

@@ -1634,6 +1634,10 @@ define([
// submit all fields within a table row
var formFields = rowElement.find('.editable');
// the "toggle" makes sure to take care about open editable fields (e.g. description)
// otherwise, changes would not be submitted in this field (not necessary)
formFields.editable('toggle');
// submit all xEditable fields
formFields.editable('submit', {
url: Init.path.saveSignatureData,

View File

@@ -317,15 +317,17 @@ define([
/**
* init form elements for validation (bootstrap3 validation)
* @returns {any|JQuery|*}
* @param options
* @returns {*}
*/
$.fn.initFormValidation = function(){
$.fn.initFormValidation = function(options){
options = (typeof options === 'undefined')? {} : options;
return this.each(function(){
var form = $(this);
// init form validation
form.validator();
form.validator(options);
// validation event listener
form.on('valid.bs.validator', function(validatorObj){
@@ -342,7 +344,6 @@ define([
inputGroup.removeClass('has-success').addClass('has-error');
}
});
});
};
@@ -545,6 +546,84 @@ define([
}
};
/**
* add character switch popover
* @param userData
*/
$.fn.initCharacterSwitchPopover = function(userData){
var elements = $(this);
var eventNamespace = 'hideCharacterPopup';
requirejs(['text!templates/tooltip/character_switch.html', 'mustache'], function (template, Mustache) {
var data = {
routes: Init.routes,
userData: userData,
otherCharacters: $.grep( userData.characters, function( character ) {
// exclude current active character
return character.id !== userData.character.id;
})
};
var content = Mustache.render(template, data);
return elements.each(function() {
var element = $(this);
// destroy "popover" and remove "click" event for animation
element.popover('destroy').off();
// init popover and add specific class to it (for styling)
element.popover({
html: true,
title: 'select character',
trigger: 'click',
placement: 'bottom',
content: content,
animation: false
}).data('bs.popover').tip().addClass('pf-character-info-popover');
element.on('click', function(e) {
e.preventDefault();
var easeEffect = $(this).attr('data-easein');
var popover = $(this).data('bs.popover').tip();
var velocityOptions = {
duration: CCP.isInGameBrowser() ? 0 : Init.animationSpeed.dialogEvents
};
switch(easeEffect){
case 'shake':
case 'pulse':
case 'tada':
case 'flash':
case 'bounce':
case 'swing':
popover.velocity('callout.' + easeEffect, velocityOptions);
break;
default:
popover.velocity('transition.' + easeEffect, velocityOptions);
break;
}
});
// hide popup on clicking "somewhere" outside
$('body').off('click.' + eventNamespace).on('click.' + eventNamespace, function (e) {
//the 'is' for buttons that trigger popups
//the 'has' for icons within a button that triggers a popup
if (
!$(element).is(e.target) &&
$(element).has(e.target).length === 0 &&
$('.popover').has(e.target).length === 0
){
$(element).popover('hide');
}
});
});
});
};
/**
* add a wormhole tooltip with wh specific data to elements
* @param tooltipData
@@ -575,7 +654,6 @@ define([
var popover = element.data('bs.popover');
popover.options.content = content;
});
});
};
@@ -1129,7 +1207,6 @@ define([
* @returns {string}
*/
var getSystemEffectTable = function(data){
var table = '';
if(data.length > 0){

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -45,7 +45,7 @@ requirejs.config({
hoverIntent: 'lib/jquery.hoverIntent.minified', // v1.8.0 Hover intention - http://cherne.net/brian/resources/jquery.hoverIntent.html
fullScreen: 'lib/jquery.fullscreen.min', // v0.5.0 Full screen mode - https://github.com/private-face/jquery.fullscreen
select2: 'lib/select2.min', // v4.0.0 Drop Down customization - https://select2.github.io/
validator: 'lib/validator.min', // v0.7.2 Validator for Bootstrap 3 - https://github.com/1000hz/bootstrap-validator
validator: 'lib/validator.min', // v0.10.1 Validator for Bootstrap 3 - https://github.com/1000hz/bootstrap-validator
lazylinepainter: 'lib/jquery.lazylinepainter-1.5.1.min', // v1.5.1 SVG line animation plugin - http://lazylinepainter.info/
blueImpGallery: 'lib/blueimp-gallery', // v2.15.2 Image Gallery - https://github.com/blueimp/Gallery/
blueImpGalleryHelper: 'lib/blueimp-helper', // helper function for Blue Imp Gallery

View File

@@ -16,6 +16,7 @@ define(['jquery'], function($) {
logOut: 'api/user/logOut', // ajax URL - logout
deleteLog: 'api/user/deleteLog', // ajax URL - delete character log
saveUserConfig: 'api/user/saveAccount', // ajax URL - saves/update user account
getUserData: 'api/user/getData', // ajax URL - get user data
saveSharingConfig: 'api/user/saveSharingConfig', // ajax URL - save "sharing settings" dialog
deleteAccount: 'api/user/deleteAccount', // ajax URL - delete Account data
// access API

View File

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

View File

@@ -84,54 +84,6 @@ define([
});
});
// login buttons ------------------------------------------------
var loginForm = $('#' + config.loginFormId);
loginForm.on('submit', function(e){
e.preventDefault();
var loginFormMessageContainer = $('#' + config.loginMessageContainerId);
// validate form
loginForm.validator('validate');
// check weather the form is valid
var formValid = loginForm.isValidForm();
if(formValid === true){
// show splash overlay
$('.' + config.splashOverlayClass).showSplashOverlay(function(){
var loginData = {loginData: loginForm.getFormValues()};
$.ajax({
type: 'POST',
url: Init.path.logIn,
data: loginData,
dataType: 'json'
}).done(function(data){
// login error
if(data.error !== undefined){
$('.' + config.splashOverlayClass).hideSplashOverlay();
loginFormMessageContainer.showMessage({title: 'Login failed', text: ' Invalid username or password', type: 'error'});
}else if(data.reroute !== undefined){
window.location = data.reroute;
}
}).fail(function( jqXHR, status, error) {
$('.' + config.splashOverlayClass).hideSplashOverlay();
var reason = status + ' ' + error;
Util.showNotify({title: jqXHR.status + ': login', text: reason, type: 'error'});
// show Form message
loginFormMessageContainer.showMessage({title: 'Login failed', text: ' internal server error', type: 'error'});
});
});
}
});
// releases -----------------------------------------------------
$('.' + config.navigationVersionLinkClass).on('click', function(e){
$.fn.releasesDialog();

View File

@@ -602,12 +602,12 @@ define([
system.attr('data-mapid', parseInt(mapContainer.data('id')));
// locked system
if( Boolean( system.data( 'locked') ) !== Boolean( parseInt( data.locked ) )){
if( Boolean( system.data( 'locked') ) !== data.locked ){
system.toggleLockSystem(false, {hideNotification: true, hideCounter: true, map: map});
}
// rally system
if( Boolean( system.data( 'rally') ) !== Boolean( parseInt( data.rally ) )){
if( Boolean( system.data( 'rally') ) !== data.rally ){
system.toggleRallyPoint(false, {hideNotification: true, hideCounter: true});
}
@@ -3099,12 +3099,10 @@ define([
// this is restricted to IGB-usage! CharacterLog data is always set through the IGB
// ->this prevent adding the same system multiple times, if a user is online with IGB AND OOG
if(
CCP.isInGameBrowser() === true &&
currentUserOnMap === false &&
currentCharacterLog &&
mapTracking
){
// add new system to the map
var requestData = {
systemData: {
@@ -3344,6 +3342,15 @@ define([
return data;
};
/**
* removes a map instance from local cache
* @param mapId
*/
var clearMapInstance = function(mapId){
if(typeof activeInstances[mapId] === 'object'){
delete activeInstances[mapId];
}
};
/**
* get a new jsPlumb map instance or or get a cached one for update
@@ -3583,4 +3590,8 @@ define([
});
};
return {
clearMapInstance: clearMapInstance
};
});

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -131,7 +131,7 @@ define([
/**
* init a select element as an ajax based "select2" object for Access resources
* user (private map), corporation (corp map), alliance (ally map)
* character (private map), corporation (corp map), alliance (ally map)
* @param options
*/
$.fn.initAccessSelect = function(options){
@@ -162,8 +162,9 @@ define([
var previewContent = '';
switch(options.type){
case 'user':
previewContent = '<i class="fa fa-lg fa-user"></i>';
case 'character':
imagePath = Init.url.ccpImageServer + 'Character/' + data.id + '_32.jpg';
previewContent = '<img src="' + imagePath + '" style="max-width: 100%" />';
break;
case 'corporation':
imagePath = Init.url.ccpImageServer + 'Corporation/' + data.id + '_32.png';

View File

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

View File

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

View File

@@ -1634,6 +1634,10 @@ define([
// submit all fields within a table row
var formFields = rowElement.find('.editable');
// the "toggle" makes sure to take care about open editable fields (e.g. description)
// otherwise, changes would not be submitted in this field (not necessary)
formFields.editable('toggle');
// submit all xEditable fields
formFields.editable('submit', {
url: Init.path.saveSignatureData,

View File

@@ -317,15 +317,17 @@ define([
/**
* init form elements for validation (bootstrap3 validation)
* @returns {any|JQuery|*}
* @param options
* @returns {*}
*/
$.fn.initFormValidation = function(){
$.fn.initFormValidation = function(options){
options = (typeof options === 'undefined')? {} : options;
return this.each(function(){
var form = $(this);
// init form validation
form.validator();
form.validator(options);
// validation event listener
form.on('valid.bs.validator', function(validatorObj){
@@ -342,7 +344,6 @@ define([
inputGroup.removeClass('has-success').addClass('has-error');
}
});
});
};
@@ -545,6 +546,84 @@ define([
}
};
/**
* add character switch popover
* @param userData
*/
$.fn.initCharacterSwitchPopover = function(userData){
var elements = $(this);
var eventNamespace = 'hideCharacterPopup';
requirejs(['text!templates/tooltip/character_switch.html', 'mustache'], function (template, Mustache) {
var data = {
routes: Init.routes,
userData: userData,
otherCharacters: $.grep( userData.characters, function( character ) {
// exclude current active character
return character.id !== userData.character.id;
})
};
var content = Mustache.render(template, data);
return elements.each(function() {
var element = $(this);
// destroy "popover" and remove "click" event for animation
element.popover('destroy').off();
// init popover and add specific class to it (for styling)
element.popover({
html: true,
title: 'select character',
trigger: 'click',
placement: 'bottom',
content: content,
animation: false
}).data('bs.popover').tip().addClass('pf-character-info-popover');
element.on('click', function(e) {
e.preventDefault();
var easeEffect = $(this).attr('data-easein');
var popover = $(this).data('bs.popover').tip();
var velocityOptions = {
duration: CCP.isInGameBrowser() ? 0 : Init.animationSpeed.dialogEvents
};
switch(easeEffect){
case 'shake':
case 'pulse':
case 'tada':
case 'flash':
case 'bounce':
case 'swing':
popover.velocity('callout.' + easeEffect, velocityOptions);
break;
default:
popover.velocity('transition.' + easeEffect, velocityOptions);
break;
}
});
// hide popup on clicking "somewhere" outside
$('body').off('click.' + eventNamespace).on('click.' + eventNamespace, function (e) {
//the 'is' for buttons that trigger popups
//the 'has' for icons within a button that triggers a popup
if (
!$(element).is(e.target) &&
$(element).has(e.target).length === 0 &&
$('.popover').has(e.target).length === 0
){
$(element).popover('hide');
}
});
});
});
};
/**
* add a wormhole tooltip with wh specific data to elements
* @param tooltipData
@@ -575,7 +654,6 @@ define([
var popover = element.data('bs.popover');
popover.options.content = content;
});
});
};
@@ -1129,7 +1207,6 @@ define([
* @returns {string}
*/
var getSystemEffectTable = function(data){
var table = '';
if(data.length > 0){

File diff suppressed because one or more lines are too long

View File

@@ -15,21 +15,6 @@
</div>
{{/userData.name}}
<div class="row">
<div class="col-sm-12">
<div class="form-group">
<label for="password" class="col-sm-3 control-label">Password</label>
<div class="col-sm-6">
<div class="input-group" title="Enter your password" data-placement="right">
<input name="password" type="password" class="form-control" id="password" placeholder="" data-error="Field is required" autocomplete="off" required>
<span class="input-group-addon"><i class="fa fa-fw fa-lock"></i></span>
</div>
<div class="help-block with-errors"></div>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-sm-12">
<div class="form-group">

View File

@@ -55,27 +55,27 @@
</div>
</div>
{{! user search ----------------------------------------------------- }}
{{#accessUser.length}}
{{! character search ------------------------------------------------ }}
{{#accessCharacter.length}}
<div class="row">
<div class="col-sm-12">
<div class="form-group">
<label class="col-sm-2 control-label" for="{{userSelectId}}">Username</label>
<label class="col-sm-2 control-label" for="{{characterSelectId}}">Character</label>
<div class="col-sm-10">
<div class="input-group" title="add/remove user">
<label for="{{userSelectId}}"></label>
<select id="{{userSelectId}}" name="mapUsers[]" multiple="multiple">
{{#accessUser}}
<div class="input-group" title="add/remove character">
<label for="{{characterSelectId}}"></label>
<select id="{{characterSelectId}}" name="mapCharacters[]" multiple="multiple">
{{#accessCharacter}}
<option value="{{id}}" selected>{{name}}</option>
{{/accessUser}}
{{/accessCharacter}}
</select>
<span class="help-block with-errors">Search user name (max {{maxUser}})</span>
<span class="help-block with-errors">Search character name (max {{maxCharacter}})</span>
</div>
</div>
</div>
</div>
</div>
{{/accessUser.length}}
{{/accessCharacter.length}}
{{! corporation search ---------------------------------------------- }}
{{#accessCorporation.length}}

View File

@@ -10,5 +10,10 @@
<h5 class="text-center">{{.}}</h5>
{{/.}}
{{/text}}
{{#textSmaller}}
{{#.}}
<h4 class="text-center">{{.}}</h4>
{{/.}}
{{/textSmaller}}
{{/content}}
</div>

View File

@@ -1,177 +1,90 @@
<div id="{{id}}">
<div role="tabpanel">
<ul class="text-center {{navigationClass}} row" role="tablist">
<li class="col-sm-3 active">
<a class="btn btn-default btn-circle btn-sm" data-toggle="tab" href="#pf-dialog-settings-account">1</a>
<h6>Account Setup</h6>
</li>
<li class="col-sm-3">
<a class="btn btn-default btn-circle btn-sm disabled" data-toggle="tab" href="#pf-dialog-settings-api">2</a>
<h6>API Keys</h6>
</li>
<li class="col-sm-3">
<a class="btn btn-default btn-circle btn-sm disabled" data-toggle="tab" href="#pf-dialog-settings-character">3</a>
<h6>Characters</h6>
</li>
<li class="col-sm-3">
<a class="btn btn-default btn-circle btn-sm disabled" data-toggle="tab" href="#pf-dialog-settings-done">4</a>
<h6>Done</h6>
</li>
</ul>
<hr>
<div class="tab-content">
<nav class="navbar navbar-default" role="navigation">
<div class="navbar-header pull-left">
<ul class="nav navbar-nav" role="tablist">
<li class="active">
<a role="tab" data-toggle="tab" data-name="account" href="#{{settingsAccountContainerId}}">
<i class="fa fa-user fa-fw"></i>&nbsp;Account
</a>
</li>
<li>
<a role="tab" data-toggle="tab" data-name="share" href="#{{settingsShareContainerId}}">
<i class="fa fa-share-alt fa-fw"></i>&nbsp;Share
</a>
</li>
</ul>
</div>
</nav>
<div class="tab-content">
<div role="tabpanel" class="tab-pane fade in active" id="{{settingsAccountContainerId}}">
{{! account tab ================================================================================================ }}
<div role="tabpanel" class="tab-pane fade in active" id="pf-dialog-settings-account">
<form role="form" class="form-horizontal">
{{^register}}
<div class="row">
<div class="col-sm-12">
<div class="form-group">
<label class="col-sm-3 control-label">Username</label>
<div class="col-sm-9">
<p class="form-control-static">{{userData.name}}</p>
</div>
</div>
<form role="form" class="form-horizontal">
{{! Username }}
<div class="row">
<div class="col-sm-12">
<div class="form-group">
<label class="col-sm-3 control-label">Username</label>
<div class="col-sm-9">
<p class="form-control-static">
<i class="fa fa-fw fa-lg fa-pencil pull-right pf-dialog-icon-button collapsed" data-toggle="collapse" data-target="#collapseUsername" aria-expanded="false" aria-controls="collapseUsername"></i>
{{userData.name}}
</p>
</div>
</div>
{{/register}}
</div>
</div>
{{#register}}
{{#invite}}
<div class="alert alert-info" style="margin-bottom: 20px">
<button type="button" class="close" data-dismiss="alert" aria-label="Close"><i class="fa fa-close"></i></button>
<span class="txt-color txt-color-information">Invite active</span>
<small>You need a "Registration Key" to complete registration</small>
</div>
<div class="row">
<div class="col-sm-12">
<div class="form-group">
<label for="registrationKey" class="col-sm-3 control-label">Registration Key</label>
<div class="col-sm-6">
<div class="input-group" title="Enter your personal registration kay" data-placement="right">
<input name="registrationKey" type="text" class="form-control" id="registrationKey" value="" placeholder="XXXXXX" data-error="Registration key required" data-minlength="40" data-minlength-error="Min. of 40 characters" autocomplete="off" required>
<span class="input-group-addon"><i class="fa fa-fw fa-certificate"></i></span>
</div>
<div class="help-block with-errors"></div>
</div>
</div>
</div>
</div>
{{/invite}}
<div class="row">
<div class="col-sm-12">
<div class="form-group">
<label for="name" class="col-sm-3 control-label">Username</label>
<div class="col-sm-6">
<div class="input-group" title="Choose your unique username" data-placement="right">
<input name="name" type="text" class="form-control" id="name" value="" placeholder="Your username" data-error="Username required" data-minlength="5" data-minlength-error="Min. of 5 characters" autocomplete="nickname" required>
<span class="input-group-addon"><i class="fa fa-fw fa-user"></i></span>
</div>
<div class="help-block with-errors"></div>
</div>
</div>
</div>
</div>
{{/register}}
{{^register}}
<div class="row">
<div class="col-sm-12">
<div class="form-group">
<label class="col-sm-3 control-label">Email</label>
<div class="col-sm-9">
<p class="form-control-static">
<i class="fa fa-fw fa-lg fa-pencil pull-right pf-dialog-icon-button collapsed" data-toggle="collapse" data-target="#collapseEmail" aria-expanded="false" aria-controls="collapseEmail"></i>
{{userData.email}}
</p>
</div>
</div>
</div>
</div>
{{/register}}
<div id="collapseEmail" class="{{^register}}collapse{{/register}}">
<div class="row">
<div class="col-sm-12">
<div class="form-group">
<label for="email" class="col-sm-3 control-label">New Email</label>
<div class="col-sm-6">
<div class="input-group" title="Enter your email. It will be kept private!" data-placement="right">
<input name="email" type="email" class="form-control" id="email" value="" placeholder="your@email.com" data-error="Email required" autocomplete="email" {{#register}}required{{/register}} >
<span class="input-group-addon"><i class="fa fa-fw fa-envelope"></i></span>
</div>
<div class="help-block with-errors"></div>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-sm-12">
<div class="form-group">
<label for="email_confirm" class="col-sm-3 control-label">Confirm Email</label>
<div class="col-sm-6">
<div class="input-group" title="Confirm your email" data-placement="right">
<input name="email_confirm" type="email" class="form-control" id="email_confirm" value="" placeholder="your@email.com" data-error="Email required" data-match="#email" data-match-error="Email fields do not match" autocomplete="email" {{#register}}required{{/register}}>
<span class="input-group-addon"><i class="fa fa-fw fa-envelope"></i></span>
</div>
<div class="help-block with-errors"></div>
<div id="collapseUsername" class="collapse">
<div class="row">
<div class="col-sm-12">
<div class="form-group">
<label for="name" class="col-sm-3 control-label">New Username</label>
<div class="col-sm-6">
<div class="input-group" title="Choose your unique username" data-placement="right">
<input name="name" type="text" class="form-control" id="name" value="" placeholder="Your username" data-error="Username required" data-minlength="5" data-minlength-error="Min. of 5 characters" data-username="true" autocomplete="nickname">
<span class="input-group-addon"><i class="fa fa-fw fa-user"></i></span>
</div>
<div class="help-block with-errors"></div>
</div>
</div>
</div>
</div>
</div>
{{^register}}
<div class="row">
<div class="col-sm-12">
<div class="form-group">
<label class="col-sm-3 control-label">Password</label>
<div class="col-sm-9">
<p class="form-control-static">
<i class="fa fa-fw fa-lg fa-pencil pull-right pf-dialog-icon-button collapsed"data-toggle="collapse" data-target="#collapsePassword" aria-expanded="false" aria-controls="collapsePassword"></i>
<i class="fa fa-fw fa-ellipsis-h"></i>
</p>
</div>
</div>
{{! Email }}
<div class="row">
<div class="col-sm-12">
<div class="form-group">
<label class="col-sm-3 control-label">Email</label>
<div class="col-sm-9">
<p class="form-control-static">
<i class="fa fa-fw fa-lg fa-pencil pull-right pf-dialog-icon-button collapsed" data-toggle="collapse" data-target="#collapseEmail" aria-expanded="false" aria-controls="collapseEmail"></i>
{{#userData.email}}
{{userData.email}}
{{/userData.email}}
{{^userData.email}}
unknown
{{/userData.email}}
</p>
</div>
</div>
{{/register}}
</div>
</div>
<div id="collapsePassword" class="{{^register}}collapse{{/register}}">
<div class="row">
<div class="col-sm-12">
<div class="form-group">
<label for="password" class="col-sm-3 control-label">New Password</label>
<div class="col-sm-6">
<div class="input-group" title="Enter your password. Do not use your EVE password" data-placement="right">
<input name="password" type="password" class="form-control" id="password" placeholder="" data-minlength="6" data-minlength-error="Min. of 6 characters" autocomplete="new-password" {{#register}}required{{/register}}>
<span class="input-group-addon"><i class="fa fa-fw fa-lock"></i></span>
</div>
<div class="help-block with-errors"></div>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-sm-12">
<div class="form-group">
<label for="password_confirm" class="col-sm-3 control-label">Confirm Password</label>
<div class="col-sm-6">
<div class="input-group" title="Confirm your password" data-placement="right">
<input name="password_confirm" type="password" class="form-control" id="password_confirm" placeholder="" data-minlength="6" data-minlength-error="Min. of 6 characters" data-match="#password" data-match-error="Password fields do not match" autocomplete="new-password" {{#register}}required{{/register}}>
<span class="input-group-addon"><i class="fa fa-fw fa-lock"></i></span>
</div>
<div class="help-block with-errors"></div>
<div id="collapseEmail" class="collapse">
<div class="row">
<div class="col-sm-12">
<div class="form-group">
<label for="email" class="col-sm-3 control-label">New Email</label>
<div class="col-sm-6">
<div class="input-group" title="Enter your email. It will be kept private!" data-placement="right">
<input name="email" type="email" class="form-control" id="email" value="" placeholder="your@email.com" data-error="Email required" autocomplete="email" required>
<span class="input-group-addon"><i class="fa fa-fw fa-envelope"></i></span>
</div>
<div class="help-block with-errors"></div>
</div>
</div>
</div>
@@ -180,124 +93,137 @@
<div class="row">
<div class="col-sm-12">
<div class="form-group">
<label class="col-sm-3 control-label"></label>
<label for="email_confirm" class="col-sm-3 control-label">Confirm Email</label>
<div class="col-sm-6">
<p id="{{captchaImageWrapperId}}">
<img id="{{captchaImageId}}" src="">
</p>
</div>
</div>
</div>
</div>
<div class="row">
<div class="row">
<div class="col-sm-12">
<div class="form-group">
<label for="captcha" class="col-sm-3 control-label">Captcha</label>
<div class="col-sm-6">
<div class="input-group" title="Enter the characters seen above" data-placement="right">
<input name="captcha" type="text" class="form-control" id="captcha" placeholder="" data-minlength="6" data-minlength-error="Min. of 6 characters" autocomplete="off" required>
<span class="input-group-addon"><i class="fa fa-fw fa-refresh"></i></span>
</div>
<div class="help-block with-errors"></div>
<div class="input-group" title="Confirm your email" data-placement="right">
<input name="email_confirm" type="email" class="form-control" id="email_confirm" value="" placeholder="your@email.com" data-error="Email required" data-match="#email" data-match-error="Email fields do not match" autocomplete="email" required>
<span class="input-group-addon"><i class="fa fa-fw fa-envelope"></i></span>
</div>
<div class="help-block with-errors"></div>
</div>
</div>
</div>
</div>
</div>
<div class="{{formErrorContainerClass}} alert alert-danger" style="display: none;">
<span class="txt-color txt-color-danger">Error</span>
<small> (important non-critical information)</small>
</div>
</form>
</div>
{{! api tab ================================================================================================ }}
<div role="tabpanel" class="tab-pane fade" id="pf-dialog-settings-api">
<form role="form" class="form-horizontal">
<div class="row">
<div class="col-sm-11">
<blockquote>
<p>
API Key(s) are required to use <em class="pf-brand">pathfinder</em>.
Don't have one? - Create a new key with an empty 'Access Mask' (8).
{{! Captcha }}
<div class="row">
<div class="col-sm-12">
<div class="form-group">
<label class="col-sm-3 control-label"></label>
<div class="col-sm-9">
<p id="{{captchaImageWrapperId}}" class="form-control-static">
<i class="fa fa-fw fa-lg fa-refresh pull-right pf-dialog-icon-button collapsed"></i>
<img id="{{captchaImageId}}" src="">
</p>
<small>Get your new/custom API Key from
<a href="https://community.eveonline.com/support/api-key/CreatePredefined?accessMask=8" target="_blank">here</a>
and come back to this page.
</small>
</blockquote>
</div>
</div>
{{#userData.api}}
<div class="row {{cloneApiRowClass}}">
<div class="col-sm-4">
<div class="form-group">
<label for="keyId" class="col-sm-4 control-label">Key ID</label>
<div class="col-sm-8">
<input name="keyId[]" type="text" value="{{keyId}}" class="form-control" id="keyId" placeholder="123456" data-error="Field is required" data-minlength="4" data-minlength-error="Min. of 4 characters" required>
<div class="help-block with-errors"></div>
</div>
</div>
</div>
<div class="col-sm-5">
<div class="form-group">
<label for="vCode" class="col-sm-2 control-label">vCode</label>
<div class="col-sm-10">
<input name="vCode[]" type="text" value="{{vCode}}" class="form-control" id="vCode" placeholder="XXXXXXXXXX" data-error="Field is required" data-minlength="64" data-minlength-error="Min. of 64 characters" required>
<div class="help-block with-errors"></div>
</div>
</div>
</div>
<div class="col-sm-3">
<button class="btn btn-danger collapsed pull-left {{deleteRowButtonClass}}">
<i class="fa fa-close"></i>
</button>
<button class="btn btn-default collapsed pull-right {{cloneRowButtonClass}}">
<i class="fa fa-fw fa-plus"></i>&nbsp;add
</button>
</div>
</div>
{{/userData.api}}
<div class="{{formErrorContainerClass}} alert alert-danger" style="display: none;">
<span class="txt-color txt-color-danger">Error</span>
<small> (important non-critical information)</small>
</div>
</div>
<div class="{{formWarningContainerClass}} alert alert-warning" style="display: none;">
<span class="txt-color txt-color-warning">Warning</span>
<small> (important non-critical information)</small>
<div class="row">
<div class="row">
<div class="col-sm-12">
<div class="form-group">
<label for="captcha" class="col-sm-3 control-label">Captcha</label>
<div class="col-sm-6">
<div class="input-group" title="Enter Captcha" data-placement="right">
<input name="captcha" type="text" class="form-control" id="captcha" placeholder="" data-error="Captcha required" data-minlength="6" maxlength="6" autocomplete="off" required>
<span class="input-group-addon"><i class="fa fa-fw fa-picture-o"></i></span>
</div>
<div class="help-block with-errors"></div>
</div>
</div>
</div>
</div>
</form>
</div>
</div>
{{! character tab ================================================================================================ }}
<div role="tabpanel" class="tab-pane fade" id="pf-dialog-settings-character">
<form role="form" class="form-horizontal">
</form>
</div>
{{! finish tab ================================================================================================ }}
<div role="tabpanel" class="tab-pane fade" id="pf-dialog-settings-done">
<form role="form" class="form-horizontal">
<h1 style="opacity: 0;" class="text-center txt-color txt-color-green"><strong><i class="fa fa-check fa-lg"></i> Complete</strong></h1>
<h5 class="text-center">Click next to finish</h5>
</form>
</div>
<div class="{{formErrorContainerClass}} alert alert-danger" style="display: none;">
<span class="txt-color txt-color-danger">Error</span>
<small> (important non-critical information)</small>
</div>
</form>
</div>
<div role="tabpanel" class="tab-pane" id="{{settingsShareContainerId}}">
{{! sharing tab ================================================================================================ }}
<form role="form" class="form-horizontal">
<div class="row">
<div class="col-sm-11">
<blockquote>
<p>
Before other pilots, corporations or alliances can invite you to their maps, you have to enable the associated option.
</p>
<small>Check out the "<a href="javascript:void(0);" onclick="$(document).triggerMenuEvent('Manual');">manual</a>" for more information
</small>
</blockquote>
</div>
</div>
{{#userData.character}}
<h2 class="pf-dynamic-area"><img src="{{ccpImageServer}}Character/{{id}}_32.jpg">&nbsp;&nbsp;Private maps "<em class="pf-map-type-private">{{name}}</em>"</h2>
<div class="row">
<div class="col-sm-9">
<div class="checkbox col-sm-10">
<label for="privateSharing">
<input id="privateSharing" type="checkbox" name="privateSharing" data-toggle="toggle" value="1" {{#shared}}checked{{/shared}}>
&nbsp;map invite for private maps
</label>
</div>
</div>
<div class="col-sm-3">
</div>
</div>
{{#corporation}}
<h2 class="pf-dynamic-area"><img src="{{ccpImageServer}}Corporation/{{id}}_32.png">&nbsp;&nbsp;Corporation maps "<em class="pf-map-type-corporation">{{name}}</em>"</h2>
<div class="row">
<div class="col-sm-9">
<div class="checkbox col-sm-10">
<label for="corporationSharing">
<input id="corporationSharing" type="checkbox" name="corporationSharing" data-toggle="toggle" value="1" {{#shared}}checked{{/shared}}>
&nbsp;map invite for corporation maps
</label>
</div>
</div>
<div class="col-sm-3">
</div>
</div>
{{/corporation}}
{{#alliance}}
<h2 class="pf-dynamic-area"><img src="{{ccpImageServer}}Alliance/{{id}}_32.png">&nbsp;&nbsp;Alliance maps "<em class="pf-map-type-alliance">{{name}}</em>"</h2>
<div class="row">
<div class="col-sm-9">
<div class="checkbox col-sm-10">
<label for="allianceSharing">
<input id="allianceSharing" type="checkbox" name="allianceSharing" data-toggle="toggle" value="1" {{#shared}}checked{{/shared}}>
&nbsp;map invite for alliance maps
</label>
</div>
</div>
<div class="col-sm-3">
</div>
</div>
{{/alliance}}
<input type="hidden" name="share">
{{/userData.character}}
</form>
</div>
</div>
</div>

View File

@@ -1,78 +0,0 @@
<div id="{{id}}" >
<form role="form" class="form-horizontal">
<div class="row">
<div class="col-sm-11">
<blockquote>
<p>
Before other pilots, corporations or alliances can invite you to their maps, you have to enable the associated option.
</p>
<small>Check out the "<a href="javascript:void(0);" onclick="$(document).triggerMenuEvent('Manual');">manual</a>" for more information
</small>
</blockquote>
</div>
</div>
{{#userData.character}}
<h2 class="pf-dynamic-area"><img src="{{ccpImageServer}}Character/{{id}}_32.jpg">&nbsp;&nbsp;Private maps "<em class="pf-map-type-private">{{userData.name}}</em>"</h2>
<div class="row">
<div class="col-sm-9">
<div class="checkbox col-sm-10">
<label for="privateSharing">
<input id="privateSharing" type="checkbox" name="privateSharing" data-toggle="toggle" value="1" {{#userData.shared}}checked{{/userData.shared}}>
&nbsp;map invite for private maps
</label>
</div>
</div>
<div class="col-sm-3">
</div>
</div>
{{#corporation}}
<h2 class="pf-dynamic-area"><img src="{{ccpImageServer}}Corporation/{{id}}_32.png">&nbsp;&nbsp;Corporation maps "<em class="pf-map-type-corporation">{{name}}</em>"</h2>
<div class="row">
<div class="col-sm-9">
<div class="checkbox col-sm-10">
<label for="corporationSharing">
<input id="corporationSharing" type="checkbox" name="corporationSharing" data-toggle="toggle" value="1" {{#shared}}checked{{/shared}}>
&nbsp;map invite for corporation maps
</label>
</div>
</div>
<div class="col-sm-3">
</div>
</div>
{{/corporation}}
{{#alliance}}
<h2 class="pf-dynamic-area"><img src="{{ccpImageServer}}Alliance/{{id}}_32.png">&nbsp;&nbsp;Alliance maps "<em class="pf-map-type-alliance">{{name}}</em>"</h2>
<div class="row">
<div class="col-sm-9">
<div class="checkbox col-sm-10">
<label for="allianceSharing">
<input id="allianceSharing" type="checkbox" name="allianceSharing" data-toggle="toggle" value="1" {{#shared}}checked{{/shared}}>
&nbsp;map invite for alliance maps
</label>
</div>
</div>
<div class="col-sm-3">
</div>
</div>
{{/alliance}}
{{/userData.character}}
</form>
</div>

View File

@@ -1,59 +0,0 @@
<div class="row">
<div class="col-sm-11">
<blockquote>
<p>
Please choose your main character. <em>Pathfinder</em> will take these character if you use the <em>OGB</em>.
The <em>IGB</em> will autodetect your current character.
</p>
<small>You can also change your main characters later on.
</small>
</blockquote>
</div>
</div>
<div class="row text-center">
{{#charactersData}}
<div class="col {{gridClass}}">
<div class="pf-dynamic-area">
<div data-id="{{id}}" class="{{imageWrapperClass}}
{{#isMain}}
{{imageWrapperMainClass}}
{{/isMain}}
">
<div class="pf-dialog-image">
<img class="pf-dialog-character-image" src="https://image.eveonline.com/Character/{{id}}_128.jpg" alt="{{characterName}}"/>
<div class="{{imageInfoClass}}">
<div style="width: 128px">
{{#corporation}}
<img src="https://image.eveonline.com/Corporation/{{id}}_32.png"/>
<div class="pf-dialog-character-info-text"><small>{{name}}</small></div>
{{/corporation}}
{{#alliance}}
<img src="https://image.eveonline.com/Alliance/{{id}}_32.png"/>
<div class="pf-dialog-character-info-text"><small>{{name}}</small></div>
{{/alliance}}
</div>
</div>
</div>
<div class="pf-dialog-character-name">
<small>
{{#status}}
<i title="{{label}}" class="fa fa-fw fa-circle pf-user-status {{class}}"></i>
{{/status}}
{{name}}
</small>
</div>
</div>
</div>
</div>
{{/charactersData}}
</div>
<div class="{{formWarningContainerClass}} alert alert-warning" style="display: none;">
<span class="txt-color txt-color-warning">Warning</span>
<small> (important non-critical information)</small>
</div>
<input type="hidden" name="mainCharacterId" value="{{mainCharacter}}">

View File

@@ -12,7 +12,7 @@
</svg>
&nbsp;&nbsp;Menu
</a>
<p class="navbar-text {{userCharacterClass}}" title="settings">
<p class="navbar-text {{userCharacterClass}}" title="switch character" data-easein="flipYIn">
<a href="javascript:void(0);">
<img class="{{userCharacterImageClass}}" src=""/>
<span>{{! will be filled with current user name }}</span>

View File

@@ -7,9 +7,14 @@
</span>
</h1>
<p class="lead">
Status: "<b>{{ @errorData->status }}</b>".
Oooops, Something went wrong!
You have experienced a technical error. We apologize.
You have experienced a technical error. We apologize.<br><br>
Status: '{{ @errorData->status }}'<br>
<check if="{{ @errorData->message }}">
Message: {{ @errorData->message }}
</check>
</p>
</div>
</div>

View File

@@ -1,6 +1,6 @@
<div class="pf-system-info-popover">
<div class="pf-character-info-popover">
<img class="pull-right" src="https://image.eveonline.com//Character/{{created.character.id}}_64.jpg">
<img class="pull-right" src="https://image.eveonline.com//Character/{{created.character.id}}_32.jpg">
<h6>
<small>
@@ -15,7 +15,7 @@
<div style="height: 1px">&nbsp;</div>
<img class="pull-right" src="https://image.eveonline.com//Character/{{updated.character.id}}_64.jpg">
<img class="pull-right" src="https://image.eveonline.com//Character/{{updated.character.id}}_32.jpg">
<h6>
<small>

View File

@@ -0,0 +1,11 @@
<div class="list-group">
{{#otherCharacters}}
<a class="list-group-item" href="{{routes.ssoLogin}}?characterId={{id}}">
<img src="https://image.eveonline.com/Character/{{id}}_32.jpg" alt="{{name}}">
{{name}}
</a>
{{/otherCharacters}}
<a class="list-group-item" target="_self" href="{{routes.ssoLogin}}?characterId=-1">
<i class="fa fa-user-plus fa-fw"></i>add new
</a>
</div>

View File

@@ -55,7 +55,7 @@
<link rel="stylesheet" type="text/css" media="screen" href="/public/css/pathfinder.css?{{ @PATHFINDER.VERSION }}">
</head>
<body class="{{ @bodyClass }}" data-trusted="{{ @trusted }}" data-js-path="{{ @pathJs }}" data-script="{{ @jsView }}" data-invite="{{ @PATHFINDER.REGISTRATION.INVITE }}" data-version="{{ @PATHFINDER.VERSION }}">
<body class="{{ @bodyClass }}" data-trusted="{{ @trusted }}" data-js-path="{{ @pathJs }}" data-script="{{ @jsView }}" data-version="{{ @PATHFINDER.VERSION }}">
<include if="{{ @pageContent }}" href="{{ @pageContent }}"/>

View File

@@ -124,7 +124,7 @@
<div class="container-fluid">
<div class="row text-center">
<div class="col-xs-12">
<a class="pf-sso-login-button {{@registrationStatusButton}}" href="{{ 'sso','action=requestAuthorization' | alias }}" type="button" tabindex="3" title="{{@registrationStatusTitle}}">&nbsp;</a><br>
<a class="pf-sso-login-button {{@registrationStatusButton}}" target="_self" href="{{ 'sso','action=requestAuthorization' | alias }}" type="button" tabindex="3" title="{{@registrationStatusTitle}}">&nbsp;</a><br>
<a class="font-lg" target="_blank" href="http://community.eveonline.com/news/dev-blogs/eve-online-sso-and-what-you-need-to-know">What is this?</a>
</div>
</div>

View File

@@ -279,19 +279,6 @@
</tr>
</thead>
<tbody>
<tr>
<td>Invite (private mode)</td>
<td class="text-right">
<check if="{{ @PATHFINDER.REGISTRATION.INVITE }}">
<true>
<kbd class="txt-color txt-color-success">enabled</kbd>
</true>
<false>
<kbd class="txt-color txt-color-warning">disabled</kbd>
</false>
</check>
</td>
</tr>
<tr>
<td>Max. invite limit</td>
<td class="text-right"><kbd>{{ @PATHFINDER.REGISTRATION.INVITE_LIMIT }}</kbd></td>

View File

@@ -77,13 +77,6 @@
}
}
// buttons ============================================================================
// hide some button until they get triggered by JS
.pf-dialog-finish-button, .pf-dialog-prev-button{
display: none;
}
}
// settings dialog ======================================================================
@@ -94,131 +87,18 @@
padding: 4px 7px 3px;
}
// show add button only on last row
.pf-dialog-api-row:not(:nth-last-child(3)) .pf-dialog-clone-button{
display: none;
}
// hide delete button if there is just a single api row
.pf-dialog-api-row:nth-child(2):nth-last-child(3) .pf-dialog-delete-button{
display: none;
}
// captcha image
#pf-dialog-captcha-wrapper{
margin: 0;
padding: 3px 0;
}
.pf-dynamic-area{
display: inline-block;
margin: 10px 5px 20px 5px;
padding: 10px 10px 5px 10px;
@include border-radius(10px);
// character images
.pf-dialog-image-wrapper{
opacity: 0;
width: 128px;
border: 2px solid $gray-light;
@include border-radius(8px);
@include transition( border-color 0.2s ease-out, box-shadow 0.2s ease-out);
@include transform( translate3d(0, 0, 0) );
will-change: border-color, transition;
overflow: hidden;
cursor: pointer;
display: inline-block;
background-color: $gray-darker;
box-sizing: content-box; // because of the borders and the fix img with of 128
// not main character star
&:before{
content: "\f005";
font-family: FontAwesome;
position: absolute;
z-index: 10;
left: 6px;
top: 4px;
color: $gray-lighter;
@include transition(color 0.2s ease-out);
}
&.pf-dialog-character-main{
border-color: $orange-dark;
&:hover{
border-color: $orange;
}
// mein character star
&:before{
color: $orange;
}
}
&:hover{
border-color: $teal-dark;
@include box-shadow(0 4px 10px rgba(0,0,0, 0.4));
.pf-dialog-character-name{
color: $teal-lighter;
}
.pf-dialog-character-image{
@include filter(grayscale(50%))
}
&:before{
color: $orange;
}
}
.pf-dialog-image{
overflow: hidden;
width: 128px;
height: 128px;
position: relative;
// info element visible on hover
.pf-dialog-character-info{
position: absolute;
top: 0;
left: 0;
width: 0; // trigger by js
height: 100%;
background: rgba($gray, 0.80);
overflow: hidden;
will-change: width, transition;
padding: 10px 0;
.pf-dialog-character-info-text{
line-height: 25px;
}
}
}
.pf-dialog-character-name{
font-size: 13px;
line-height: 30px;
border-top: 1px solid $gray-dark;
@include transition( color 0.2s ease-out );
}
.pf-dialog-character-image{
@include transition(all 0.3s ease-out);
@include filter(grayscale(0%));
}
}
}
}
// map settings dialog ====================================
#pf-map-dialog{
#pf-map-dialog-user-select,
#pf-map-dialog-character-select,
#pf-map-dialog-corporation-select,
#pf-map-dialog-alliance-select,{
#pf-map-dialog-alliance-select{
width: 300px;
}
}

View File

@@ -63,6 +63,7 @@ em{
// icon buttons =====================================================
.pf-dialog-icon-button{
cursor: pointer;
margin-top: 2px;
@include transition(color 0.15s ease-out);
&:not(.collapsed), &:hover{

View File

@@ -2,11 +2,11 @@
// popover =================================================================
.pf-system-info-popover{
.pf-character-info-popover{
display: initial;
img{
width: 39px
.popover-content{
padding: 0; // overwrite default popover padding
}
h6{
@@ -19,6 +19,34 @@
margin-top: 7px;
margin-bottom: 10px;
}
.list-group{
margin: 0;
.list-group-item{
color: $gray-dark;
&:hover{
color: $gray-darkest;
}
&.disabled{
background-color: $gray;
color: $gray-light;
cursor: not-allowed;
}
img{
width: 30px;
margin: -8px 10px -6px -8px;
border-radius: 0;
}
i{
margin-right: 20px;
}
}
}
}
// system info module ======================================================
@@ -27,6 +55,7 @@
// breadcrumb
h5{
text-transform: capitalize;
line-height: 16px;
}
// dynamic area specific for the description field