New "Invite" feature implemented

This commit is contained in:
Exodus4D
2015-08-30 23:25:02 +02:00
parent 67bab61fd3
commit 9c6ed24704
25 changed files with 577 additions and 134 deletions

View File

@@ -6,7 +6,7 @@ DEBUG = 0
; If TRUE, the framework, after having logged stack trace and errors, stops execution (die without any status) when a non-fatal error is detected.
HALT = FALSE
ONERROR = "Controller\MapController->showError"
ONERROR = "Controller\Controller->showError"
; Timezone to use. Sync program with eve server time
TZ = "UTC"

View File

@@ -451,7 +451,7 @@ class Map extends \Controller\AccessController {
}
}else{
// user logged of
// user logged off
$return->error[] = $this->getUserLoggedOffError();
}
@@ -498,14 +498,16 @@ class Map extends \Controller\AccessController {
$return = (object) [];
$return->error = [];
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');
$user = $this->_getUser();
if($user){
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');
$user = $this->_getUser();
if($user){
// update current location (IGB data)
$user->updateCharacterLog(60 * 5);
@@ -551,16 +553,17 @@ class Map extends \Controller\AccessController {
// with the same main char
$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();
}else{
// user logged of
$return->error[] = $this->getUserLoggedOffError();
}
// 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();
}else{
// user logged off
$return->error[] = $this->getUserLoggedOffError();
}
echo json_encode( $return );
}

View File

@@ -8,6 +8,7 @@
namespace Controller\Api;
use Controller;
use controller\MailController;
use Model;
use Exception;
@@ -205,6 +206,60 @@ class User extends Controller\Controller{
echo json_encode($return);
}
/**
* 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 data
* @param $f3
@@ -225,6 +280,10 @@ class User extends Controller\Controller{
// check user if if he is new
$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'];
@@ -242,6 +301,16 @@ class User extends Controller\Controller{
// change/set sensitive user data requires captcha!
if($user === false){
// 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 = $mapType = Model\BasicModel::getNew('UserModel');
$loginAfterSave = true;
@@ -359,6 +428,11 @@ class User extends Controller\Controller{
// 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->logUserIn( $user->name, $settingsData['password'] );
@@ -380,6 +454,7 @@ class User extends Controller\Controller{
}catch(Exception\RegistrationException $e){
$registrationError = (object) [];
$registrationError->type = 'error';
$registrationError->field = $e->getField();
$registrationError->message = $e->getMessage();
$return->error[] = $registrationError;
}
@@ -390,4 +465,107 @@ class User extends Controller\Controller{
}
echo json_encode($return);
}
/**
* send mail with registration key
* -> check INVITE in pathfinder.ini
* @param $f3
* @throws Exception
*/
public function sendRegistration($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 per an 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->sendRegistrationKey($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);
}
}

View File

