closed #138 added new cookie based login
This commit is contained in:
@@ -19,9 +19,9 @@ class AccessController extends Controller {
|
||||
function beforeroute(\Base $f3) {
|
||||
parent::beforeroute($f3);
|
||||
|
||||
// Any CMS route of a child class of this one, requires a
|
||||
// valid logged in user!
|
||||
$loginCheck = $this->checkLogIn($f3);
|
||||
// Any route/endpoint of a child class of this one,
|
||||
// requires a valid logged in user!
|
||||
$loginCheck = $this->checkLogTimer($f3);
|
||||
|
||||
if( !$loginCheck ){
|
||||
// no user found or LogIn timer expired
|
||||
@@ -29,32 +29,4 @@ class AccessController extends Controller {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* checks weather a user is currently logged in
|
||||
* @param \Base $f3
|
||||
* @return bool
|
||||
*/
|
||||
private function checkLogIn($f3){
|
||||
$loginCheck = false;
|
||||
|
||||
if($f3->get(Api\User::SESSION_KEY_CHARACTER_TIME) > 0){
|
||||
// check logIn time
|
||||
$logInTime = new \DateTime();
|
||||
$logInTime->setTimestamp( $f3->get(Api\User::SESSION_KEY_CHARACTER_TIME) );
|
||||
$now = new \DateTime();
|
||||
|
||||
$timeDiff = $now->diff($logInTime);
|
||||
|
||||
$minutes = $timeDiff->days * 60 * 24 * 60;
|
||||
$minutes += $timeDiff->h * 60;
|
||||
$minutes += $timeDiff->i;
|
||||
|
||||
if($minutes <= $f3->get('PATHFINDER.TIMER.LOGGED')){
|
||||
$loginCheck = true;
|
||||
}
|
||||
}
|
||||
|
||||
return $loginCheck;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -568,7 +568,6 @@ class Route extends \Controller\AccessController {
|
||||
$item = false;
|
||||
}
|
||||
$data[0]->reset();
|
||||
|
||||
}
|
||||
|
||||
}, [$map, $validMaps, $activeCharacter]);
|
||||
|
||||
@@ -84,6 +84,35 @@ class User extends Controller\Controller{
|
||||
return $login;
|
||||
}
|
||||
|
||||
/**
|
||||
* validate cookie character information
|
||||
* -> return character data (if valid)
|
||||
* @param \Base $f3
|
||||
*/
|
||||
public function getCookieCharacter($f3){
|
||||
$data = $f3->get('POST');
|
||||
|
||||
$return = (object) [];
|
||||
$return->error = [];
|
||||
|
||||
if( !empty($data['cookie']) ){
|
||||
if( !empty($cookieData = $this->getCookieByName($data['cookie']) )){
|
||||
// cookie data is valid -> validate data against DB (security check!)
|
||||
if( !empty($characters = $this->getCookieCharacters(array_slice($cookieData, 0, 1, true))) ){
|
||||
// character is valid and allowed to login
|
||||
$return->character = reset($characters)->getData();
|
||||
}else{
|
||||
$characterError = (object) [];
|
||||
$characterError->type = 'warning';
|
||||
$characterError->message = 'This can happen through "invalid cookie data", "login restrictions", "CREST problems".';
|
||||
$return->error[] = $characterError;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
echo json_encode($return);
|
||||
}
|
||||
|
||||
/**
|
||||
* get captcha image and store key to session
|
||||
* @param \Base $f3
|
||||
@@ -149,64 +178,6 @@ class User extends Controller\Controller{
|
||||
parent::logOut($f3);
|
||||
}
|
||||
|
||||
/**
|
||||
* save/update "map sharing" configurations for all map types
|
||||
* the user has access to
|
||||
* @param \Base $f3
|
||||
*/
|
||||
public function saveSharingConfig(\Base $f3){
|
||||
$data = $f3->get('POST');
|
||||
|
||||
$return = (object) [];
|
||||
|
||||
$activeCharacter = $this->getCharacter();
|
||||
|
||||
if($activeCharacter){
|
||||
$privateSharing = 0;
|
||||
$corporationSharing = 0;
|
||||
$allianceSharing = 0;
|
||||
|
||||
// form values
|
||||
if(isset($data['formData'])){
|
||||
$formData = $data['formData'];
|
||||
|
||||
if(isset($formData['privateSharing'])){
|
||||
$privateSharing = 1;
|
||||
}
|
||||
|
||||
if(isset($formData['corporationSharing'])){
|
||||
$corporationSharing = 1;
|
||||
}
|
||||
|
||||
if(isset($formData['allianceSharing'])){
|
||||
$allianceSharing = 1;
|
||||
}
|
||||
}
|
||||
|
||||
$activeCharacter->shared = $privateSharing;
|
||||
$activeCharacter = $activeCharacter->save();
|
||||
|
||||
// update corp/ally ---------------------------------------------------------------
|
||||
$corporation = $activeCharacter->getCorporation();
|
||||
$alliance = $activeCharacter->getAlliance();
|
||||
|
||||
if(is_object($corporation)){
|
||||
$corporation->shared = $corporationSharing;
|
||||
$corporation->save();
|
||||
}
|
||||
|
||||
if(is_object($alliance)){
|
||||
$alliance->shared = $allianceSharing;
|
||||
$alliance->save();
|
||||
}
|
||||
|
||||
$user = $activeCharacter->getUser();
|
||||
$return->userData = $user->getData();
|
||||
}
|
||||
|
||||
echo json_encode($return);
|
||||
}
|
||||
|
||||
/**
|
||||
* update user account data
|
||||
* -> a fresh user automatically generated on first login with a new character
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
|
||||
namespace Controller;
|
||||
use Controller\Ccp as Ccp;
|
||||
use Model;
|
||||
|
||||
class AppController extends Controller {
|
||||
|
||||
@@ -43,6 +44,12 @@ class AppController extends Controller {
|
||||
|
||||
// JS main file
|
||||
$f3->set('jsView', 'login');
|
||||
|
||||
// characters from cookies
|
||||
$f3->set('cookieCharacters', $this->getCookieByName(self::COOKIE_PREFIX_CHARACTER, true));
|
||||
$f3->set('getCharacterGrid', function($characters){
|
||||
return ( ((12 / count($characters)) <= 4) ? 4 : (12 / count($characters)) );
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
@@ -52,6 +52,7 @@ class Sso extends Api\User{
|
||||
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';
|
||||
const ERROR_COOKIE_LOGIN = 'Login from Cookie failed. Please retry by CCP SSO';
|
||||
|
||||
/**
|
||||
* CREST "Scopes" are used by pathfinder
|
||||
@@ -156,7 +157,7 @@ class Sso extends Api\User{
|
||||
// get character data from CREST
|
||||
$characterData = $this->getCharacterData($accessData->accessToken);
|
||||
|
||||
if(isset($characterData->character)){
|
||||
if( isset($characterData->character) ){
|
||||
// add "ownerHash" and CREST tokens
|
||||
$characterData->character['ownerHash'] = $verificationCharacterData->CharacterOwnerHash;
|
||||
$characterData->character['crestAccessToken'] = $accessData->accessToken;
|
||||
@@ -203,6 +204,9 @@ class Sso extends Api\User{
|
||||
$loginCheck = $this->loginByCharacter($characterModel);
|
||||
|
||||
if($loginCheck){
|
||||
// set "login" cookie
|
||||
$this->setLoginCookie($characterModel);
|
||||
|
||||
// route to "map"
|
||||
$f3->reroute('@map');
|
||||
}else{
|
||||
@@ -235,6 +239,38 @@ class Sso extends Api\User{
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* login by cookie
|
||||
* @param \Base $f3
|
||||
*/
|
||||
public function login(\Base $f3){
|
||||
$data = (array)$f3->get('GET');
|
||||
$character = null;
|
||||
|
||||
if( !empty($data['cookie']) ){
|
||||
if( !empty($cookieData = $this->getCookieByName($data['cookie']) )){
|
||||
// cookie data is valid -> validate data against DB (security check!)
|
||||
if( !empty($characters = $this->getCookieCharacters(array_slice($cookieData, 0, 1, true))) ){
|
||||
// character is valid and allowed to login
|
||||
$character = $characters[$data['cookie']];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if( is_object($character)){
|
||||
// login by character
|
||||
$loginCheck = $this->loginByCharacter($character);
|
||||
if($loginCheck){
|
||||
// route to "map"
|
||||
$f3->reroute('@map');
|
||||
}
|
||||
}
|
||||
|
||||
// on error -> route back to login form
|
||||
$f3->set(self::SESSION_KEY_SSO_ERROR, self::ERROR_COOKIE_LOGIN);
|
||||
$f3->reroute('@login');
|
||||
}
|
||||
|
||||
/**
|
||||
* get a valid "access_token" for oAuth 2.0 verification
|
||||
* -> if $authCode is set -> request NEW "access_token"
|
||||
@@ -357,7 +393,7 @@ class Sso extends Api\User{
|
||||
* @param $accessToken
|
||||
* @return mixed|null
|
||||
*/
|
||||
protected function verifyCharacterData($accessToken){
|
||||
public function verifyCharacterData($accessToken){
|
||||
$verifyUserUrl = self::getVerifyUserEndpoint();
|
||||
$verifyUrlParts = parse_url($verifyUserUrl);
|
||||
$characterData = null;
|
||||
@@ -492,7 +528,7 @@ class Sso extends Api\User{
|
||||
* @param array $additionalOptions
|
||||
* @return object
|
||||
*/
|
||||
protected function getCharacterData($accessToken, $additionalOptions = []){
|
||||
public function getCharacterData($accessToken, $additionalOptions = []){
|
||||
$endpoints = $this->getEndpoints($accessToken, $additionalOptions);
|
||||
$characterData = (object) [];
|
||||
|
||||
|
||||
@@ -13,6 +13,10 @@ use DB;
|
||||
|
||||
class Controller {
|
||||
|
||||
// cookie specific keys (names)
|
||||
const COOKIE_NAME_STATE = 'cookie';
|
||||
const COOKIE_PREFIX_CHARACTER = 'char';
|
||||
|
||||
/**
|
||||
* @var \Base
|
||||
*/
|
||||
@@ -114,6 +118,200 @@ class Controller {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* get cookies "state" information
|
||||
* -> whether user accepts cookies
|
||||
* @return bool
|
||||
*/
|
||||
protected function getCookieState(){
|
||||
return (bool)count( $this->getCookieByName(self::COOKIE_NAME_STATE) );
|
||||
}
|
||||
|
||||
/**
|
||||
* search for existing cookies
|
||||
* -> either a specific cookie by its name
|
||||
* -> or get multiple cookies by their name (search by prefix)
|
||||
* @param $cookieName
|
||||
* @param bool $prefix
|
||||
* @return array
|
||||
*/
|
||||
protected function getCookieByName($cookieName, $prefix = false){
|
||||
$data = [];
|
||||
|
||||
if(!empty($cookieName)){
|
||||
$cookieData = (array)$this->getF3()->get('COOKIE');
|
||||
if($prefix === true){
|
||||
// look for multiple cookies with same prefix
|
||||
foreach($cookieData as $name => $value){
|
||||
if(strpos($name, $cookieName) === 0){
|
||||
$data[$name] = $value;
|
||||
}
|
||||
}
|
||||
}elseif( isset($cookieData[$cookieName]) ){
|
||||
// look for a single cookie
|
||||
$data[$cookieName] = $cookieData[$cookieName];
|
||||
}
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* set/update logged in cookie by character model
|
||||
* -> store validation data in DB
|
||||
* @param Model\CharacterModel $character
|
||||
*/
|
||||
protected function setLoginCookie(Model\CharacterModel $character){
|
||||
|
||||
if( $this->getCookieState() ){
|
||||
$expireSeconds = (int) $this->getF3()->get('PATHFINDER.LOGIN.COOKIE_EXPIRE');
|
||||
$expireSeconds *= 24 * 60 * 60;
|
||||
|
||||
$timezone = new \DateTimeZone( $this->getF3()->get('TZ') );
|
||||
$expireTime = new \DateTime('now', $timezone);
|
||||
|
||||
// add cookie expire time
|
||||
$expireTime->add(new \DateInterval('PT' . $expireSeconds . 'S'));
|
||||
|
||||
// unique "selector" -> to facilitate database look-ups (small size)
|
||||
// -> This is preferable to simply using the database id field,
|
||||
// which leaks the number of active users on the application
|
||||
$selector = bin2hex(mcrypt_create_iv(12, MCRYPT_DEV_URANDOM));
|
||||
|
||||
// generate unique "validator" (strong encryption)
|
||||
// -> plaintext set to user (cookie), hashed version of this in DB
|
||||
$size = mcrypt_get_iv_size(MCRYPT_CAST_256, MCRYPT_MODE_CFB);
|
||||
$validator = bin2hex(mcrypt_create_iv($size, MCRYPT_DEV_URANDOM));
|
||||
|
||||
// generate unique cookie token
|
||||
$token = hash('sha256', $validator);
|
||||
|
||||
// get unique cookie name for this character
|
||||
$name = md5($character->name);
|
||||
|
||||
$authData = [
|
||||
'characterId' => $character,
|
||||
'selector' => $selector,
|
||||
'token' => $token,
|
||||
'expires' => $expireTime->format('Y-m-d H:i:s')
|
||||
];
|
||||
|
||||
$authenticationModel = $character->rel('characterTokens');
|
||||
$authenticationModel->copyfrom($authData);
|
||||
$authenticationModel->save();
|
||||
|
||||
$cookieValue = implode(':', [$selector, $validator]);
|
||||
|
||||
// get cookie name -> save new one OR update existing cookie
|
||||
$cookieName = 'COOKIE.' . self::COOKIE_PREFIX_CHARACTER . '_' . $name;
|
||||
$this->getF3()->set($cookieName, $cookieValue, $expireSeconds);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* get characters from given cookie data
|
||||
* -> validate cookie data
|
||||
* -> validate characters
|
||||
* @param array $cookieData
|
||||
* @return array
|
||||
* @throws \Exception
|
||||
*/
|
||||
protected function getCookieCharacters($cookieData = []){
|
||||
$characters = [];
|
||||
|
||||
if(
|
||||
$this->getCookieState() &&
|
||||
!empty($cookieData)
|
||||
){
|
||||
/**
|
||||
* @var $characterAuth Model\CharacterAuthenticationModel
|
||||
*/
|
||||
$characterAuth = Model\BasicModel::getNew('CharacterAuthenticationModel');
|
||||
|
||||
$timezone = new \DateTimeZone( $this->getF3()->get('TZ') );
|
||||
$currentTime = new \DateTime('now', $timezone);
|
||||
|
||||
foreach($cookieData as $name => $value){
|
||||
// remove invalid cookies
|
||||
$invalidCookie = false;
|
||||
|
||||
$data = explode(':', $value);
|
||||
if(count($data) === 2){
|
||||
// cookie data is well formatted
|
||||
$characterAuth->getByForeignKey('selector', $data[0], ['limit' => 1]);
|
||||
|
||||
// validate expire data
|
||||
// validate token
|
||||
if(
|
||||
!$characterAuth->dry() &&
|
||||
strtotime($characterAuth->expires) >= $currentTime->getTimestamp() &&
|
||||
hash_equals($characterAuth->token, hash('sha256', $data[1]))
|
||||
){
|
||||
// cookie information is valid
|
||||
// -> try to update character information from CREST
|
||||
// e.g. Corp has changed, this also ensures valid "access_token"
|
||||
/**
|
||||
* @var $character Model\CharacterModel
|
||||
*/
|
||||
$character = $characterAuth->characterId;
|
||||
$updateStatus = $character->updateFromCrest();
|
||||
|
||||
// check if character still has user (is not the case of "ownerHash" changed
|
||||
// check if character is still authorized to log in (e.g. corp/ally or config has changed
|
||||
// -> do NOT remove cookie on failure. This can be a temporary problem (e.g. CREST is down,..)
|
||||
if(
|
||||
empty($updateStatus) &&
|
||||
$character->hasUserCharacter() &&
|
||||
$character->isAuthorized()
|
||||
){
|
||||
$characters[$name] = $character;
|
||||
}
|
||||
}else{
|
||||
$invalidCookie = true;
|
||||
}
|
||||
$characterAuth->reset();
|
||||
}else{
|
||||
$invalidCookie = true;
|
||||
}
|
||||
|
||||
// remove invalid cookie
|
||||
if($invalidCookie){
|
||||
$this->getF3()->clear('COOKIE.' . $name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $characters;
|
||||
}
|
||||
|
||||
/**
|
||||
* checks whether a user is currently logged in
|
||||
* @param \Base $f3
|
||||
* @return bool
|
||||
*/
|
||||
protected function checkLogTimer($f3){
|
||||
$loginCheck = false;
|
||||
|
||||
if($f3->get(Api\User::SESSION_KEY_CHARACTER_TIME) > 0){
|
||||
// check logIn time
|
||||
$logInTime = new \DateTime();
|
||||
$logInTime->setTimestamp( $f3->get(Api\User::SESSION_KEY_CHARACTER_TIME) );
|
||||
$now = new \DateTime();
|
||||
|
||||
$timeDiff = $now->diff($logInTime);
|
||||
|
||||
$minutes = $timeDiff->days * 60 * 24 * 60;
|
||||
$minutes += $timeDiff->h * 60;
|
||||
$minutes += $timeDiff->i;
|
||||
|
||||
if($minutes <= $f3->get('PATHFINDER.TIMER.LOGGED')){
|
||||
$loginCheck = true;
|
||||
}
|
||||
}
|
||||
|
||||
return $loginCheck;
|
||||
}
|
||||
|
||||
/**
|
||||
* get current character model
|
||||
* @param int $ttl
|
||||
@@ -127,12 +325,15 @@ class Controller {
|
||||
$characterId = (int)$this->getF3()->get(Api\User::SESSION_KEY_CHARACTER_ID);
|
||||
if($characterId){
|
||||
/**
|
||||
* @var $characterModel \Model\CharacterModel
|
||||
* @var $characterModel Model\CharacterModel
|
||||
*/
|
||||
$characterModel = Model\BasicModel::getNew('CharacterModel');
|
||||
$characterModel->getById($characterId, $ttl);
|
||||
|
||||
if( !$characterModel->dry() ){
|
||||
if(
|
||||
!$characterModel->dry() &&
|
||||
$characterModel->hasUserCharacter()
|
||||
){
|
||||
$character = &$characterModel;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,6 +40,7 @@ class Setup extends Controller {
|
||||
|
||||
'Model\UserCharacterModel',
|
||||
'Model\CharacterModel',
|
||||
'Model\CharacterAuthenticationModel',
|
||||
'Model\CharacterLogModel',
|
||||
|
||||
'Model\SystemModel',
|
||||
|
||||
@@ -375,18 +375,18 @@ abstract class BasicModel extends \DB\Cortex {
|
||||
/**
|
||||
* get dataSet by foreign column (single result)
|
||||
* @param $key
|
||||
* @param $id
|
||||
* @param $value
|
||||
* @param array $options
|
||||
* @param int $ttl
|
||||
* @return \DB\Cortex
|
||||
*/
|
||||
public function getByForeignKey($key, $id, $options = [], $ttl = 60){
|
||||
public function getByForeignKey($key, $value, $options = [], $ttl = 60){
|
||||
|
||||
$querySet = [];
|
||||
$query = [];
|
||||
if($this->exists($key)){
|
||||
$query[] = $key . " = :" . $key;
|
||||
$querySet[':' . $key] = $id;
|
||||
$querySet[':' . $key] = $value;
|
||||
}
|
||||
|
||||
// check active column
|
||||
|
||||
54
app/main/model/characterauthenticationmodel.php
Normal file
54
app/main/model/characterauthenticationmodel.php
Normal file
@@ -0,0 +1,54 @@
|
||||
<?php
|
||||
/**
|
||||
* Created by PhpStorm.
|
||||
* User: Exodus
|
||||
* Date: 25.04.2016
|
||||
* Time: 19:33
|
||||
*/
|
||||
|
||||
namespace Model;
|
||||
|
||||
use DB\SQL\Schema;
|
||||
|
||||
class CharacterAuthenticationModel extends BasicModel{
|
||||
|
||||
protected $table = 'character_authentication';
|
||||
|
||||
protected $fieldConf = [
|
||||
'active' => [
|
||||
'type' => Schema::DT_BOOL,
|
||||
'nullable' => false,
|
||||
'default' => 1,
|
||||
'index' => true
|
||||
],
|
||||
'characterId' => [
|
||||
'type' => Schema::DT_INT,
|
||||
'index' => true,
|
||||
'belongs-to-one' => 'Model\CharacterModel',
|
||||
'constraint' => [
|
||||
[
|
||||
'table' => 'character',
|
||||
'on-delete' => 'CASCADE'
|
||||
]
|
||||
]
|
||||
],
|
||||
'selector' => [
|
||||
'type' => Schema::DT_VARCHAR128,
|
||||
'nullable' => false,
|
||||
'default' => '',
|
||||
'index' => true,
|
||||
'unique' => true
|
||||
],
|
||||
'token' => [
|
||||
'type' => Schema::DT_VARCHAR128,
|
||||
'nullable' => false,
|
||||
'default' => '',
|
||||
'index' => true
|
||||
],
|
||||
'expires' => [
|
||||
'type' => Schema::DT_TIMESTAMP,
|
||||
'default' => Schema::DF_CURRENT_TIMESTAMP,
|
||||
'index' => true
|
||||
]
|
||||
];
|
||||
}
|
||||
@@ -9,7 +9,7 @@
|
||||
namespace Model;
|
||||
|
||||
use Controller;
|
||||
use Controller\Ccp;
|
||||
use Controller\Ccp\Sso as Sso;
|
||||
use DB\SQL\Schema;
|
||||
use Data\Mapper as Mapper;
|
||||
|
||||
@@ -93,6 +93,9 @@ class CharacterModel extends BasicModel {
|
||||
],
|
||||
'characterMaps' => [
|
||||
'has-many' => ['Model\CharacterMapModel', 'characterId']
|
||||
],
|
||||
'characterTokens' => [
|
||||
'has-many' => ['Model\CharacterAuthenticationModel', 'characterId']
|
||||
]
|
||||
];
|
||||
|
||||
@@ -234,7 +237,7 @@ class CharacterModel extends BasicModel {
|
||||
* get CREST API "access_token" from OAuth
|
||||
* @return bool|string
|
||||
*/
|
||||
private function getAccessToken(){
|
||||
public function getAccessToken(){
|
||||
$accessToken = false;
|
||||
|
||||
// check if there is already an "accessToken" for this user
|
||||
@@ -252,7 +255,7 @@ class CharacterModel extends BasicModel {
|
||||
// add expire time buffer for this "accessToken"
|
||||
// token should be marked as "deprecated" BEFORE it actually expires.
|
||||
$timeBuffer = 2 * 60;
|
||||
$tokenTime->add(new \DateInterval('PT' . (Ccp\Sso::ACCESS_KEY_EXPIRE_TIME - $timeBuffer) . 'S'));
|
||||
$tokenTime->add(new \DateInterval('PT' . (Sso::ACCESS_KEY_EXPIRE_TIME - $timeBuffer) . 'S'));
|
||||
|
||||
$now = new \DateTime('now', $timezone);
|
||||
if($tokenTime->getTimestamp() > $now->getTimestamp()){
|
||||
@@ -266,7 +269,7 @@ class CharacterModel extends BasicModel {
|
||||
!empty($this->crestRefreshToken)
|
||||
){
|
||||
// no accessToken found OR token is deprecated
|
||||
$ssoController = new Ccp\Sso();
|
||||
$ssoController = new Sso();
|
||||
$accessData = $ssoController->refreshAccessToken($this->crestRefreshToken);
|
||||
|
||||
if(
|
||||
@@ -363,7 +366,7 @@ class CharacterModel extends BasicModel {
|
||||
}else{
|
||||
// get Location Data from CREST endpoint
|
||||
// user is NOT with IGB online OR has not jet set "trusted" page
|
||||
$ssoController = new Ccp\Sso();
|
||||
$ssoController = new Sso();
|
||||
$logData = $ssoController->getCharacterLocationData($this->getAccessToken(), 10, $additionalOptions);
|
||||
|
||||
if($logData['timeout'] === false){
|
||||
@@ -396,6 +399,64 @@ class CharacterModel extends BasicModel {
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* update character data from CCPs CREST API
|
||||
* @return array (some status messages)
|
||||
*/
|
||||
public function updateFromCrest(){
|
||||
$status = [];
|
||||
|
||||
if( $accessToken = $this->getAccessToken() ){
|
||||
// et basic character data
|
||||
// -> this is required for "ownerHash" hash check (e.g. character was sold,..)
|
||||
// -> the "id" check is just for security and should NEVER fail!
|
||||
$ssoController = new Sso();
|
||||
if(
|
||||
!is_null( $verificationCharacterData = $ssoController->verifyCharacterData($accessToken) ) &&
|
||||
$verificationCharacterData->CharacterID === $this->_id
|
||||
){
|
||||
// get character data from CREST
|
||||
$characterData = $ssoController->getCharacterData($accessToken);
|
||||
if( isset($characterData->character) ){
|
||||
$characterData->character['ownerHash'] = $verificationCharacterData->CharacterOwnerHash;
|
||||
|
||||
$corporation = null;
|
||||
$alliance = null;
|
||||
if( isset($characterData->corporation) ){
|
||||
/**
|
||||
* @var $corporation CorporationModel
|
||||
*/
|
||||
$corporation = $this->rel('corporationId');
|
||||
$corporation->getById($characterData->corporation['id'], 0);
|
||||
$corporation->copyfrom($characterData->corporation, ['name', 'isNPC']);
|
||||
$corporation->save();
|
||||
}
|
||||
|
||||
if( isset($characterData->alliance) ){
|
||||
/**
|
||||
* @var $alliance AllianceModel
|
||||
*/
|
||||
$alliance = $this->rel('allianceId');
|
||||
$alliance->getById($characterData->alliance['id'], 0);
|
||||
$alliance->copyfrom($characterData->alliance, ['name']);
|
||||
$alliance->save();
|
||||
}
|
||||
|
||||
$this->copyfrom($characterData->character, ['name', 'ownerHash']);
|
||||
$this->set('corporationId', is_object($corporation) ? $corporation->get('id') : null);
|
||||
$this->set('allianceId', is_object($alliance) ? $corporation->get('id') : null);
|
||||
$this->save();
|
||||
}
|
||||
}else{
|
||||
$status[] = sprintf(Sso::ERROR_VERIFY_CHARACTER, $this->name);
|
||||
}
|
||||
}else{
|
||||
$status[] = sprintf(Sso::ERROR_ACCESS_TOKEN, $this->name);
|
||||
}
|
||||
|
||||
return $status;
|
||||
}
|
||||
|
||||
/**
|
||||
* get the character log entry for this character
|
||||
* @return bool|CharacterLogModel
|
||||
|
||||
@@ -35,6 +35,9 @@ INVITE = 0
|
||||
INVITE_LIMIT = 50
|
||||
|
||||
[PATHFINDER.LOGIN]
|
||||
; expire time (in days) for login cookies
|
||||
COOKIE_EXPIRE = 30
|
||||
|
||||
; restrict login to specific corporations/alliances by id (e.g. 1000166,1000080)
|
||||
CORPORATION =
|
||||
ALLIANCE =
|
||||
|
||||
@@ -12,12 +12,11 @@ define(['jquery'], function($) {
|
||||
// user API
|
||||
getCaptcha: 'api/user/getCaptcha', // ajax URL - get captcha image
|
||||
sendInviteKey: 'api/user/sendInvite', // ajax URL - send registration key
|
||||
getCookieCharacterData: 'api/user/getCookieCharacter', // ajax URL - get character data from cookie
|
||||
logIn: 'api/user/logIn', // ajax URL - login
|
||||
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
|
||||
searchAccess: 'api/access/search', // ajax URL - search user/corporation/ally by name
|
||||
|
||||
244
js/app/login.js
244
js/app/login.js
@@ -43,11 +43,15 @@ define([
|
||||
navigationLinkLicenseClass: 'pf-navbar-license', // class for "license" trigger link
|
||||
navigationVersionLinkClass: 'pf-navbar-version-info', // class for "version information"
|
||||
|
||||
// login form
|
||||
loginFormId: 'pf-login-form', // id for login form
|
||||
loginButtonClass: 'pf-login-button', // class for "login" button(s)
|
||||
registerButtonClass: 'pf-register-button', // class for "register" button(s)
|
||||
loginMessageContainerId: 'pf-login-message-container', // id for login form message container
|
||||
// cookie hint
|
||||
cookieHintId: 'pf-cookie-hint', // id for "cookie hint" element
|
||||
|
||||
// character select
|
||||
characterSelectionClass: 'pf-character-selection', // class for character panel wrapper
|
||||
characterRowAnimateClass: 'pf-character-row-animate', // class for character panel row during animation
|
||||
characterImageWrapperClass: 'pf-character-image-wrapper', // class for image wrapper (animated)
|
||||
characterImageInfoClass: 'pf-character-info', // class for character info layer (visible on hover)
|
||||
dynamicMessageContainerClass: 'pf-dynamic-message-container', // class for "dynamic" (JS) message container
|
||||
|
||||
// gallery
|
||||
galleryId: 'pf-gallery', // id for gallery container
|
||||
@@ -59,49 +63,75 @@ define([
|
||||
animateElementClass: 'pf-animate-on-visible' // class for elements that will be animated to show
|
||||
};
|
||||
|
||||
/**
|
||||
* set a cookie
|
||||
* @param cname
|
||||
* @param cvalue
|
||||
* @param exdays
|
||||
*/
|
||||
var setCookie = function(cname, cvalue, exdays) {
|
||||
var d = new Date();
|
||||
d.setTime(d.getTime() + (exdays*24*60*60*1000));
|
||||
var expires = 'expires=' + d.toUTCString();
|
||||
document.cookie = cname + '=' + cvalue + '; ' + expires;
|
||||
};
|
||||
|
||||
/**
|
||||
* get cookie value by name
|
||||
* @param cname
|
||||
* @returns {*}
|
||||
*/
|
||||
var getCookie = function(cname) {
|
||||
var name = cname + '=';
|
||||
var ca = document.cookie.split(';');
|
||||
|
||||
for(var i = 0; i <ca.length; i++) {
|
||||
var c = ca[i];
|
||||
while (c.charAt(0) === ' ') {
|
||||
c = c.substring(1);
|
||||
}
|
||||
|
||||
if (c.indexOf(name) === 0) {
|
||||
return c.substring(name.length,c.length);
|
||||
}
|
||||
}
|
||||
return '';
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* set page observer
|
||||
*/
|
||||
var setPageObserver = function(){
|
||||
|
||||
// login form =====================================================================================
|
||||
// register buttons ---------------------------------------------
|
||||
$('.' + config.registerButtonClass).on('click', function(e){
|
||||
e.preventDefault();
|
||||
// cookie hint --------------------------------------------------------
|
||||
if(getCookie('cookie') !== '1'){
|
||||
// hint not excepted
|
||||
$('#' + config.cookieHintId).collapse('show');
|
||||
}
|
||||
|
||||
// logout current user (if there e.g. to register a second account)
|
||||
Util.logout({
|
||||
ajaxData: {
|
||||
reroute: 0
|
||||
}
|
||||
});
|
||||
|
||||
// show register/settings dialog
|
||||
$.fn.showSettingsDialog({
|
||||
register: 1,
|
||||
invite : parseInt( $('body').data('invite') )
|
||||
});
|
||||
$('#' + config.cookieHintId + ' .btn-success').on('click', function(){
|
||||
setCookie('cookie', 1, 365);
|
||||
});
|
||||
|
||||
// releases -----------------------------------------------------
|
||||
// releases -----------------------------------------------------------
|
||||
$('.' + config.navigationVersionLinkClass).on('click', function(e){
|
||||
$.fn.releasesDialog();
|
||||
});
|
||||
|
||||
// manual -------------------------------------------------------
|
||||
// manual -------------------------------------------------------------
|
||||
$('.' + config.navigationLinkManualClass).on('click', function(e){
|
||||
e.preventDefault();
|
||||
$.fn.showMapManual();
|
||||
});
|
||||
|
||||
// license ------------------------------------------------------
|
||||
// license ------------------------------------------------------------
|
||||
$('.' + config.navigationLinkLicenseClass).on('click', function(e){
|
||||
e.preventDefault();
|
||||
$.fn.showCreditsDialog(false, true);
|
||||
});
|
||||
|
||||
// tooltips -----------------------------------------------------
|
||||
// tooltips -----------------------------------------------------------
|
||||
var mapTooltipOptions = {
|
||||
toggle: 'tooltip',
|
||||
container: 'body',
|
||||
@@ -169,8 +199,6 @@ define([
|
||||
responseData.error.length > 0
|
||||
){
|
||||
form.showFormMessage(responseData.error);
|
||||
|
||||
|
||||
}else{
|
||||
$('.modal').modal('hide');
|
||||
Util.showNotify({title: 'Registration Key send', text: 'Check your Mails', type: 'success'});
|
||||
@@ -240,7 +268,7 @@ define([
|
||||
return newSlideContent[0];
|
||||
};
|
||||
|
||||
// initialize carousel ------------------------------------------
|
||||
// initialize carousel ------------------------------------------------
|
||||
var carousel = new Gallery([
|
||||
{
|
||||
title: 'IGB',
|
||||
@@ -363,29 +391,31 @@ define([
|
||||
|
||||
var initYoutube = function(){
|
||||
|
||||
$(".youtube").each(function() {
|
||||
$('.youtube').each(function() {
|
||||
// Based on the YouTube ID, we can easily find the thumbnail image
|
||||
$(this).css('background-image', 'url(https://i.ytimg.com/vi/' + this.id + '/sddefault.jpg)');
|
||||
|
||||
// Overlay the Play icon to make it look like a video player
|
||||
$(this).append($('<div/>', {'class': 'play'}));
|
||||
|
||||
$(document).delegate('#'+this.id, 'click', function() {
|
||||
$(document).delegate('#' + this.id, 'click', function() {
|
||||
// Create an iFrame with autoplay set to true
|
||||
var iframe_url = "https://www.youtube.com/embed/" + this.id + "?autoplay=1&autohide=1";
|
||||
if ($(this).data('params')) iframe_url+='&'+$(this).data('params');
|
||||
var iFrameUrl = 'https://www.youtube.com/embed/' + this.id + '?autoplay=1&autohide=1';
|
||||
if ( $(this).data('params') ){
|
||||
iFrameUrl += '&'+$(this).data('params');
|
||||
}
|
||||
|
||||
// The height and width of the iFrame should be the same as parent
|
||||
var iframe = $('<iframe/>', {
|
||||
var iFrame = $('<iframe/>', {
|
||||
frameborder: '0',
|
||||
src: iframe_url,
|
||||
src: iFrameUrl,
|
||||
width: $(this).width(),
|
||||
height: $(this).height(),
|
||||
class: 'pricing-big'
|
||||
});
|
||||
|
||||
// Replace the YouTube thumbnail with YouTube HTML5 Player
|
||||
$(this).replaceWith(iframe);
|
||||
$(this).replaceWith(iFrame);
|
||||
});
|
||||
});
|
||||
};
|
||||
@@ -436,6 +466,148 @@ define([
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* load character data from cookie information
|
||||
* -> all validation is done server side!
|
||||
*/
|
||||
var initCharacterSelect = function(){
|
||||
|
||||
/**
|
||||
* init panel animation for an element
|
||||
* @param imageWrapperElement
|
||||
*/
|
||||
var initCharacterAnimation = function(imageWrapperElement){
|
||||
|
||||
imageWrapperElement.velocity('stop').delay(300).velocity('transition.flipBounceXIn', {
|
||||
display: 'inline-block',
|
||||
stagger: 60,
|
||||
drag: true,
|
||||
duration: 600
|
||||
});
|
||||
|
||||
// Hover effect for character info layer
|
||||
imageWrapperElement.hoverIntent(function(e){
|
||||
var characterInfoElement = $(this).find('.' + config.characterImageInfoClass);
|
||||
|
||||
characterInfoElement.velocity('finish').velocity({
|
||||
width: ['100%', [ 400, 15 ] ]
|
||||
},{
|
||||
easing: 'easeInSine'
|
||||
});
|
||||
}, function(e){
|
||||
var characterInfoElement = $(this).find('.' + config.characterImageInfoClass);
|
||||
|
||||
characterInfoElement.velocity('finish').velocity({
|
||||
width: 0
|
||||
},{
|
||||
duration: 150,
|
||||
easing: 'easeInOutSine'
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* update all character panels -> set CSS class (e.g. after some panels were added/removed,..)
|
||||
*/
|
||||
var updateCharacterPanels = function(){
|
||||
var characterRows = $('.' + config.characterSelectionClass + ' .pf-dynamic-area').parent();
|
||||
var rowClassIdentifier = ((12 / characterRows.length ) <= 4) ? 4 : (12 / characterRows.length);
|
||||
$(characterRows).removeClass().addClass('col-sm-' + rowClassIdentifier);
|
||||
};
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
var removeCharacterPanel = function(panelElement){
|
||||
$(panelElement).velocity('transition.expandOut', {
|
||||
duration: 250,
|
||||
complete: function () {
|
||||
// lock row for CSS animations while removing...
|
||||
$(this).parent().addClass(config.characterRowAnimateClass);
|
||||
|
||||
$(this).parent().velocity({
|
||||
width: 0
|
||||
},{
|
||||
easing: 'ease',
|
||||
duration: 300,
|
||||
complete: function(){
|
||||
$(this).remove();
|
||||
// reset column CSS classes for all existing panels
|
||||
updateCharacterPanels();
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
// request character data for each character panel
|
||||
$('.' + config.characterSelectionClass + ' .pf-dynamic-area').each(function(){
|
||||
var characterElement = $(this);
|
||||
|
||||
characterElement.showLoadingAnimation();
|
||||
|
||||
var requestData = {
|
||||
cookie: characterElement.data('cookie')
|
||||
};
|
||||
|
||||
$.ajax({
|
||||
type: 'POST',
|
||||
url: Init.path.getCookieCharacterData,
|
||||
data: requestData,
|
||||
dataType: 'json',
|
||||
context: {
|
||||
href: characterElement.data('href'),
|
||||
cookieName: requestData.cookie,
|
||||
characterElement: characterElement
|
||||
}
|
||||
}).done(function(responseData){
|
||||
var characterElement = this.characterElement;
|
||||
characterElement.hideLoadingAnimation();
|
||||
|
||||
if(
|
||||
responseData.error &&
|
||||
responseData.error.length > 0
|
||||
){
|
||||
$('.' + config.dynamicMessageContainerClass).showMessage({
|
||||
type: responseData.error[0].type,
|
||||
title: 'Character verification failed',
|
||||
text: responseData.error[0].message
|
||||
});
|
||||
}
|
||||
|
||||
if(responseData.hasOwnProperty('character')){
|
||||
|
||||
var data = {
|
||||
link: this.href,
|
||||
cookieName: this.cookieName,
|
||||
character: responseData.character
|
||||
};
|
||||
|
||||
requirejs(['text!templates/ui/character_panel.html', 'mustache'], function(template, Mustache) {
|
||||
var content = Mustache.render(template, data);
|
||||
characterElement.html(content);
|
||||
|
||||
// show character panel (animation settings)
|
||||
initCharacterAnimation(characterElement.find('.' + config.characterImageWrapperClass));
|
||||
});
|
||||
}else{
|
||||
// character data not available -> remove panel
|
||||
removeCharacterPanel(this.characterElement);
|
||||
}
|
||||
|
||||
}).fail(function( jqXHR, status, error) {
|
||||
var characterElement = this.characterElement;
|
||||
characterElement.hideLoadingAnimation();
|
||||
|
||||
// character data not available -> remove panel
|
||||
removeCharacterPanel(this.characterElement);
|
||||
});
|
||||
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* main init "landing" page
|
||||
*/
|
||||
@@ -490,6 +662,8 @@ define([
|
||||
// hide splash loading animation
|
||||
$('.' + config.splashOverlayClass).hideSplashOverlay();
|
||||
|
||||
initCharacterSelect();
|
||||
|
||||
// init carousel
|
||||
initCarousel();
|
||||
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -12,12 +12,11 @@ define(['jquery'], function($) {
|
||||
// user API
|
||||
getCaptcha: 'api/user/getCaptcha', // ajax URL - get captcha image
|
||||
sendInviteKey: 'api/user/sendInvite', // ajax URL - send registration key
|
||||
getCookieCharacterData: 'api/user/getCookieCharacter', // ajax URL - get character data from cookie
|
||||
logIn: 'api/user/logIn', // ajax URL - login
|
||||
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
|
||||
searchAccess: 'api/access/search', // ajax URL - search user/corporation/ally by name
|
||||
|
||||
@@ -43,11 +43,15 @@ define([
|
||||
navigationLinkLicenseClass: 'pf-navbar-license', // class for "license" trigger link
|
||||
navigationVersionLinkClass: 'pf-navbar-version-info', // class for "version information"
|
||||
|
||||
// login form
|
||||
loginFormId: 'pf-login-form', // id for login form
|
||||
loginButtonClass: 'pf-login-button', // class for "login" button(s)
|
||||
registerButtonClass: 'pf-register-button', // class for "register" button(s)
|
||||
loginMessageContainerId: 'pf-login-message-container', // id for login form message container
|
||||
// cookie hint
|
||||
cookieHintId: 'pf-cookie-hint', // id for "cookie hint" element
|
||||
|
||||
// character select
|
||||
characterSelectionClass: 'pf-character-selection', // class for character panel wrapper
|
||||
characterRowAnimateClass: 'pf-character-row-animate', // class for character panel row during animation
|
||||
characterImageWrapperClass: 'pf-character-image-wrapper', // class for image wrapper (animated)
|
||||
characterImageInfoClass: 'pf-character-info', // class for character info layer (visible on hover)
|
||||
dynamicMessageContainerClass: 'pf-dynamic-message-container', // class for "dynamic" (JS) message container
|
||||
|
||||
// gallery
|
||||
galleryId: 'pf-gallery', // id for gallery container
|
||||
@@ -59,49 +63,75 @@ define([
|
||||
animateElementClass: 'pf-animate-on-visible' // class for elements that will be animated to show
|
||||
};
|
||||
|
||||
/**
|
||||
* set a cookie
|
||||
* @param cname
|
||||
* @param cvalue
|
||||
* @param exdays
|
||||
*/
|
||||
var setCookie = function(cname, cvalue, exdays) {
|
||||
var d = new Date();
|
||||
d.setTime(d.getTime() + (exdays*24*60*60*1000));
|
||||
var expires = 'expires=' + d.toUTCString();
|
||||
document.cookie = cname + '=' + cvalue + '; ' + expires;
|
||||
};
|
||||
|
||||
/**
|
||||
* get cookie value by name
|
||||
* @param cname
|
||||
* @returns {*}
|
||||
*/
|
||||
var getCookie = function(cname) {
|
||||
var name = cname + '=';
|
||||
var ca = document.cookie.split(';');
|
||||
|
||||
for(var i = 0; i <ca.length; i++) {
|
||||
var c = ca[i];
|
||||
while (c.charAt(0) === ' ') {
|
||||
c = c.substring(1);
|
||||
}
|
||||
|
||||
if (c.indexOf(name) === 0) {
|
||||
return c.substring(name.length,c.length);
|
||||
}
|
||||
}
|
||||
return '';
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* set page observer
|
||||
*/
|
||||
var setPageObserver = function(){
|
||||
|
||||
// login form =====================================================================================
|
||||
// register buttons ---------------------------------------------
|
||||
$('.' + config.registerButtonClass).on('click', function(e){
|
||||
e.preventDefault();
|
||||
// cookie hint --------------------------------------------------------
|
||||
if(getCookie('cookie') !== '1'){
|
||||
// hint not excepted
|
||||
$('#' + config.cookieHintId).collapse('show');
|
||||
}
|
||||
|
||||
// logout current user (if there e.g. to register a second account)
|
||||
Util.logout({
|
||||
ajaxData: {
|
||||
reroute: 0
|
||||
}
|
||||
});
|
||||
|
||||
// show register/settings dialog
|
||||
$.fn.showSettingsDialog({
|
||||
register: 1,
|
||||
invite : parseInt( $('body').data('invite') )
|
||||
});
|
||||
$('#' + config.cookieHintId + ' .btn-success').on('click', function(){
|
||||
setCookie('cookie', 1, 365);
|
||||
});
|
||||
|
||||
// releases -----------------------------------------------------
|
||||
// releases -----------------------------------------------------------
|
||||
$('.' + config.navigationVersionLinkClass).on('click', function(e){
|
||||
$.fn.releasesDialog();
|
||||
});
|
||||
|
||||
// manual -------------------------------------------------------
|
||||
// manual -------------------------------------------------------------
|
||||
$('.' + config.navigationLinkManualClass).on('click', function(e){
|
||||
e.preventDefault();
|
||||
$.fn.showMapManual();
|
||||
});
|
||||
|
||||
// license ------------------------------------------------------
|
||||
// license ------------------------------------------------------------
|
||||
$('.' + config.navigationLinkLicenseClass).on('click', function(e){
|
||||
e.preventDefault();
|
||||
$.fn.showCreditsDialog(false, true);
|
||||
});
|
||||
|
||||
// tooltips -----------------------------------------------------
|
||||
// tooltips -----------------------------------------------------------
|
||||
var mapTooltipOptions = {
|
||||
toggle: 'tooltip',
|
||||
container: 'body',
|
||||
@@ -169,8 +199,6 @@ define([
|
||||
responseData.error.length > 0
|
||||
){
|
||||
form.showFormMessage(responseData.error);
|
||||
|
||||
|
||||
}else{
|
||||
$('.modal').modal('hide');
|
||||
Util.showNotify({title: 'Registration Key send', text: 'Check your Mails', type: 'success'});
|
||||
@@ -240,7 +268,7 @@ define([
|
||||
return newSlideContent[0];
|
||||
};
|
||||
|
||||
// initialize carousel ------------------------------------------
|
||||
// initialize carousel ------------------------------------------------
|
||||
var carousel = new Gallery([
|
||||
{
|
||||
title: 'IGB',
|
||||
@@ -363,29 +391,31 @@ define([
|
||||
|
||||
var initYoutube = function(){
|
||||
|
||||
$(".youtube").each(function() {
|
||||
$('.youtube').each(function() {
|
||||
// Based on the YouTube ID, we can easily find the thumbnail image
|
||||
$(this).css('background-image', 'url(https://i.ytimg.com/vi/' + this.id + '/sddefault.jpg)');
|
||||
|
||||
// Overlay the Play icon to make it look like a video player
|
||||
$(this).append($('<div/>', {'class': 'play'}));
|
||||
|
||||
$(document).delegate('#'+this.id, 'click', function() {
|
||||
$(document).delegate('#' + this.id, 'click', function() {
|
||||
// Create an iFrame with autoplay set to true
|
||||
var iframe_url = "https://www.youtube.com/embed/" + this.id + "?autoplay=1&autohide=1";
|
||||
if ($(this).data('params')) iframe_url+='&'+$(this).data('params');
|
||||
var iFrameUrl = 'https://www.youtube.com/embed/' + this.id + '?autoplay=1&autohide=1';
|
||||
if ( $(this).data('params') ){
|
||||
iFrameUrl += '&'+$(this).data('params');
|
||||
}
|
||||
|
||||
// The height and width of the iFrame should be the same as parent
|
||||
var iframe = $('<iframe/>', {
|
||||
var iFrame = $('<iframe/>', {
|
||||
frameborder: '0',
|
||||
src: iframe_url,
|
||||
src: iFrameUrl,
|
||||
width: $(this).width(),
|
||||
height: $(this).height(),
|
||||
class: 'pricing-big'
|
||||
});
|
||||
|
||||
// Replace the YouTube thumbnail with YouTube HTML5 Player
|
||||
$(this).replaceWith(iframe);
|
||||
$(this).replaceWith(iFrame);
|
||||
});
|
||||
});
|
||||
};
|
||||
@@ -436,6 +466,148 @@ define([
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* load character data from cookie information
|
||||
* -> all validation is done server side!
|
||||
*/
|
||||
var initCharacterSelect = function(){
|
||||
|
||||
/**
|
||||
* init panel animation for an element
|
||||
* @param imageWrapperElement
|
||||
*/
|
||||
var initCharacterAnimation = function(imageWrapperElement){
|
||||
|
||||
imageWrapperElement.velocity('stop').delay(300).velocity('transition.flipBounceXIn', {
|
||||
display: 'inline-block',
|
||||
stagger: 60,
|
||||
drag: true,
|
||||
duration: 600
|
||||
});
|
||||
|
||||
// Hover effect for character info layer
|
||||
imageWrapperElement.hoverIntent(function(e){
|
||||
var characterInfoElement = $(this).find('.' + config.characterImageInfoClass);
|
||||
|
||||
characterInfoElement.velocity('finish').velocity({
|
||||
width: ['100%', [ 400, 15 ] ]
|
||||
},{
|
||||
easing: 'easeInSine'
|
||||
});
|
||||
}, function(e){
|
||||
var characterInfoElement = $(this).find('.' + config.characterImageInfoClass);
|
||||
|
||||
characterInfoElement.velocity('finish').velocity({
|
||||
width: 0
|
||||
},{
|
||||
duration: 150,
|
||||
easing: 'easeInOutSine'
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* update all character panels -> set CSS class (e.g. after some panels were added/removed,..)
|
||||
*/
|
||||
var updateCharacterPanels = function(){
|
||||
var characterRows = $('.' + config.characterSelectionClass + ' .pf-dynamic-area').parent();
|
||||
var rowClassIdentifier = ((12 / characterRows.length ) <= 4) ? 4 : (12 / characterRows.length);
|
||||
$(characterRows).removeClass().addClass('col-sm-' + rowClassIdentifier);
|
||||
};
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
var removeCharacterPanel = function(panelElement){
|
||||
$(panelElement).velocity('transition.expandOut', {
|
||||
duration: 250,
|
||||
complete: function () {
|
||||
// lock row for CSS animations while removing...
|
||||
$(this).parent().addClass(config.characterRowAnimateClass);
|
||||
|
||||
$(this).parent().velocity({
|
||||
width: 0
|
||||
},{
|
||||
easing: 'ease',
|
||||
duration: 300,
|
||||
complete: function(){
|
||||
$(this).remove();
|
||||
// reset column CSS classes for all existing panels
|
||||
updateCharacterPanels();
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
// request character data for each character panel
|
||||
$('.' + config.characterSelectionClass + ' .pf-dynamic-area').each(function(){
|
||||
var characterElement = $(this);
|
||||
|
||||
characterElement.showLoadingAnimation();
|
||||
|
||||
var requestData = {
|
||||
cookie: characterElement.data('cookie')
|
||||
};
|
||||
|
||||
$.ajax({
|
||||
type: 'POST',
|
||||
url: Init.path.getCookieCharacterData,
|
||||
data: requestData,
|
||||
dataType: 'json',
|
||||
context: {
|
||||
href: characterElement.data('href'),
|
||||
cookieName: requestData.cookie,
|
||||
characterElement: characterElement
|
||||
}
|
||||
}).done(function(responseData){
|
||||
var characterElement = this.characterElement;
|
||||
characterElement.hideLoadingAnimation();
|
||||
|
||||
if(
|
||||
responseData.error &&
|
||||
responseData.error.length > 0
|
||||
){
|
||||
$('.' + config.dynamicMessageContainerClass).showMessage({
|
||||
type: responseData.error[0].type,
|
||||
title: 'Character verification failed',
|
||||
text: responseData.error[0].message
|
||||
});
|
||||
}
|
||||
|
||||
if(responseData.hasOwnProperty('character')){
|
||||
|
||||
var data = {
|
||||
link: this.href,
|
||||
cookieName: this.cookieName,
|
||||
character: responseData.character
|
||||
};
|
||||
|
||||
requirejs(['text!templates/ui/character_panel.html', 'mustache'], function(template, Mustache) {
|
||||
var content = Mustache.render(template, data);
|
||||
characterElement.html(content);
|
||||
|
||||
// show character panel (animation settings)
|
||||
initCharacterAnimation(characterElement.find('.' + config.characterImageWrapperClass));
|
||||
});
|
||||
}else{
|
||||
// character data not available -> remove panel
|
||||
removeCharacterPanel(this.characterElement);
|
||||
}
|
||||
|
||||
}).fail(function( jqXHR, status, error) {
|
||||
var characterElement = this.characterElement;
|
||||
characterElement.hideLoadingAnimation();
|
||||
|
||||
// character data not available -> remove panel
|
||||
removeCharacterPanel(this.characterElement);
|
||||
});
|
||||
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* main init "landing" page
|
||||
*/
|
||||
@@ -490,6 +662,8 @@ define([
|
||||
// hide splash loading animation
|
||||
$('.' + config.splashOverlayClass).hideSplashOverlay();
|
||||
|
||||
initCharacterSelect();
|
||||
|
||||
// init carousel
|
||||
initCarousel();
|
||||
|
||||
|
||||
23
public/templates/ui/character_panel.html
Normal file
23
public/templates/ui/character_panel.html
Normal file
@@ -0,0 +1,23 @@
|
||||
<a href="{{ link }}?cookie={{ cookieName }}">
|
||||
<div class="pf-character-image-wrapper">
|
||||
<div class="pf-character-select-image">
|
||||
<img class="pf-character-image" src="https://image.eveonline.com/Character/{{ character.id }}_128.jpg" alt="{{ character.name }}"/>
|
||||
|
||||
<div class="pf-character-info">
|
||||
<div style="width: 128px">
|
||||
{{#character.corporation}}
|
||||
<img src="https://image.eveonline.com/Corporation/{{ id }}_32.png"/>
|
||||
<div class="pf-character-info-text"><small>{{ name }}</small></div>
|
||||
{{/character.corporation}}
|
||||
{{#character.alliance}}
|
||||
<img src="https://image.eveonline.com/Alliance/{{ id }}_32.png"/>
|
||||
<div class="pf-character-info-text"><small>{{ name }}</small></div>
|
||||
{{/character.alliance}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="pf-character-name">
|
||||
<small>{{ character.name }}</small>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
@@ -70,66 +70,96 @@
|
||||
{* login form *}
|
||||
<section id="pf-landing-login">
|
||||
<div class="container">
|
||||
<div class="row text-center">
|
||||
<div class="col-md-8 col-md-offset-2">
|
||||
<h2><span class="text-primary">Please</span> log in</h2>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<form id="pf-login-form" class="" role="form" method="post" action="#">
|
||||
{* login message container *}
|
||||
<check if="{{ @SESSION.SSO.ERROR }}">
|
||||
{* cookie characters *}
|
||||
<check if="{{ @cookieCharacters }}">
|
||||
<true>
|
||||
<div class="row text-center">
|
||||
<div class="col-md-8 col-md-offset-2">
|
||||
<h2><span class="text-primary">Select</span> your character</h2>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{* dynamic message container *}
|
||||
<div class="container-fluid">
|
||||
<div class="row ">
|
||||
<div class="col-sm-8 col-sm-offset-2">
|
||||
<div class="alert alert-danger" >
|
||||
<span class="txt-color txt-color-danger">Access denied</span>
|
||||
<small>{{ @SESSION.SSO.ERROR }}</small>
|
||||
</div>
|
||||
<div class="col-sm-8 col-sm-offset-2 pf-dynamic-message-container">
|
||||
{* client side message e.g. ajax errors *}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</check>
|
||||
|
||||
{* check for setup mode *}
|
||||
<check if="{{ array_key_exists('/setup', @ROUTES) && @PATHFINDER.SHOW_SETUP_WARNING }}">
|
||||
<div class="container-fluid">
|
||||
<div class="row">
|
||||
<div class="col-sm-8 col-sm-offset-2">
|
||||
<div class="alert alert-warning" >
|
||||
<span class="txt-color txt-color-warning">Setup mode active</span>
|
||||
<small>Disable setup route in routes.ini in production environment! <a href="/setup">Setup</a></small>
|
||||
{* login message container *}
|
||||
<check if="{{ @SESSION.SSO.ERROR }}">
|
||||
<div class="container-fluid">
|
||||
<div class="row ">
|
||||
<div class="col-sm-8 col-sm-offset-2">
|
||||
<div class="alert alert-danger" >
|
||||
<span class="txt-color txt-color-danger">Access denied</span>
|
||||
<small>{{ @SESSION.SSO.ERROR }}</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</check>
|
||||
</check>
|
||||
|
||||
{* IGB warning *}
|
||||
<check if="{{ @isIngame == 1 }}">
|
||||
<div class="container-fluid">
|
||||
<div class="row">
|
||||
<div class="col-sm-8 col-sm-offset-2">
|
||||
<div class="alert alert-warning" >
|
||||
<button type="button" class="close" data-dismiss="alert" aria-label="Close"><i class="fa fa-close"></i></button>
|
||||
<span class="txt-color txt-color-warning">Hint</span>
|
||||
<small>Some functions may not work within the outdated IGB browser!</small>
|
||||
{* check for setup mode *}
|
||||
<check if="{{ array_key_exists('/setup', @ROUTES) && @PATHFINDER.SHOW_SETUP_WARNING }}">
|
||||
<div class="container-fluid">
|
||||
<div class="row">
|
||||
<div class="col-sm-8 col-sm-offset-2">
|
||||
<div class="alert alert-warning" >
|
||||
<span class="txt-color txt-color-warning">Setup mode active</span>
|
||||
<small>Disable setup route in routes.ini in production environment! <a href="/setup">Setup</a></small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</check>
|
||||
</check>
|
||||
|
||||
{* SSO login *}
|
||||
<div class="container-fluid">
|
||||
{* IGB warning *}
|
||||
<check if="{{ @isIngame == 1 }}">
|
||||
<div class="container-fluid">
|
||||
<div class="row">
|
||||
<div class="col-sm-8 col-sm-offset-2">
|
||||
<div class="alert alert-warning" >
|
||||
<button type="button" class="close" data-dismiss="alert" aria-label="Close"><i class="fa fa-close"></i></button>
|
||||
<span class="txt-color txt-color-warning">Hint</span>
|
||||
<small>Some functions may not work within the outdated IGB browser!</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</check>
|
||||
|
||||
<div class="row text-center pf-character-selection">
|
||||
<repeat group="{{ @cookieCharacters }}" key="{{ @cookieName }}" value="{{ @characterData }}">
|
||||
<div class="col-sm-{{ @getCharacterGrid( @cookieCharacters ) }}">
|
||||
<div class="pf-dynamic-area" data-cookie="{{ @cookieName }}" data-href="{{ 'sso','action=login' | alias }}">
|
||||
{* character data is dynamically *}
|
||||
</div>
|
||||
</div>
|
||||
</repeat>
|
||||
</div>
|
||||
</true>
|
||||
<false>
|
||||
<div class="row text-center">
|
||||
<div class="col-xs-12">
|
||||
<a class="pf-sso-login-button {{@registrationStatusButton}}" target="_self" href="{{ 'sso','action=requestAuthorization' | alias }}" type="button" tabindex="3" title="{{@registrationStatusTitle}}"> </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 class="col-md-8 col-md-offset-2">
|
||||
<h2><span class="text-primary">Sign</span> up</h2>
|
||||
</div>
|
||||
</div>
|
||||
</false>
|
||||
</check>
|
||||
|
||||
{* SSO login *}
|
||||
<div class="container-fluid">
|
||||
<div class="row text-center">
|
||||
<div class="col-xs-12">
|
||||
<a class="pf-sso-login-button {{@registrationStatusButton}}" target="_self" href="{{ 'sso','action=requestAuthorization' | alias }}" type="button" tabindex="3" title="{{@registrationStatusTitle}}"> </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>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</section>
|
||||
@@ -893,6 +923,17 @@
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
{* cookie warning *}
|
||||
<footer class="navbar-default navbar-fixed-bottom collapse" id="pf-cookie-hint">
|
||||
<div class="container-fluid">
|
||||
<p class="navbar-text">
|
||||
Pathfinder requires cookies to maintain your login state between browser sessions. Read <a target="_blank" href="https://github.com/exodus4d/pathfinder/issues/138">more</a>.
|
||||
</p>
|
||||
<div class="pull-right">
|
||||
<button type="button" class="btn btn-default navbar-btn btn-success" data-toggle="collapse" data-target="#pf-cookie-hint" aria-expanded="false">That´s fine</button>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
{* hidden dialog *}
|
||||
<include href="templates/dialog/gallery.html" />
|
||||
|
||||
@@ -320,6 +320,87 @@
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
// character select panel animation (e.g. on delete)
|
||||
.pf-character-selection > div:not(.pf-character-row-animate){
|
||||
@include transition( width 0.2s ease, margin 0.2s ease);
|
||||
}
|
||||
|
||||
.pf-dynamic-area{
|
||||
display: inline-block;
|
||||
margin: 10px 5px 20px 5px;
|
||||
padding: 10px 10px 5px 10px;
|
||||
min-width: 155px;
|
||||
min-height: 184px;
|
||||
@include border-radius(10px);
|
||||
@include box-shadow(0 4px 10px rgba(0,0,0, 0.4));
|
||||
|
||||
// character images
|
||||
.pf-character-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
|
||||
|
||||
&:hover{
|
||||
border-color: $green-dark;
|
||||
|
||||
.pf-character-name{
|
||||
color: $green-dark;
|
||||
}
|
||||
|
||||
.pf-character-image{
|
||||
@include filter(grayscale(50%))
|
||||
}
|
||||
}
|
||||
|
||||
.pf-character-select-image{
|
||||
overflow: hidden;
|
||||
width: 128px;
|
||||
height: 128px;
|
||||
position: relative;
|
||||
|
||||
// info element visible on hover
|
||||
.pf-character-info{
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 0; // trigger by js
|
||||
height: 100%;
|
||||
color: $gray-lighter;
|
||||
background: rgba($gray, 0.80);
|
||||
overflow: hidden;
|
||||
will-change: width, transition;
|
||||
padding: 10px 0;
|
||||
|
||||
.pf-character-info-text{
|
||||
line-height: 25px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.pf-character-name{
|
||||
font-size: 13px;
|
||||
line-height: 30px;
|
||||
border-top: 1px solid $gray-dark;
|
||||
color: $gray-lighter;
|
||||
@include transition( color 0.2s ease-out );
|
||||
}
|
||||
|
||||
.pf-character-image{
|
||||
@include transition(all 0.3s ease-out);
|
||||
@include filter(grayscale(0%));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.pf-sso-login-button{
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
|
||||
@@ -989,3 +989,4 @@ Animate the stripes
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user