@@ -40,7 +40,8 @@ class CcpApiController extends Controller{
$requestOptions = [
'timeout' => 8,
'method' => 'POST',
'user_agent' => $this->getUserAgent()
'user_agent' => $this->getUserAgent(),
'follow_location' => false // otherwise CURLOPT_FOLLOWLOCATION will fail
];
return $requestOptions;
@@ -94,7 +95,10 @@ class CcpApiController extends Controller{
// request successful
$rowApiData = $xml->result->key->rowset;
if($rowApiData->children()){
if(
is_object($rowApiData) &&
$rowApiData->children()
){
$characterModel = Model\BasicModel::getNew('CharacterModel');
$corporationModel = Model\BasicModel::getNew('CorporationModel');
$allianceModel = Model\BasicModel::getNew('AllianceModel');
@@ -151,7 +155,6 @@ class CcpApiController extends Controller{
}
$userApiModel->userCharacters->rewind();
}
$characterModel->id = $characterId;

View File

@@ -256,4 +256,39 @@ class Controller {
return $data;
}
/**
* function is called on each error
* @param $f3
*/
public function showError($f3){
// set HTTP status
if(!empty($f3->get('ERROR.code'))){
$f3->status($f3->get('ERROR.code'));
}
if($f3->get('AJAX')){
header('Content-type: application/json');
// error on ajax call
$errorData = [
'status' => $f3->get('ERROR.status'),
'code' => $f3->get('ERROR.code'),
'text' => $f3->get('ERROR.text')
];
// append stack trace for greater debug level
if( $f3->get('DEBUG') === 3){
$errorData['trace'] = $f3->get('ERROR.trace');
}
echo json_encode($errorData);
}else{
echo $f3->get('ERROR.text');
}
die();
}
}

View File

@@ -0,0 +1,43 @@
<?php
/**
* Created by PhpStorm.
* User: Exodus
* Date: 30.08.2015
* Time: 14:48
*/
namespace controller;
class MailController extends \SMTP{
public function __construct(){
$host = Controller::getEnvironmentData('SMTP_HOST');
$port = Controller::getEnvironmentData('SMTP_PORT');
$scheme = Controller::getEnvironmentData('SMTP_SCHEME');
$user = Controller::getEnvironmentData('SMTP_USER');
$pw = Controller::getEnvironmentData('SMTP_PASS');
parent::__construct($host,$port,$scheme,$user,$pw);
// error handling
$this->set('Errors-to', '' . Controller::getEnvironmentData('SMTP_ERROR') . '>');
}
/**
* send registration key
* @param $to
* @param $msg
* @return bool
*/
public function sendRegistrationKey($to, $msg){
$this->set('To', '"<' . $to . '>');
$this->set('From', '"PATHFINDER" <' . Controller::getEnvironmentData('SMTP_FROM') . '>');
$this->set('Subject', 'PATHFINDERR - Registration Key');
$status = $this->send($msg);
return $status;
}
}

View File

@@ -31,38 +31,4 @@ class MapController extends \Controller\AccessController {
$this->setTemplate('templates/view/index.html');
}
/**
* function is called on each error
* @param $f3
*/
public function showError($f3){
// set HTTP status
if(!empty($f3->get('ERROR.code'))){
$f3->status($f3->get('ERROR.code'));
}
if($f3->get('AJAX')){
header('Content-type: application/json');
// error on ajax call
$errorData = [
'status' => $f3->get('ERROR.status'),
'code' => $f3->get('ERROR.code'),
'text' => $f3->get('ERROR.text')
];
// append stack trace for greater debug level
if( $f3->get('DEBUG') === 3){
$errorData['trace'] = $f3->get('ERROR.trace');
}
echo json_encode($errorData);
}else{
echo $f3->get('ERROR.text');
}
die();
}
}

View File

@@ -11,8 +11,30 @@ namespace Exception;
class RegistrationException extends BaseException{
public function __construct($message){
/**
* form field name that causes this exception
* @var string
*/
private $field;
/**
* @return mixed
*/
public function getField(){
return $this->field;
}
/**
* @param mixed $field
*/
public function setField($field){
$this->field = $field;
}
public function __construct($message, $field = ''){
parent::__construct($message, self::REGISTRATION_FAILED);
$this->setField($field);
}
}

View File

@@ -0,0 +1,16 @@
<?php
/**
* Created by PhpStorm.
* User: exodus4d
* Date: 29.08.15
* Time: 11:57
*/
namespace Model;
class RegistrationKeyModel extends BasicModel {
protected $table = 'registration_key';
}

View File

@@ -35,7 +35,7 @@ class UserModel extends BasicModel {
'name' => [
'length' => [
'min' => 5,
'max' => 20
'max' => 25
]
],
'email' => [

View File

@@ -46,6 +46,15 @@ DB_CCP_NAME = eve_test
DB_CCP_USER = root
DB_CCP_PASS =
; SMTP settings
SMTP_HOST = localhost
SMTP_PORT = 25
SMTP_SCHEME = ""
SMTP_USER = pathfinder
SMTP_PASS = root
SMTP_FROM = pathfinder@localhost.com
[PATHFINDER.ENVIRONMENT.PRODUCTION]
BASE = /www/htdocs/w0128162/www.pathfinder.exodus4d.de
@@ -70,6 +79,16 @@ DB_CCP_NAME = d01f20be
DB_CCP_USER = d01f20be
DB_CCP_PASS = 2gkBWs87zDcApH4A
; SMTP settings
SMTP_HOST = localhost
SMTP_PORT = 25
SMTP_SCHEME = TLS
SMTP_USER = pathfinder
SMTP_PASS = root
SMTP_FROM = pathfinder@localhost.com
SMTP_ERROR = pathfinder@localhost.com
; ======================================================================================================
[PATHFINDER.REGISTRATION]
; registration status (0=disabled, 1=enabled)
@@ -77,6 +96,12 @@ STATUS = 1
; disabled message
MSG_DISABLED = "User registration is currently not allowed"
; use the invite system e.g. beta testing. A "registration key" is required (0=disabled, 1=enabled)
INVITE = 1
; the limit of registration keys. Increase it to hand out more keys
INVITE_LIMIT = 50
; ======================================================================================================
; Lifetime for map types
[PATHFINDER.MAP.PRIVATE]
@@ -107,10 +132,6 @@ DBL_CLICK = 250
; time for status change visibility in header (ms)
PROGRAM_STATUS_VISIBLE = 5000
; get all client map data (ms)
[PATHFINDER.TIMER.GET_CLIENT_MAP_DATA]
EXECUTION_LIMIT = 50
; main map update ping (ajax) (ms)
[PATHFINDER.TIMER.UPDATE_SERVER_MAP]
DELAY = 5000

View File

@@ -11,6 +11,7 @@ define(['jquery'], function($) {
img: 'public/img/', // path for images
// user API
getCaptcha: 'api/user/getCaptcha', // ajax URL - get captcha image
sendRegistrationKey: 'api/user/sendRegistration', // ajax URL - send registration key
logIn: 'api/user/logIn', // ajax URL - login
logOut: 'api/user/logOut', // ajax URL - logout
deleteLog: 'api/user/deleteLog', // ajax URL - delete character log

View File

@@ -55,9 +55,11 @@ define([
};
/**
* set page observer
*/
var setPageObserver = function(){
// login form =====================================================================================
// register buttons ---------------------------------------------
$('.' + config.registerButtonClass).on('click', function(e){
@@ -67,7 +69,10 @@ define([
Util.logout();
// show register/settings dialog
$.fn.showSettingsDialog(true);
$.fn.showSettingsDialog({
register: 1,
invite : parseInt( $('body').data('invite') )
});
});
@@ -142,6 +147,87 @@ define([
};
var showRequestRegistrationKeyDialog = function(){
var data = {
id: config.signatureReaderDialogId,
formErrorContainerClass: Util.config.formErrorContainerClass,
formWarningContainerClass: Util.config.formWarningContainerClass
};
requirejs(['text!templates/dialog/registration.html', 'mustache'], function(template, Mustache) {
var content = Mustache.render(template, data);
var registrationKeyDialog = bootbox.dialog({
title: 'Registration Key',
message: content,
buttons: {
close: {
label: 'cancel',
className: 'btn-default'
},
success: {
label: '<i class="fa fa-envelope fa-fw"></i>&nbsp;send',
className: 'btn-success',
callback: function () {
var dialogElement = $(this);
var form = dialogElement.find('form');
// validate form
form.validator('validate');
var formValid = form.isValidForm();
if(formValid){
var formValues = form.getFormValues();
if( !$.isEmptyObject(formValues) ){
// send Tab data and store values
var requestData = {
settingsData: formValues
};
var modalContent = registrationKeyDialog.find('.modal-content');
modalContent.showLoadingAnimation();
$.ajax({
type: 'POST',
url: Init.path.sendRegistrationKey,
data: requestData,
dataType: 'json'
}).done(function(responseData){
if(
responseData.error &&
responseData.error.length > 0
){
form.showFormMessage(responseData.error);
}else{
$('.modal').modal('hide');
Util.showNotify({title: 'Registration Key send', text: 'Check your Mails', type: 'success'});
}
modalContent.hideLoadingAnimation();
}).fail(function( jqXHR, status, error) {
modalContent.hideLoadingAnimation();
var reason = status + ' ' + error;
Util.showNotify({title: jqXHR.status + ': send Registration Key', text: reason, type: 'error'});
});
}
}
return false;
}
}
}
});
});
};
/**
* init image carousel
@@ -387,6 +473,12 @@ define([
}
}
// show get registration key dialog
var showRegistrationDialog = location.search.split('register')[1];
if(showRegistrationDialog !== undefined){
showRequestRegistrationKeyDialog();
}
// init scrollspy
initScrollspy();

View File

@@ -65,8 +65,6 @@ define([
var mapModule = $(this);
// log keys ------------------------------------------------------------------------
// get map data from client
var logKeyGetClientMapData = 'GET_CLIENT_MAP_DATA';
// ajax request update map data
var logKeyServerMapData = 'UPDATE_SERVER_MAP';
@@ -98,12 +96,7 @@ define([
}
// get updated map data
Util.timeStart(logKeyGetClientMapData);
var updatedMapData = mapModule.getMapModuleDataForUpdate();
var mapDataLogDuration = Util.timeStop(logKeyGetClientMapData);
// log execution time
Util.log(logKeyGetClientMapData, {duration: mapDataLogDuration, type: 'client', description: 'get client data'});
// wrap array to object
updatedMapData = {mapData: updatedMapData};
@@ -223,7 +216,7 @@ define([
if(userData.character === undefined){
// no active character found -> show settings dialog
Util.showNotify({title: 'No main character found', text: 'Set up your main character', type: 'error'});
Util.showNotify({title: 'Main character missing', text: 'Check API and set a main character', type: 'error'});
$(document).triggerMenuEvent('ShowSettingsDialog');
}

View File

@@ -518,7 +518,10 @@ define([
$(document).on('pf:menuShowSettingsDialog', function(e){
// show character select dialog
$.fn.showSettingsDialog(false);
$.fn.showSettingsDialog({
register: 0,
invite : parseInt( $('body').data('invite') )
});
return false;
});

View File

@@ -154,8 +154,10 @@ define([
/**
* show "register/settings" dialog
* @param options
* @returns {boolean}
*/
$.fn.showSettingsDialog = function(register){
$.fn.showSettingsDialog = function(options){
// check if there is already a settings dialog open
var settingsDialog = $('#' + config.settingsDialogId);
@@ -191,7 +193,7 @@ define([
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(register){
if(options.register === 1){
Init.currentUserData = {};
Init.currentUserData.api = [{
keyId: '',
@@ -206,7 +208,8 @@ define([
var data = {
id: config.settingsDialogId,
register: register,
register: options.register === 1 ? 1 : 0,
invite : options.invite === 1 ? 1 : 0,
navigationClass: config.dialogWizardNavigationClass,
userData: Init.currentUserData,
cloneApiRowClass: config.settingsCloneApiRowClass,
@@ -221,7 +224,7 @@ define([
var content = Mustache.render(template, data);
var selectCharacterDialog = bootbox.dialog({
title: register ? 'Registration' : 'Account settings',
title: options.register === 1 ? 'Registration' : 'Account settings',
message: content,
buttons: {
close: {
@@ -229,7 +232,7 @@ define([
className: ['btn-success', 'pull-right', config.settingsFinishButtonClass].join(' '),
callback: function(e){
if(register){
if(options.register === 1){
if(reroutePath !== undefined){
// root user to main app
window.location = reroutePath;
@@ -358,23 +361,20 @@ define([
var fieldName = 'name';
if(errorObj.text.match( fieldName )){
// name exist
showFormMessage([{type: 'error', message: 'Username already exists'}]);
resetFormField( fieldName );
form.showFormMessage([{type: 'error', message: 'Username already exists', field: fieldName}]);
}
fieldName = 'email';
if(errorObj.text.match( fieldName )){
// name exist
showFormMessage([{type: 'error', message: 'Email already exists'}]);
resetFormField( fieldName );
resetFormField( fieldName + '_confirm');
// email exist
form.showFormMessage([{type: 'error', message: 'Email already exists', field: fieldName}]);
}
}
}
}
}
if(!register){
if( options.register !== 1 ){
$(document).setProgramStatus('problem');
}

View File

@@ -191,6 +191,13 @@ define([
type_sort: tempSystemData.type.id
};
// security
var securityClass = Util.getSecurityClassForSystem(tempSystemData.security);
tempData.security = {
security: '<span class="' + securityClass + '">' + tempSystemData.security + '</span>',
security_sort: tempSystemData.security
};
// name
tempData.name = tempSystemData.name;
@@ -261,19 +268,6 @@ define([
};
}
// rally point
if(tempSystemData.rally === 1){
tempData.rally = {
rally: '<i class="fa fa-users fa-lg fa-fw"></i>',
rally_sort: tempSystemData.rally
};
}else{
tempData.rally = {
rally: '',
rally_sort: 0
};
}
// updated
tempData.updated = tempSystemData.updated.updated;
@@ -288,7 +282,7 @@ define([
paging: true,
lengthMenu: [[5, 10, 20, 50, -1], [5, 10, 20, 50, 'All']],
ordering: true,
order: [[ 7, 'desc' ], [ 2, 'asc' ]],
order: [[ 9, 'desc' ], [ 3, 'asc' ]],
autoWidth: false,
responsive: {
breakpoints: [
@@ -318,10 +312,19 @@ define([
_: 'type',
sort: 'type_sort'
}
},{
title: '',
width: '1px',
searchable: false,
data: 'security',
render: {
_: 'security',
sort: 'security_sort'
}
},{
title: 'sec',
width: '18px',
className: 'text-center',
className: ['text-center', 'min-desktop'].join(' '),
searchable: false,
data: 'trueSec',
render: {
@@ -381,16 +384,6 @@ define([
_: 'locked',
sort: 'locked_sort'
}
},{
title: '<i class="fa fa-users fa-lg fa-fw" title="rally&nbsp;point" data-toggle="tooltip"></i>',
width: '15px',
className: ['min-desktop'].join(' '),
searchable: false,
data: 'rally',
render: {
_: 'rally',
sort: 'rally_sort'
}
},{
title: 'updated',
width: '80px',

View File

@@ -739,7 +739,9 @@ define([
// jump to "next" editable field on save
var openNextEditDialogOnSave = function(fields){
fields.on('save', function(e){
fields.on('save', function(e, a){
console.log(e);
console.log(a)
var currentField = $(this);
setTimeout(function() {
@@ -1504,9 +1506,10 @@ define([
$(cell).initTimestampCounter();
// highlight cell
var diff = new Date().getTime() - cellData.updated * 1000;
var dateDiff = new Date(diff);
if(dateDiff.getUTCDate() > 1){
var diff = Math.floor((new Date()).getTime()) - cellData.updated * 1000;
// age > 1 day
if( diff > 86400000){
$(cell).addClass('txt-color txt-color-warning');
}
}

View File

@@ -153,7 +153,10 @@ define([
errorMessage.push( errors[i].message );
// mark form field as invalid in case of a validation error
if(errors[i].field){
if(
errors[i].field &&
errors[i].field.length > 0
){
var formField = formElement.find('[name="' + errors[i].field + '"]');
formField.parents('.form-group').removeClass('has-success').addClass('has-error');
}

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,40 @@
<form role="form" class="form-horizontal">
<div class="row">
<div class="col-sm-11">
<blockquote>
<p>
The number of active accounts is limited during the beta phase. This restriction may help to find bugs and performance issues.
The limit will be increased continuously and fully removed when the beta phase is over.
</p>
<small>If the pool of beta keys has been exhausted, please try again in a few days/weeks.
</small>
</blockquote>
</div>
</div>
<div class="row">
<div class="col-sm-12">
<div class="form-group">
<label for="email" class="col-sm-3 control-label">Email</label>
<div class="col-sm-6">
<div class="input-group" title="Send invite to this address" data-placement="right">
<input name="email" type="email" class="form-control" id="email" value="" placeholder="your@email.com" data-error="Email required" autocomplete="off" 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 class="{{formWarningContainerClass}} alert alert-warning" style="display: none;">
<span class="txt-color txt-color-warning">Warning</span>
<small> (important non-critical information)</small>
</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>

View File

@@ -41,20 +41,45 @@
{{/register}}
{{#register}}
<div class="row">
<div class="col-sm-12">
<div class="form-group">
<label for="email" 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="off" required>
<span class="input-group-addon"><i class="fa fa-user"></i></span>
{{#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="email" 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="off" 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 class="help-block with-errors"></div>
</div>
</div>
</div>
</div>
{{/register}}
{{^register}}
@@ -81,7 +106,7 @@
<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="off" {{#register}}required{{/register}} >
<span class="input-group-addon"><i class="fa fa-envelope"></i></span>
<span class="input-group-addon"><i class="fa fa-fw fa-envelope"></i></span>
</div>
<div class="help-block with-errors"></div>
</div>
@@ -96,7 +121,7 @@
<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="off" {{#register}}required{{/register}}>
<span class="input-group-addon"><i class="fa fa-envelope"></i></span>
<span class="input-group-addon"><i class="fa fa-fw fa-envelope"></i></span>
</div>
<div class="help-block with-errors"></div>
</div>
@@ -129,7 +154,7 @@
<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" {{#register}}required{{/register}}>
<span class="input-group-addon"><i class="fa fa-lock"></i></span>
<span class="input-group-addon"><i class="fa fa-fw fa-lock"></i></span>
</div>
<div class="help-block with-errors"></div>
</div>
@@ -144,7 +169,7 @@
<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" {{#register}}required{{/register}}>
<span class="input-group-addon"><i class="fa fa-lock"></i></span>
<span class="input-group-addon"><i class="fa fa-fw fa-lock"></i></span>
</div>
<div class="help-block with-errors"></div>
</div>
@@ -174,7 +199,7 @@
<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-refresh"></i></span>
<span class="input-group-addon"><i class="fa fa-fw fa-refresh"></i></span>
</div>
<div class="help-block with-errors"></div>
</div>

View File

@@ -46,7 +46,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-version="{{ @PATHFINDER.VERSION }}">
<body class="{{ @bodyClass }}" data-trusted="{{ @trusted }}" data-js-path="{{ @pathJs }}" data-script="{{ @jsView }}" data-invite="{{ @PATHFINDER.REGISTRATION.INVITE }}" data-version="{{ @PATHFINDER.VERSION }}">
<include if="{{ @pageContent }}" href="{{ @pageContent }}"/>

View File

@@ -130,10 +130,10 @@
<div class="row text-center">
<div class="col-sm-6 col-sm-offset-3">
<div class="col-sm-4 col-sm-offset-2" data-placement="left" title="{{@registrationStatusTitle}}">
<button class="pf-register-button btn-block btn btn-primary {{@registrationStatusButton}}" tabindex="4"><i class="fa fa-fw fa-user-plus"></i> Sign up</button>
<button type="button" class="pf-register-button btn-block btn btn-primary {{@registrationStatusButton}}" tabindex="4"><i class="fa fa-fw fa-user-plus"></i> Sign up</button>
</div>
<div class="col-sm-4">
<button class="pf-login-button btn-block btn btn-success" tabindex="3"><i class="fa fa-fw fa-sign-in"></i> Log in</button>
<button type="submit" class="pf-login-button btn-block btn btn-success" tabindex="3"><i class="fa fa-fw fa-sign-in"></i> Log in</button>
</div>
</div>
</div>
@@ -537,7 +537,7 @@
</div>
</div>
<div class="panel-footer text-align-center" data-placement="top" title="{{@registrationStatusTitle}}">
<button class="btn btn-primary btn-block pf-register-button {{@registrationStatusButton}}" role="button"><i class="fa fa-fw fa-user-plus"></i> Sign up</button>
<button type="button" class="btn btn-primary btn-block pf-register-button {{@registrationStatusButton}}" role="button"><i class="fa fa-fw fa-user-plus"></i> Sign up</button>
</div>
</div>
</div>
@@ -776,7 +776,7 @@
The program code is open source and can be used by anyone who have the required software skills.
Please make sure to keep all 3rd party plugin licence and respect them.
At the moment there is no developer guide available. But one the beta phase is finished, i will probably write a short technical documentation.
Do not expect any "out of the boy" install routine ot this point.
Do not expect any "out of the boy" install routine at this point.
<br>
Server requirements:
</p>

View File

@@ -165,6 +165,7 @@ select:active, select:hover {
left: 0;
opacity: 0;
background: $gray-darker;
z-index: 1060;
@include border-radius(5px);
.pf-loading-overlay-wrapper{
@@ -320,9 +321,7 @@ select:active, select:hover {
overflow: hidden;
@include border-radius(5px);
&:after{
//content: "\f09b";
//font: normal normal normal 14px/1 FontAwesome; // shortening font declaration
&:before{
content:'';
position: absolute;
top: 0;
@@ -823,6 +822,10 @@ select:active, select:hover {
cursor: pointer;
}
.navbar-text{
min-width: 60px; // fixes a load-delay issue for "toggle" map-tracking
}
// tooltips header
.tooltip{
.tooltip-inner{