initial release
This commit is contained in:
commit
f0643ce999
47 changed files with 2840 additions and 0 deletions
28
.gitignore
vendored
Executable file
28
.gitignore
vendored
Executable file
|
@ -0,0 +1,28 @@
|
||||||
|
|
||||||
|
# only keep executable php files
|
||||||
|
bin/*
|
||||||
|
!bin/.gitkeep
|
||||||
|
!bin/*.php
|
||||||
|
|
||||||
|
# composer
|
||||||
|
vendor/
|
||||||
|
composer.lock
|
||||||
|
composer.phar
|
||||||
|
|
||||||
|
# do not commit environment vars
|
||||||
|
config/env
|
||||||
|
|
||||||
|
# only keep folder
|
||||||
|
cache/*
|
||||||
|
!cache/.gitkeep
|
||||||
|
css/.sass-cache/
|
||||||
|
|
||||||
|
# do not commit database
|
||||||
|
data/db.sqlite
|
||||||
|
|
||||||
|
# only keep folder
|
||||||
|
log/*
|
||||||
|
!log/.gitkeep
|
||||||
|
|
||||||
|
# IDE
|
||||||
|
.idea/
|
69
README.md
Executable file
69
README.md
Executable file
|
@ -0,0 +1,69 @@
|
||||||
|
# README #
|
||||||
|
|
||||||
|
A simple WebUI for [`admin_rest`](https://github.com/snowblindroan/mod_admin_rest) module of prosody allowing 2 step verification of new user accounts (as an alternative to the integrated `register_web` module).
|
||||||
|
|
||||||
|
This app uses
|
||||||
|
|
||||||
|
* Slim Version 3
|
||||||
|
* Eloquent ORM
|
||||||
|
* PHPMigration
|
||||||
|
* GUMP Validation
|
||||||
|
* Twig
|
||||||
|
* Curl
|
||||||
|
* PHPMailer
|
||||||
|
* Symfony Translation
|
||||||
|
* Sqlite
|
||||||
|
|
||||||
|
as dependencies.
|
||||||
|
|
||||||
|
## Requirements ##
|
||||||
|
|
||||||
|
* admin_rest module of prosody
|
||||||
|
* composer
|
||||||
|
* sqlite pdo, mb_string
|
||||||
|
|
||||||
|
## Install ##
|
||||||
|
|
||||||
|
* Install composer
|
||||||
|
* Change directory to project home
|
||||||
|
* Copy `config/env.example` to `config/env` and adjust to your needs
|
||||||
|
* `composer install`
|
||||||
|
* `php bin/phpmig migrate`
|
||||||
|
* start server with `php -S localhost:8080 -t public public/index.php`
|
||||||
|
* point browser to [localhost:8080](http://localhost:8080) to have a preview
|
||||||
|
|
||||||
|
## Deployment ##
|
||||||
|
|
||||||
|
* Set up a cron job using `php projectRootDir/bin/UsersAwaitingVerificationCleanUpCronJob.php` to clean up users who signed up but did not verify their account periodically.
|
||||||
|
* Point your document root to `public/`.
|
||||||
|
* Example nginx conf:
|
||||||
|
|
||||||
|
root .../public;
|
||||||
|
index index.php;
|
||||||
|
|
||||||
|
rewrite_log on;
|
||||||
|
|
||||||
|
location / {
|
||||||
|
try_files $uri $uri/ @ee;
|
||||||
|
}
|
||||||
|
|
||||||
|
location @ee {
|
||||||
|
rewrite ^(.*) /index.php?$1 last;
|
||||||
|
}
|
||||||
|
|
||||||
|
# php fpm
|
||||||
|
location ~ \.php$ {
|
||||||
|
fastcgi_split_path_info ^(.+\.php)(/.+)$;
|
||||||
|
fastcgi_pass unix:/var/run/php-fpm/php-fpm.sock;
|
||||||
|
include fastcgi_params;
|
||||||
|
}
|
||||||
|
|
||||||
|
## Upgrade ##
|
||||||
|
|
||||||
|
* Change directory to project home
|
||||||
|
* `git pull`
|
||||||
|
* `composer update`
|
||||||
|
* `php bin/phpmig migrate`
|
||||||
|
|
||||||
|
## Translations ##
|
||||||
|
This app uses Symfony Translator. It's bootstraped in `Util\TranslationHelper` and locales are placed under `data/locale/`. Adjust to your needs or help translating.
|
0
bin/.gitkeep
Normal file
0
bin/.gitkeep
Normal file
50
bin/UsersAwaitingVerificationCleanUpCronJob.php
Normal file
50
bin/UsersAwaitingVerificationCleanUpCronJob.php
Normal file
|
@ -0,0 +1,50 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
use Carbon\Carbon;
|
||||||
|
|
||||||
|
require_once __DIR__ . DIRECTORY_SEPARATOR . '../vendor/autoload.php';
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Bootstrap environment, configs and database
|
||||||
|
*/
|
||||||
|
$env = EnvironmentHelper::getAppEnvironment();
|
||||||
|
$config = Config::$CONFIG;
|
||||||
|
$db = DatabaseHelper::bootORM();
|
||||||
|
$translator = TranslationHelper::getAppTranslator();
|
||||||
|
$logger = LoggerHelper::getAppLogger();
|
||||||
|
|
||||||
|
// handle all users awaiting verification and notify them
|
||||||
|
$users = UserAwaitingVerification::all();
|
||||||
|
$now = Carbon::now();
|
||||||
|
$now->modify('+' . getenv('verification_cleanup_time'));
|
||||||
|
|
||||||
|
foreach ($users as $user) {
|
||||||
|
$createdAt = DateHelper::convertToCarbon($user->created_at);
|
||||||
|
|
||||||
|
if (!empty($createdAt)) {
|
||||||
|
if ($createdAt->lt($now)) {
|
||||||
|
$mailer = new PHPMailer();
|
||||||
|
$mailer->CharSet = 'UTF-8';
|
||||||
|
$mailer->ContentType = 'text/plain';
|
||||||
|
$mailer->isSMTP();
|
||||||
|
$mailer->SMTPSecure = getenv('mail_secure');
|
||||||
|
$mailer->SMTPAuth = getenv('mail_auth');
|
||||||
|
|
||||||
|
$mailer->Host = getenv('mail_host');
|
||||||
|
$mailer->Port = getenv('mail_port');
|
||||||
|
$mailer->Username = getenv('mail_username');
|
||||||
|
$mailer->Password = getenv('mail_password');
|
||||||
|
$mailer->From = getenv('mail_from');
|
||||||
|
$mailer->FromName = getenv('mail_from_name');
|
||||||
|
|
||||||
|
$mailer->addAddress($user->email);
|
||||||
|
|
||||||
|
$mailer->Subject = $translator->trans('cleanup.mail.subject', ['%server%' => getenv('site_xmpp_server_displayname')]);
|
||||||
|
$mailer->Body = $translator->trans('cleanup.mail.body', ['%username%' => $user->username, '%server%' => getenv('site_xmpp_server_displayname')]);
|
||||||
|
$mailer->send();
|
||||||
|
|
||||||
|
$logger->info($translator->trans('log.verification.cleanup', ['%username%' => $user->username]));
|
||||||
|
$user->delete();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
0
cache/.gitkeep
vendored
Normal file
0
cache/.gitkeep
vendored
Normal file
25
composer.json
Normal file
25
composer.json
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
{
|
||||||
|
"require": {
|
||||||
|
"slim/slim": "^3.0",
|
||||||
|
"monolog/monolog": "^1.18",
|
||||||
|
"slim/twig-view": "^2.1",
|
||||||
|
"slim/flash": "^0.1.0",
|
||||||
|
"wixel/gump": "^1.3",
|
||||||
|
"curl/curl": "^1.4",
|
||||||
|
"phpmailer/phpmailer": "^5.2",
|
||||||
|
"illuminate/database": "~5.2",
|
||||||
|
"davedevelopment/phpmig": "^1.2",
|
||||||
|
"symfony/translation": "^3.1",
|
||||||
|
"symfony/twig-bridge": "^3.1",
|
||||||
|
"vlucas/phpdotenv": "^2.3"
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"bin-dir": "bin/"
|
||||||
|
},
|
||||||
|
"autoload": {
|
||||||
|
"classmap": [
|
||||||
|
"src/",
|
||||||
|
"config/"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
35
config/Config.php
Normal file
35
config/Config.php
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
use Monolog\Logger;
|
||||||
|
|
||||||
|
class Config
|
||||||
|
{
|
||||||
|
public static $CONFIG =
|
||||||
|
[
|
||||||
|
// no need to change anything here
|
||||||
|
'db_settings' => [
|
||||||
|
'driver' => 'sqlite',
|
||||||
|
'database' => __DIR__ . DIRECTORY_SEPARATOR . '..'. DIRECTORY_SEPARATOR .'data'. DIRECTORY_SEPARATOR .'db.sqlite',
|
||||||
|
'charset' => 'utf8',
|
||||||
|
'collation' => 'utf8_unicode_ci',
|
||||||
|
'prefix' => '',
|
||||||
|
],
|
||||||
|
|
||||||
|
'slim_settings' => [
|
||||||
|
'displayErrorDetails' => true,
|
||||||
|
'determineRouteBeforeAppMiddleware' => true,
|
||||||
|
],
|
||||||
|
|
||||||
|
'twig_settings' => [
|
||||||
|
'twig_dir' => __DIR__ . DIRECTORY_SEPARATOR . '..'. DIRECTORY_SEPARATOR .'src'. DIRECTORY_SEPARATOR .'View',
|
||||||
|
'twig_cache_dir' => false,
|
||||||
|
//'twig_cache_dir' => __DIR__ . DIRECTORY_SEPARATOR . '..'. DIRECTORY_SEPARATOR .'src'. DIRECTORY_SEPARATOR .'cache',
|
||||||
|
],
|
||||||
|
|
||||||
|
'logger_settings' => [
|
||||||
|
'level' => Logger::DEBUG,
|
||||||
|
'name' => 'application',
|
||||||
|
'path' => __DIR__ . DIRECTORY_SEPARATOR . '..'. DIRECTORY_SEPARATOR .'log'. DIRECTORY_SEPARATOR .'application.log',
|
||||||
|
],
|
||||||
|
];
|
||||||
|
}
|
39
config/Routes.php
Normal file
39
config/Routes.php
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
// Action factory
|
||||||
|
// error
|
||||||
|
$container[NotFoundAction::class] = function ($c) {
|
||||||
|
return new NotFoundAction($c->get('view'), $c->get('logger'), $c->get('flash'), $c->get('translator'));
|
||||||
|
};
|
||||||
|
$container[NotAuthorizedAction::class] = function ($c) {
|
||||||
|
return new NotAuthorizedAction($c->get('view'), $c->get('logger'), $c->get('flash'), $c->get('translator'));
|
||||||
|
};
|
||||||
|
$container[ForbiddenAction::class] = function ($c) {
|
||||||
|
return new ForbiddenAction($c->get('view'), $c->get('logger'), $c->get('flash'), $c->get('translator'));
|
||||||
|
};
|
||||||
|
$container[InternalApplicationError::class] = function ($c) {
|
||||||
|
return new InternalApplicationError($c->get('view'), $c->get('logger'), $c->get('flash'), $c->get('translator'));
|
||||||
|
};
|
||||||
|
|
||||||
|
// pages
|
||||||
|
$container[IndexAction::class] = function ($c) {
|
||||||
|
return new IndexAction($c->get('view'), $c->get('logger'), $c->get('flash'), $c->get('translator'));
|
||||||
|
};
|
||||||
|
$container[SignUpAction::class] = function ($c) {
|
||||||
|
return new SignUpAction($c->get('view'), $c->get('logger'), $c->get('flash'), $c->get('translator'));
|
||||||
|
};
|
||||||
|
$container[VerificationAction::class] = function ($c) {
|
||||||
|
return new VerificationAction($c->get('view'), $c->get('logger'), $c->get('flash'), $c->get('translator'));
|
||||||
|
};
|
||||||
|
|
||||||
|
// Routes
|
||||||
|
// error
|
||||||
|
$app->get('/401', NotAuthorizedAction::class)->setName('401');
|
||||||
|
$app->get('/403', ForbiddenAction::class)->setName('403');
|
||||||
|
$app->get('/404', NotFoundAction::class)->setName('404');
|
||||||
|
$app->get('/500', InternalApplicationError::class)->setName('500');
|
||||||
|
|
||||||
|
// pages
|
||||||
|
$app->get('/', IndexAction::class)->setName('/');
|
||||||
|
$app->map(['GET', 'POST'], '/signup', SignUpAction::class)->setName('signup');
|
||||||
|
$app->get('/verification/{verificationCode}', VerificationAction::class)->setName('verification');
|
25
config/env.example
Normal file
25
config/env.example
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
# site settings
|
||||||
|
site_title=""
|
||||||
|
site_navbar_index_displayname="Sign Up"
|
||||||
|
site_navbar_backlink_enabled="false" # enables a link in the navbar to go back to e.g. main server site
|
||||||
|
site_navbar_backlink_uri=""
|
||||||
|
site_navbar_backlink_displayname=""
|
||||||
|
site_xmpp_server_displayname="jabber.server.org"
|
||||||
|
|
||||||
|
# verification_timeout and non-verified users will be deleted
|
||||||
|
verification_cleanup_time="7 day"
|
||||||
|
|
||||||
|
# mod_admin_rest Settings
|
||||||
|
xmpp_curl_uri="/admin_rest" # uri to admin_rest
|
||||||
|
xmpp_curl_auth_admin_username="" # configured in prosody lua file
|
||||||
|
xmpp_curl_auth_admin_password="" # configured in prosody lua file
|
||||||
|
|
||||||
|
# Mail Settings
|
||||||
|
mail_host=""
|
||||||
|
mail_port="587"
|
||||||
|
mail_secure="tls"
|
||||||
|
mail_auth="true"
|
||||||
|
mail_username=""
|
||||||
|
mail_password=""
|
||||||
|
mail_from="webmaster@jabber.server.org"
|
||||||
|
mail_from_name="jabber.server.org"
|
17
config/phpmig.php
Normal file
17
config/phpmig.php
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
use \Phpmig\Adapter;
|
||||||
|
|
||||||
|
$container = new ArrayObject();
|
||||||
|
$container['env'] = EnvironmentHelper::getAppEnvironment();
|
||||||
|
$container['db'] = DatabaseHelper::bootORM();
|
||||||
|
|
||||||
|
$container['phpmig.adapter'] = new Phpmig\Adapter\PDO\Sql($container['db']->getConnection()->getPdo(), 'migrations');
|
||||||
|
|
||||||
|
$container['phpmig.migrations_template_path'] = __DIR__ . DIRECTORY_SEPARATOR . '..'. DIRECTORY_SEPARATOR .'data'. DIRECTORY_SEPARATOR .'phpmig_template.php';
|
||||||
|
$container['phpmig.migrations_path'] = __DIR__ . DIRECTORY_SEPARATOR . '..'. DIRECTORY_SEPARATOR .'data'. DIRECTORY_SEPARATOR .'migrations';
|
||||||
|
|
||||||
|
|
||||||
|
$container['schema'] = $container['db']->schema();
|
||||||
|
|
||||||
|
return $container;
|
83
data/locale/messages.en.yml
Normal file
83
data/locale/messages.en.yml
Normal file
|
@ -0,0 +1,83 @@
|
||||||
|
# Sign up
|
||||||
|
sign.up.title: Sign Up
|
||||||
|
sign.up.flash.success: Signed up successfully. Check your inbox.
|
||||||
|
sign.up.flash.already_in_use_email_and_username: %username%/%email% is already in use.
|
||||||
|
sign.up.flash.already_in_use_email: %email% is already in use.
|
||||||
|
sign.up.flash.already_in_use_username: %username% is already in use.
|
||||||
|
sign.up.form.button: Sign up
|
||||||
|
sign.up.form.username: Username:
|
||||||
|
sign.up.form.username.placeholder: username
|
||||||
|
sign.up.form.email: Email (only used for one time email verification, not persisted further):
|
||||||
|
sign.up.form.email.placeholder: xyz@domain.tld
|
||||||
|
sign.up.form.password: Password:
|
||||||
|
sign.up.form.password.placeholder: Password:
|
||||||
|
|
||||||
|
# Verification
|
||||||
|
verification.mail.subject: %server% jabber account verification
|
||||||
|
verification.mail.body: |
|
||||||
|
Hello %username%,
|
||||||
|
you've signed up for a jabber account on %server%.
|
||||||
|
In order to complete your registration, verify your email within 7 days by clicking on %verificationLink%.
|
||||||
|
verification.code.invalid: Verification code %verificationCode% is not valid.
|
||||||
|
verification.flash.already_in_use_username: %username% is already in use.
|
||||||
|
verification.flash.success: Verification successful. You can now sign in to your newly created jabber account %username%@hoth.one.
|
||||||
|
verification.flash.unknown_error: Could not process sign up of %username%. Please contact administrator.
|
||||||
|
verification.mail.success.subject: %server% jabber account information
|
||||||
|
verification.mail.success.body: |
|
||||||
|
Hello %username%,
|
||||||
|
you've verified your email address successfully and your jabber account on %server% has been created.
|
||||||
|
Your password is "%password%". Keep this mail safe!
|
||||||
|
|
||||||
|
# Cleanup
|
||||||
|
cleanup.mail.subject: %server% jabber account verification expired
|
||||||
|
cleanup.mail.body: |
|
||||||
|
Hello %username%,
|
||||||
|
you've recently signed up for a jabber account on %server% but you did not verify your account within 7 days.
|
||||||
|
Your verification code is invalid now.
|
||||||
|
|
||||||
|
# Log
|
||||||
|
log.internal.application.error: Internal application error.
|
||||||
|
log.signed.up: %username% signed up.
|
||||||
|
log.verification.invalid: Tried to use code %verificationCode% but it is invalid.
|
||||||
|
log.verification.sucess: %username% verified.
|
||||||
|
log.verification.unknown_error: Unknown error in XMPP Rest API.
|
||||||
|
log.verification.cleanup: %username% did not verify. Deleted.
|
||||||
|
|
||||||
|
# Error
|
||||||
|
error.401.title: 401
|
||||||
|
error.401.content: Not authorized.
|
||||||
|
error.403.title: 403
|
||||||
|
error.403.content: Forbidden. Not allowed to access.
|
||||||
|
error.404.title: 404
|
||||||
|
error.404.content: Not found.
|
||||||
|
error.500.title: 500
|
||||||
|
error.500.content: Internal application error.
|
||||||
|
|
||||||
|
# Validation
|
||||||
|
mismatch: There is no validation rule for %field%.
|
||||||
|
validate_required: The %field% field is required: The %field% field is required.
|
||||||
|
validate_valid_json_string: Field %field% has to be valid JSON.
|
||||||
|
validate_valid_email: The %field% field is required to be a valid email address.
|
||||||
|
validate_max_len: The %field% field needs to be shorter than %param% characters.
|
||||||
|
validate_min_len: The %field% field needs to be longer than %param% characters.
|
||||||
|
validate_exact_len: The %field% field needs to be exactly %param% characters in length.
|
||||||
|
validate_alpha: The %field% field may only contain alpha characters(a-z).
|
||||||
|
validate_alpha_numeric: The %field% field may only contain alpha-numeric characters.
|
||||||
|
validate_alpha_dash: The %field% field may only contain alpha characters and dashes.
|
||||||
|
validate_numeric: The %field% field may only contain numeric characters.
|
||||||
|
validate_integer: The %field% field may only contain a numeric value.
|
||||||
|
validate_boolean: The %field% field may only contain a true or false value.
|
||||||
|
validate_float: The %field% field may only contain a float value.
|
||||||
|
validate_valid_url: The %field% field is required to be a valid URL.
|
||||||
|
validate_url_exists: The %field% URL does not exist.
|
||||||
|
validate_valid_ip: The %field% field needs to contain a valid IP address.
|
||||||
|
validate_valid_cc: The %field% field needs to contain a valid credit card number.
|
||||||
|
validate_valid_name: The %field% field needs to contain a valid human name.
|
||||||
|
validate_contains: The %field% field needs to contain one of these values: %param%.
|
||||||
|
validate_street_address: The %field% field needs to be a valid street address.
|
||||||
|
validate_date: The %field% field needs to be a valid date.
|
||||||
|
validate_min_numeric: The %field% field needs to be a numeric value, equal to, or higher than %param%.
|
||||||
|
validate_max_numeric: The %field% field needs to be a numeric value, equal to, or lower than %param%.
|
||||||
|
validate_equals: %field% not equal to %param%.
|
||||||
|
validate_set_min_len: %field% needs to have at least %param% item(s).
|
||||||
|
validate_default: Field (%field%) is invalid due to an unknown reason (message missing).
|
|
@ -0,0 +1,39 @@
|
||||||
|
<?php
|
||||||
|
use Phpmig\Migration\Migration;
|
||||||
|
|
||||||
|
class UsersAwaitingVerificationTable extends Migration
|
||||||
|
{
|
||||||
|
public $tableName = 'users_awaiting_verification'; // Table name
|
||||||
|
public $db;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Do the migration
|
||||||
|
*/
|
||||||
|
public function up()
|
||||||
|
{
|
||||||
|
$this->db->create($this->tableName, function($table) {
|
||||||
|
$table->increments('id');
|
||||||
|
$table->string('username');
|
||||||
|
$table->string('email')->unique();
|
||||||
|
$table->string('password');
|
||||||
|
$table->string('verification_code');
|
||||||
|
$table->timestamps();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Undo the migration
|
||||||
|
*/
|
||||||
|
public function down()
|
||||||
|
{
|
||||||
|
$this->db->dropIfExists($this->tableName);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Init the migration
|
||||||
|
*/
|
||||||
|
public function init()
|
||||||
|
{
|
||||||
|
$this->db = $this->container['schema'];
|
||||||
|
}
|
||||||
|
}
|
35
data/phpmig_template.php
Normal file
35
data/phpmig_template.php
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
<?= "<?php ";?>
|
||||||
|
|
||||||
|
use Phpmig\Migration\Migration;
|
||||||
|
|
||||||
|
class <?= $className ?> extends Migration
|
||||||
|
{
|
||||||
|
public $tableName = ''; // Table name
|
||||||
|
public $db;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Do the migration
|
||||||
|
*/
|
||||||
|
public function up()
|
||||||
|
{
|
||||||
|
$this->db->create($this->tableName, function($table) {
|
||||||
|
$table->timestamps();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Undo the migration
|
||||||
|
*/
|
||||||
|
public function down()
|
||||||
|
{
|
||||||
|
$this->db->dropIfExists($this->tableName);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Init the migration
|
||||||
|
*/
|
||||||
|
public function init()
|
||||||
|
{
|
||||||
|
$this->db = $this->container['schema'];
|
||||||
|
}
|
||||||
|
}
|
0
log/.gitkeep
Normal file
0
log/.gitkeep
Normal file
13
public/.htaccess
Normal file
13
public/.htaccess
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
<IfModule mod_rewrite.c>
|
||||||
|
RewriteEngine On
|
||||||
|
|
||||||
|
RewriteCond %{REQUEST_FILENAME} -s [OR]
|
||||||
|
RewriteCond %{REQUEST_FILENAME} -l [OR]
|
||||||
|
RewriteCond %{REQUEST_FILENAME} -d
|
||||||
|
RewriteRule ^.*$ - [NC,L]
|
||||||
|
|
||||||
|
|
||||||
|
RewriteCond %{REQUEST_URI}::$1 ^(/.+)(.+)::\2$
|
||||||
|
RewriteRule ^(.*) - [E=BASE:%1]
|
||||||
|
RewriteRule ^(.*)$ %{ENV:BASE}index.php [NC,L]
|
||||||
|
</IfModule>
|
0
public/css/.gitkeep
Normal file
0
public/css/.gitkeep
Normal file
46
public/css/base.scss
Normal file
46
public/css/base.scss
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
/*
|
||||||
|
* Emerald is a simple blog theme built for Jekyll.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*- Base reset -*/
|
||||||
|
|
||||||
|
* {
|
||||||
|
-webkit-box-sizing: border-box;
|
||||||
|
-moz-box-sizing: border-box;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
html, body, h1, h2, h3, h4, h5, h6, p, ul, ol, li, img {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
border: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*- Base color -*/
|
||||||
|
|
||||||
|
$main-color: #008A3C;
|
||||||
|
$background-color: #FDFDFD;
|
||||||
|
$text-color: #222222;
|
||||||
|
|
||||||
|
/*- Base settings -*/
|
||||||
|
|
||||||
|
html {
|
||||||
|
background-color: $background-color;
|
||||||
|
font-size: 16px;
|
||||||
|
@media (min-width: 940px) {
|
||||||
|
font-size: 18px;
|
||||||
|
}
|
||||||
|
line-height: 1.5;
|
||||||
|
color: $text-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*- Link -*/
|
||||||
|
a {
|
||||||
|
color: $main-color;
|
||||||
|
text-decoration: none;
|
||||||
|
font-weight: 700;
|
||||||
|
&:hover,
|
||||||
|
&:focus {
|
||||||
|
color: darken($main-color, 5%);
|
||||||
|
}
|
||||||
|
}
|
3
public/css/custom.scss
Normal file
3
public/css/custom.scss
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
/*- Custom style -*/
|
||||||
|
|
||||||
|
// -- Put custom style under this point -- //
|
255
public/css/layout.scss
Normal file
255
public/css/layout.scss
Normal file
|
@ -0,0 +1,255 @@
|
||||||
|
/* -- General Layout -- */
|
||||||
|
|
||||||
|
/* Navigation */
|
||||||
|
|
||||||
|
#nav, #nav-left {
|
||||||
|
a {
|
||||||
|
display: block;
|
||||||
|
color: $background-color;
|
||||||
|
padding: 0.33334em 0;
|
||||||
|
font-size: 1.5em;
|
||||||
|
font-weight: 400;
|
||||||
|
@media (min-width: 940px) {
|
||||||
|
font-size: 1em;
|
||||||
|
}
|
||||||
|
&:hover {
|
||||||
|
background-color: lighten($main-color, 5%);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
span {
|
||||||
|
font-weight: 200;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#nav {
|
||||||
|
@include nav-position(right);
|
||||||
|
}
|
||||||
|
|
||||||
|
#nav-left {
|
||||||
|
@include nav-position(left);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Toggle class to open menu */
|
||||||
|
|
||||||
|
#nav.menu-open {
|
||||||
|
@include open(-14rem);
|
||||||
|
}
|
||||||
|
|
||||||
|
#nav-left.menu-open-left {
|
||||||
|
@include open(14rem);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Separator after menu */
|
||||||
|
|
||||||
|
#nav-list:after {
|
||||||
|
display: block;
|
||||||
|
content: '';
|
||||||
|
width: 5rem;
|
||||||
|
height: 1px;
|
||||||
|
margin: 23px auto;
|
||||||
|
background-color: $background-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Icon menu */
|
||||||
|
|
||||||
|
#nav-menu {
|
||||||
|
@include icon-position(right);
|
||||||
|
}
|
||||||
|
|
||||||
|
#nav-menu-left {
|
||||||
|
@include icon-position(left);
|
||||||
|
}
|
||||||
|
|
||||||
|
#menu {
|
||||||
|
height: 4px;
|
||||||
|
width: 1.5em;
|
||||||
|
background-color: lighten($text-color, 35%);
|
||||||
|
margin-top: 8px;
|
||||||
|
&:after, &:before {
|
||||||
|
content: "";
|
||||||
|
display: block;
|
||||||
|
position: relative;
|
||||||
|
height: 4px;
|
||||||
|
width: 1.5em;
|
||||||
|
background-color: lighten($text-color, 35%);
|
||||||
|
transition: all 0.3s ease-in;
|
||||||
|
}
|
||||||
|
&:before {
|
||||||
|
top: -8px;
|
||||||
|
}
|
||||||
|
&:after {
|
||||||
|
top: 4px;
|
||||||
|
}
|
||||||
|
&.btn-close {
|
||||||
|
background: none;
|
||||||
|
}
|
||||||
|
&.btn-close:before {
|
||||||
|
top: 0;
|
||||||
|
-webkit-transform: rotate(-45deg);
|
||||||
|
-moz-transform: rotate(-45deg);
|
||||||
|
-ms-transform: rotate(-45deg);
|
||||||
|
transform: rotate(-45deg);
|
||||||
|
background-color: $background-color;
|
||||||
|
}
|
||||||
|
&.btn-close:after {
|
||||||
|
top: -4px;
|
||||||
|
-webkit-transform: rotate(45deg);
|
||||||
|
-moz-transform: rotate(45deg);
|
||||||
|
-ms-transform: rotate(45deg);
|
||||||
|
transform: rotate(45deg);
|
||||||
|
background-color: $background-color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Main content */
|
||||||
|
|
||||||
|
.fixed {
|
||||||
|
position: fixed;
|
||||||
|
@media (min-width: 940px) {
|
||||||
|
position: static;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#container {
|
||||||
|
margin: 0 auto;
|
||||||
|
max-width: 730px;
|
||||||
|
padding: 0 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
#header {
|
||||||
|
text-align: center;
|
||||||
|
padding: 24px 0;
|
||||||
|
position: relative;
|
||||||
|
a {
|
||||||
|
text-decoration: none;
|
||||||
|
color: $text-color;
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
img {
|
||||||
|
max-height: 72px;
|
||||||
|
margin: 0 auto;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
h1 {
|
||||||
|
font-family: 'Signika', sans-serif; //Emerald logo font
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
&:after {
|
||||||
|
display: block;
|
||||||
|
content: '';
|
||||||
|
width: 5rem;
|
||||||
|
height: 1px;
|
||||||
|
margin: 23px auto;
|
||||||
|
background-color: lighten($text-color, 70%);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Posts */
|
||||||
|
|
||||||
|
#posts {
|
||||||
|
li {
|
||||||
|
list-style-type: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#post-page {
|
||||||
|
margin-bottom: 1.5em;
|
||||||
|
@media (min-width: 940px) {
|
||||||
|
margin-bottom: 1.3334em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.post + .post:before {
|
||||||
|
display: block;
|
||||||
|
content: '';
|
||||||
|
width: 5rem;
|
||||||
|
height: 1px;
|
||||||
|
margin: 23px auto;
|
||||||
|
background-color: lighten($text-color, 70%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.by-line {
|
||||||
|
display: block;
|
||||||
|
color: lighten($text-color, 25%);
|
||||||
|
line-height: 1.5em; /* 24px/16px */
|
||||||
|
margin-bottom: 1.5em; /* 24px/16px */
|
||||||
|
font-weight: 200;
|
||||||
|
@media (min-width: 940px) {
|
||||||
|
display: block;
|
||||||
|
color: lighten($text-color, 25%);
|
||||||
|
line-height: 1.3334em; /* 24px/18px */
|
||||||
|
margin-bottom: 1.3334em; /* 24px/18px */
|
||||||
|
font-weight: 200;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
img {
|
||||||
|
max-width: 100%;
|
||||||
|
display: block;
|
||||||
|
margin: 0 auto;
|
||||||
|
margin-bottom: 24px;
|
||||||
|
-webkit-border-radius: 4px;
|
||||||
|
-moz-border-radius: 4px;
|
||||||
|
-ms-border-radius: 4px;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
img[title="Emerald"] {
|
||||||
|
box-shadow: 0 2px 6px #ddd;
|
||||||
|
}
|
||||||
|
|
||||||
|
code {
|
||||||
|
color: lighten($text-color, 35%);
|
||||||
|
background-color: lighten($background-color, 35%);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Set the vertical rhythm (and padding-left) for lists inside post content */
|
||||||
|
|
||||||
|
.content ul, .content ol {
|
||||||
|
line-height: 1.5em; /* 24px/16px */
|
||||||
|
padding-left: 1.5em;
|
||||||
|
@media (min-width: 940px) {
|
||||||
|
line-height: 1.33334em; /* 24px/18px */
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Pages */
|
||||||
|
|
||||||
|
#page ul, #page ol {
|
||||||
|
padding-left: 1.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Paginator */
|
||||||
|
|
||||||
|
.pagination {
|
||||||
|
text-align: center;
|
||||||
|
margin: 2.666668em;
|
||||||
|
span {
|
||||||
|
background-color: darken($background-color, 5%);
|
||||||
|
color: $text-color;
|
||||||
|
}
|
||||||
|
a:hover {
|
||||||
|
background-color: lighten($main-color, 5%);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-item {
|
||||||
|
background-color: $main-color;
|
||||||
|
color: $background-color;
|
||||||
|
padding: 4px 8px;
|
||||||
|
font-weight: 400;
|
||||||
|
padding: 0.5em 1em;
|
||||||
|
-webkit-border-radius: 4px;
|
||||||
|
-moz-border-radius: 4px;
|
||||||
|
-ms-border-radius: 4px;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Footer */
|
||||||
|
|
||||||
|
footer {
|
||||||
|
background-color: $main-color;
|
||||||
|
color: $background-color;
|
||||||
|
text-align: center;
|
||||||
|
padding: 0.6667em 0;
|
||||||
|
}
|
728
public/css/main.css
Normal file
728
public/css/main.css
Normal file
|
@ -0,0 +1,728 @@
|
||||||
|
/*
|
||||||
|
* Emerald is a simple blog theme built for Jekyll.
|
||||||
|
*/
|
||||||
|
/*- Base reset -*/
|
||||||
|
* {
|
||||||
|
-webkit-box-sizing: border-box;
|
||||||
|
-moz-box-sizing: border-box;
|
||||||
|
box-sizing: border-box; }
|
||||||
|
|
||||||
|
html, body, h1, h2, h3, h4, h5, h6, p, ul, ol, li, img {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
border: 0; }
|
||||||
|
|
||||||
|
/*- Base color -*/
|
||||||
|
/*- Base settings -*/
|
||||||
|
html {
|
||||||
|
background-color: #FDFDFD;
|
||||||
|
font-size: 16px;
|
||||||
|
line-height: 1.5;
|
||||||
|
color: #222222; }
|
||||||
|
@media (min-width: 940px) {
|
||||||
|
html {
|
||||||
|
font-size: 18px; } }
|
||||||
|
|
||||||
|
/*- Link -*/
|
||||||
|
a {
|
||||||
|
color: #008A3C;
|
||||||
|
text-decoration: none;
|
||||||
|
font-weight: 700; }
|
||||||
|
a:hover, a:focus {
|
||||||
|
color: #007131; }
|
||||||
|
|
||||||
|
/*- Typography -*/
|
||||||
|
body {
|
||||||
|
font-family: 'Source Sans Pro', sans-serif;
|
||||||
|
letter-spacing: 0.01em; }
|
||||||
|
|
||||||
|
/*- Typography for medium and small screen, based on 16px font-size -*/
|
||||||
|
p, ul, ol {
|
||||||
|
font-size: 1em;
|
||||||
|
/* 16px */
|
||||||
|
line-height: 1.5em;
|
||||||
|
/* 24px/16px */
|
||||||
|
margin-bottom: 1.5em;
|
||||||
|
/* 24px/16px */ }
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
font-size: 2.25em;
|
||||||
|
/* 36px/16px */
|
||||||
|
line-height: 1.3333em;
|
||||||
|
/* 48px/36px */
|
||||||
|
padding: 0.33335em 0;
|
||||||
|
/* 12px/36px * 2 (Use padding instead of margin to maintain proximity with paragraph) */ }
|
||||||
|
|
||||||
|
h2 {
|
||||||
|
font-size: 1.5em;
|
||||||
|
/* 24px/16px */
|
||||||
|
line-height: 1em;
|
||||||
|
/* 24px/24px */
|
||||||
|
padding: 1em 0 0 0;
|
||||||
|
/* 12px/24px * 2, only top (Use padding instead of margin to maintain proximity with paragwithph) */ }
|
||||||
|
|
||||||
|
h3, h4, h5, h6 {
|
||||||
|
font-size: 1.125em;
|
||||||
|
/* 18px/16px */
|
||||||
|
line-height: 1.3334em;
|
||||||
|
/* 24px/18px */
|
||||||
|
padding: 0.66667em 0;
|
||||||
|
/* 12px/18px * 2 (Use padding instead of margin to maintain proximity with paragraph) */ }
|
||||||
|
|
||||||
|
blockquote {
|
||||||
|
font-style: italic;
|
||||||
|
margin: 1.5em;
|
||||||
|
/* 24px/18px */
|
||||||
|
-webkit-border-radius: 4px;
|
||||||
|
-moz-border-radius: 4px;
|
||||||
|
-ms-border-radius: 4px;
|
||||||
|
border-radius: 4px;
|
||||||
|
background-color: #f0f0f0;
|
||||||
|
padding: 0 1.5em;
|
||||||
|
/* 24px/18px */ }
|
||||||
|
blockquote p, blockquote ul, blockquote ol {
|
||||||
|
padding: 1.5em 0;
|
||||||
|
/* 24px/18px */ }
|
||||||
|
|
||||||
|
/*- Typography for big screen, based on 18px font-size -*/
|
||||||
|
@media (min-width: 940px) {
|
||||||
|
p, ul, ol {
|
||||||
|
font-size: 1em;
|
||||||
|
/* 18px */
|
||||||
|
line-height: 1.3334em;
|
||||||
|
/* 24px/18px */
|
||||||
|
margin-bottom: 1.3334em;
|
||||||
|
/* 24px/18px */ }
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
font-size: 2.6667em;
|
||||||
|
/* 48px/18px */
|
||||||
|
line-height: 1em;
|
||||||
|
/* 48px/48px */
|
||||||
|
padding: 0.25em 0;
|
||||||
|
/* 12px/48px * 2 (Use padding instead of margin to maintain proximity with paragraph) */ }
|
||||||
|
|
||||||
|
h2 {
|
||||||
|
font-size: 2em;
|
||||||
|
/* 36px/18px */
|
||||||
|
line-height: 1.3334em;
|
||||||
|
/* 48px/36px */
|
||||||
|
padding: 0.66667em 0 0 0;
|
||||||
|
/* 12px/36px * 2, pnly top (Use padding instead of margin to maintain proximity with paragraph) */ }
|
||||||
|
|
||||||
|
h3, h4, h5, h6 {
|
||||||
|
font-size: 1.3334em;
|
||||||
|
/* 24px/18px */
|
||||||
|
line-height: 1em;
|
||||||
|
/* 24px/24px */
|
||||||
|
padding: 0.5em 0;
|
||||||
|
/* 12px/24px * 2 (Use padding instead of margin to maintain proximity with paragraph) */ }
|
||||||
|
|
||||||
|
blockquote {
|
||||||
|
font-style: italic;
|
||||||
|
margin: 1.3334em;
|
||||||
|
/* 24px/18px */
|
||||||
|
-webkit-border-radius: 4px;
|
||||||
|
-moz-border-radius: 4px;
|
||||||
|
-ms-border-radius: 4px;
|
||||||
|
border-radius: 4px;
|
||||||
|
background-color: #f0f0f0;
|
||||||
|
padding: 0 1.33334em;
|
||||||
|
/* 24px/18px */ }
|
||||||
|
blockquote p, blockquote ul, blockquote ol {
|
||||||
|
padding: 1.33334em 0;
|
||||||
|
/* 24px/18px */ } }
|
||||||
|
/* -- General Layout -- */
|
||||||
|
/* Navigation */
|
||||||
|
#nav a, #nav-left a {
|
||||||
|
display: block;
|
||||||
|
color: #FDFDFD;
|
||||||
|
padding: 0.33334em 0;
|
||||||
|
font-size: 1.5em;
|
||||||
|
font-weight: 400; }
|
||||||
|
@media (min-width: 940px) {
|
||||||
|
#nav a, #nav-left a {
|
||||||
|
font-size: 1em; } }
|
||||||
|
#nav a:hover, #nav-left a:hover {
|
||||||
|
background-color: #00a447; }
|
||||||
|
#nav span, #nav-left span {
|
||||||
|
font-weight: 200; }
|
||||||
|
|
||||||
|
#nav {
|
||||||
|
width: 14rem;
|
||||||
|
position: fixed;
|
||||||
|
background-color: #008A3C;
|
||||||
|
top: 0;
|
||||||
|
bottom: 0;
|
||||||
|
right: -14rem;
|
||||||
|
color: #FDFDFD;
|
||||||
|
opacity: 0.95;
|
||||||
|
-webkit-transition: all 0.3s ease-in;
|
||||||
|
-moz-transition: all 0.3s ease-in;
|
||||||
|
-ms-transition: all 0.3s ease-in;
|
||||||
|
transition: all 0.3s ease-in;
|
||||||
|
z-index: 1;
|
||||||
|
padding: 72px 0;
|
||||||
|
text-align: center; }
|
||||||
|
|
||||||
|
#nav-left {
|
||||||
|
width: 14rem;
|
||||||
|
position: fixed;
|
||||||
|
background-color: #008A3C;
|
||||||
|
top: 0;
|
||||||
|
bottom: 0;
|
||||||
|
left: -14rem;
|
||||||
|
color: #FDFDFD;
|
||||||
|
opacity: 0.95;
|
||||||
|
-webkit-transition: all 0.3s ease-in;
|
||||||
|
-moz-transition: all 0.3s ease-in;
|
||||||
|
-ms-transition: all 0.3s ease-in;
|
||||||
|
transition: all 0.3s ease-in;
|
||||||
|
z-index: 1;
|
||||||
|
padding: 72px 0;
|
||||||
|
text-align: center; }
|
||||||
|
|
||||||
|
/* Toggle class to open menu */
|
||||||
|
#nav.menu-open {
|
||||||
|
-webkit-transform: translateX(-14rem);
|
||||||
|
-moz-transform: translateX(-14rem);
|
||||||
|
-ms-transform: translateX(-14rem);
|
||||||
|
transform: translateX(-14rem);
|
||||||
|
width: 100%; }
|
||||||
|
@media (min-width: 940px) {
|
||||||
|
#nav.menu-open {
|
||||||
|
width: 30%; } }
|
||||||
|
|
||||||
|
#nav-left.menu-open-left {
|
||||||
|
-webkit-transform: translateX(14rem);
|
||||||
|
-moz-transform: translateX(14rem);
|
||||||
|
-ms-transform: translateX(14rem);
|
||||||
|
transform: translateX(14rem);
|
||||||
|
width: 100%; }
|
||||||
|
@media (min-width: 940px) {
|
||||||
|
#nav-left.menu-open-left {
|
||||||
|
width: 30%; } }
|
||||||
|
|
||||||
|
/* Separator after menu */
|
||||||
|
#nav-list:after {
|
||||||
|
display: block;
|
||||||
|
content: '';
|
||||||
|
width: 5rem;
|
||||||
|
height: 1px;
|
||||||
|
margin: 23px auto;
|
||||||
|
background-color: #FDFDFD; }
|
||||||
|
|
||||||
|
/* Icon menu */
|
||||||
|
#nav-menu {
|
||||||
|
display: block;
|
||||||
|
position: fixed;
|
||||||
|
top: 35px;
|
||||||
|
right: 25px;
|
||||||
|
z-index: 10;
|
||||||
|
height: 24px; }
|
||||||
|
|
||||||
|
#nav-menu-left {
|
||||||
|
display: block;
|
||||||
|
position: fixed;
|
||||||
|
top: 35px;
|
||||||
|
left: 25px;
|
||||||
|
z-index: 10;
|
||||||
|
height: 24px; }
|
||||||
|
|
||||||
|
#menu {
|
||||||
|
height: 4px;
|
||||||
|
width: 1.5em;
|
||||||
|
background-color: #7b7b7b;
|
||||||
|
margin-top: 8px; }
|
||||||
|
#menu:after, #menu:before {
|
||||||
|
content: "";
|
||||||
|
display: block;
|
||||||
|
position: relative;
|
||||||
|
height: 4px;
|
||||||
|
width: 1.5em;
|
||||||
|
background-color: #7b7b7b;
|
||||||
|
transition: all 0.3s ease-in; }
|
||||||
|
#menu:before {
|
||||||
|
top: -8px; }
|
||||||
|
#menu:after {
|
||||||
|
top: 4px; }
|
||||||
|
#menu.btn-close {
|
||||||
|
background: none; }
|
||||||
|
#menu.btn-close:before {
|
||||||
|
top: 0;
|
||||||
|
-webkit-transform: rotate(-45deg);
|
||||||
|
-moz-transform: rotate(-45deg);
|
||||||
|
-ms-transform: rotate(-45deg);
|
||||||
|
transform: rotate(-45deg);
|
||||||
|
background-color: #FDFDFD; }
|
||||||
|
#menu.btn-close:after {
|
||||||
|
top: -4px;
|
||||||
|
-webkit-transform: rotate(45deg);
|
||||||
|
-moz-transform: rotate(45deg);
|
||||||
|
-ms-transform: rotate(45deg);
|
||||||
|
transform: rotate(45deg);
|
||||||
|
background-color: #FDFDFD; }
|
||||||
|
|
||||||
|
/* Main content */
|
||||||
|
.fixed {
|
||||||
|
position: fixed; }
|
||||||
|
@media (min-width: 940px) {
|
||||||
|
.fixed {
|
||||||
|
position: static; } }
|
||||||
|
|
||||||
|
#container {
|
||||||
|
margin: 0 auto;
|
||||||
|
max-width: 730px;
|
||||||
|
padding: 0 1.5rem; }
|
||||||
|
|
||||||
|
#header {
|
||||||
|
text-align: center;
|
||||||
|
padding: 24px 0;
|
||||||
|
position: relative; }
|
||||||
|
#header a {
|
||||||
|
text-decoration: none;
|
||||||
|
color: #222222;
|
||||||
|
display: inline-block; }
|
||||||
|
#header img {
|
||||||
|
max-height: 72px;
|
||||||
|
margin: 0 auto;
|
||||||
|
display: block; }
|
||||||
|
#header h1 {
|
||||||
|
font-family: 'Signika', sans-serif;
|
||||||
|
font-weight: 600; }
|
||||||
|
#header:after {
|
||||||
|
display: block;
|
||||||
|
content: '';
|
||||||
|
width: 5rem;
|
||||||
|
height: 1px;
|
||||||
|
margin: 23px auto;
|
||||||
|
background-color: #d5d5d5; }
|
||||||
|
|
||||||
|
/* Posts */
|
||||||
|
#posts li {
|
||||||
|
list-style-type: none; }
|
||||||
|
|
||||||
|
#post-page {
|
||||||
|
margin-bottom: 1.5em; }
|
||||||
|
@media (min-width: 940px) {
|
||||||
|
#post-page {
|
||||||
|
margin-bottom: 1.3334em; } }
|
||||||
|
|
||||||
|
.post + .post:before {
|
||||||
|
display: block;
|
||||||
|
content: '';
|
||||||
|
width: 5rem;
|
||||||
|
height: 1px;
|
||||||
|
margin: 23px auto;
|
||||||
|
background-color: #d5d5d5; }
|
||||||
|
|
||||||
|
.by-line {
|
||||||
|
display: block;
|
||||||
|
color: #626262;
|
||||||
|
line-height: 1.5em;
|
||||||
|
/* 24px/16px */
|
||||||
|
margin-bottom: 1.5em;
|
||||||
|
/* 24px/16px */
|
||||||
|
font-weight: 200; }
|
||||||
|
@media (min-width: 940px) {
|
||||||
|
.by-line {
|
||||||
|
display: block;
|
||||||
|
color: #626262;
|
||||||
|
line-height: 1.3334em;
|
||||||
|
/* 24px/18px */
|
||||||
|
margin-bottom: 1.3334em;
|
||||||
|
/* 24px/18px */
|
||||||
|
font-weight: 200; } }
|
||||||
|
|
||||||
|
img {
|
||||||
|
max-width: 100%;
|
||||||
|
display: block;
|
||||||
|
margin: 0 auto;
|
||||||
|
margin-bottom: 24px;
|
||||||
|
-webkit-border-radius: 4px;
|
||||||
|
-moz-border-radius: 4px;
|
||||||
|
-ms-border-radius: 4px;
|
||||||
|
border-radius: 4px; }
|
||||||
|
|
||||||
|
img[title="Emerald"] {
|
||||||
|
box-shadow: 0 2px 6px #ddd; }
|
||||||
|
|
||||||
|
code {
|
||||||
|
color: #7b7b7b;
|
||||||
|
background-color: white; }
|
||||||
|
|
||||||
|
/* Set the vertical rhythm (and padding-left) for lists inside post content */
|
||||||
|
.content ul, .content ol {
|
||||||
|
line-height: 1.5em;
|
||||||
|
/* 24px/16px */
|
||||||
|
padding-left: 1.5em; }
|
||||||
|
@media (min-width: 940px) {
|
||||||
|
.content ul, .content ol {
|
||||||
|
line-height: 1.33334em;
|
||||||
|
/* 24px/18px */ } }
|
||||||
|
|
||||||
|
/* Pages */
|
||||||
|
#page ul, #page ol {
|
||||||
|
padding-left: 1.5em; }
|
||||||
|
|
||||||
|
/* Paginator */
|
||||||
|
.pagination {
|
||||||
|
text-align: center;
|
||||||
|
margin: 2.666668em; }
|
||||||
|
.pagination span {
|
||||||
|
background-color: #f0f0f0;
|
||||||
|
color: #222222; }
|
||||||
|
.pagination a:hover {
|
||||||
|
background-color: #00a447; }
|
||||||
|
|
||||||
|
.page-item {
|
||||||
|
background-color: #008A3C;
|
||||||
|
color: #FDFDFD;
|
||||||
|
padding: 4px 8px;
|
||||||
|
font-weight: 400;
|
||||||
|
padding: 0.5em 1em;
|
||||||
|
-webkit-border-radius: 4px;
|
||||||
|
-moz-border-radius: 4px;
|
||||||
|
-ms-border-radius: 4px;
|
||||||
|
border-radius: 4px; }
|
||||||
|
|
||||||
|
/* Footer */
|
||||||
|
footer {
|
||||||
|
background-color: #008A3C;
|
||||||
|
color: #FDFDFD;
|
||||||
|
text-align: center;
|
||||||
|
padding: 0.6667em 0; }
|
||||||
|
|
||||||
|
/*
|
||||||
|
* A Github stylesheet to highlight code snippet
|
||||||
|
* https://github.com/mojombo/tpw/blob/master/css/syntax.css
|
||||||
|
*/
|
||||||
|
.lineno {
|
||||||
|
color: #bdbdbd;
|
||||||
|
margin-right: 1em; }
|
||||||
|
|
||||||
|
.highlight .c {
|
||||||
|
color: #999988;
|
||||||
|
font-style: italic; }
|
||||||
|
|
||||||
|
/* Comment */
|
||||||
|
.highlight .err {
|
||||||
|
color: #a61717;
|
||||||
|
background-color: #e3d2d2; }
|
||||||
|
|
||||||
|
/* Error */
|
||||||
|
.highlight .k {
|
||||||
|
font-weight: bold; }
|
||||||
|
|
||||||
|
/* Keyword */
|
||||||
|
.highlight .o {
|
||||||
|
font-weight: bold; }
|
||||||
|
|
||||||
|
/* Operator */
|
||||||
|
.highlight .cm {
|
||||||
|
color: #999988;
|
||||||
|
font-style: italic; }
|
||||||
|
|
||||||
|
/* Comment.Multiline */
|
||||||
|
.highlight .cp {
|
||||||
|
color: #999999;
|
||||||
|
font-weight: bold; }
|
||||||
|
|
||||||
|
/* Comment.Preproc */
|
||||||
|
.highlight .c1 {
|
||||||
|
color: #999988;
|
||||||
|
font-style: italic; }
|
||||||
|
|
||||||
|
/* Comment.Single */
|
||||||
|
.highlight .cs {
|
||||||
|
color: #999999;
|
||||||
|
font-weight: bold;
|
||||||
|
font-style: italic; }
|
||||||
|
|
||||||
|
/* Comment.Special */
|
||||||
|
.highlight .gd {
|
||||||
|
color: #000000;
|
||||||
|
background-color: #ffdddd; }
|
||||||
|
|
||||||
|
/* Generic.Deleted */
|
||||||
|
.highlight .gd .x {
|
||||||
|
color: #000000;
|
||||||
|
background-color: #ffaaaa; }
|
||||||
|
|
||||||
|
/* Generic.Deleted.Specific */
|
||||||
|
.highlight .ge {
|
||||||
|
font-style: italic; }
|
||||||
|
|
||||||
|
/* Generic.Emph */
|
||||||
|
.highlight .gr {
|
||||||
|
color: #aa0000; }
|
||||||
|
|
||||||
|
/* Generic.Error */
|
||||||
|
.highlight .gh {
|
||||||
|
color: #999999; }
|
||||||
|
|
||||||
|
/* Generic.Heading */
|
||||||
|
.highlight .gi {
|
||||||
|
color: #000000;
|
||||||
|
background-color: #ddffdd; }
|
||||||
|
|
||||||
|
/* Generic.Inserted */
|
||||||
|
.highlight .gi .x {
|
||||||
|
color: #000000;
|
||||||
|
background-color: #aaffaa; }
|
||||||
|
|
||||||
|
/* Generic.Inserted.Specific */
|
||||||
|
.highlight .go {
|
||||||
|
color: #888888; }
|
||||||
|
|
||||||
|
/* Generic.Output */
|
||||||
|
.highlight .gp {
|
||||||
|
color: #555555; }
|
||||||
|
|
||||||
|
/* Generic.Prompt */
|
||||||
|
.highlight .gs {
|
||||||
|
font-weight: bold; }
|
||||||
|
|
||||||
|
/* Generic.Strong */
|
||||||
|
.highlight .gu {
|
||||||
|
color: #aaaaaa; }
|
||||||
|
|
||||||
|
/* Generic.Subheading */
|
||||||
|
.highlight .gt {
|
||||||
|
color: #aa0000; }
|
||||||
|
|
||||||
|
/* Generic.Traceback */
|
||||||
|
.highlight .kc {
|
||||||
|
font-weight: bold; }
|
||||||
|
|
||||||
|
/* Keyword.Constant */
|
||||||
|
.highlight .kd {
|
||||||
|
font-weight: bold; }
|
||||||
|
|
||||||
|
/* Keyword.Declaration */
|
||||||
|
.highlight .kp {
|
||||||
|
font-weight: bold; }
|
||||||
|
|
||||||
|
/* Keyword.Pseudo */
|
||||||
|
.highlight .kr {
|
||||||
|
font-weight: bold; }
|
||||||
|
|
||||||
|
/* Keyword.Reserved */
|
||||||
|
.highlight .kt {
|
||||||
|
color: #445588;
|
||||||
|
font-weight: bold; }
|
||||||
|
|
||||||
|
/* Keyword.Type */
|
||||||
|
.highlight .m {
|
||||||
|
color: #009999; }
|
||||||
|
|
||||||
|
/* Literal.Number */
|
||||||
|
.highlight .s {
|
||||||
|
color: #d14; }
|
||||||
|
|
||||||
|
/* Literal.String */
|
||||||
|
.highlight .na {
|
||||||
|
color: #008080; }
|
||||||
|
|
||||||
|
/* Name.Attribute */
|
||||||
|
.highlight .nb {
|
||||||
|
color: #0086B3; }
|
||||||
|
|
||||||
|
/* Name.Builtin */
|
||||||
|
.highlight .nc {
|
||||||
|
color: #445588;
|
||||||
|
font-weight: bold; }
|
||||||
|
|
||||||
|
/* Name.Class */
|
||||||
|
.highlight .no {
|
||||||
|
color: #008080; }
|
||||||
|
|
||||||
|
/* Name.Constant */
|
||||||
|
.highlight .ni {
|
||||||
|
color: #800080; }
|
||||||
|
|
||||||
|
/* Name.Entity */
|
||||||
|
.highlight .ne {
|
||||||
|
color: #990000;
|
||||||
|
font-weight: bold; }
|
||||||
|
|
||||||
|
/* Name.Exception */
|
||||||
|
.highlight .nf {
|
||||||
|
color: #990000;
|
||||||
|
font-weight: bold; }
|
||||||
|
|
||||||
|
/* Name.Function */
|
||||||
|
.highlight .nn {
|
||||||
|
color: #555555; }
|
||||||
|
|
||||||
|
/* Name.Namespace */
|
||||||
|
.highlight .nt {
|
||||||
|
color: #000080; }
|
||||||
|
|
||||||
|
/* Name.Tag */
|
||||||
|
.highlight .nv {
|
||||||
|
color: #008080; }
|
||||||
|
|
||||||
|
/* Name.Variable */
|
||||||
|
.highlight .ow {
|
||||||
|
font-weight: bold; }
|
||||||
|
|
||||||
|
/* Operator.Word */
|
||||||
|
.highlight .w {
|
||||||
|
color: #bbbbbb; }
|
||||||
|
|
||||||
|
/* Text.Whitespace */
|
||||||
|
.highlight .mf {
|
||||||
|
color: #009999; }
|
||||||
|
|
||||||
|
/* Literal.Number.Float */
|
||||||
|
.highlight .mh {
|
||||||
|
color: #009999; }
|
||||||
|
|
||||||
|
/* Literal.Number.Hex */
|
||||||
|
.highlight .mi {
|
||||||
|
color: #009999; }
|
||||||
|
|
||||||
|
/* Literal.Number.Integer */
|
||||||
|
.highlight .mo {
|
||||||
|
color: #009999; }
|
||||||
|
|
||||||
|
/* Literal.Number.Oct */
|
||||||
|
.highlight .sb {
|
||||||
|
color: #d14; }
|
||||||
|
|
||||||
|
/* Literal.String.Backtick */
|
||||||
|
.highlight .sc {
|
||||||
|
color: #d14; }
|
||||||
|
|
||||||
|
/* Literal.String.Char */
|
||||||
|
.highlight .sd {
|
||||||
|
color: #d14; }
|
||||||
|
|
||||||
|
/* Literal.String.Doc */
|
||||||
|
.highlight .s2 {
|
||||||
|
color: #d14; }
|
||||||
|
|
||||||
|
/* Literal.String.Double */
|
||||||
|
.highlight .se {
|
||||||
|
color: #d14; }
|
||||||
|
|
||||||
|
/* Literal.String.Escape */
|
||||||
|
.highlight .sh {
|
||||||
|
color: #d14; }
|
||||||
|
|
||||||
|
/* Literal.String.Heredoc */
|
||||||
|
.highlight .si {
|
||||||
|
color: #d14; }
|
||||||
|
|
||||||
|
/* Literal.String.Interpol */
|
||||||
|
.highlight .sx {
|
||||||
|
color: #d14; }
|
||||||
|
|
||||||
|
/* Literal.String.Other */
|
||||||
|
.highlight .sr {
|
||||||
|
color: #009926; }
|
||||||
|
|
||||||
|
/* Literal.String.Regex */
|
||||||
|
.highlight .s1 {
|
||||||
|
color: #d14; }
|
||||||
|
|
||||||
|
/* Literal.String.Single */
|
||||||
|
.highlight .ss {
|
||||||
|
color: #990073; }
|
||||||
|
|
||||||
|
/* Literal.String.Symbol */
|
||||||
|
.highlight .bp {
|
||||||
|
color: #999999; }
|
||||||
|
|
||||||
|
/* Name.Builtin.Pseudo */
|
||||||
|
.highlight .vc {
|
||||||
|
color: #008080; }
|
||||||
|
|
||||||
|
/* Name.Variable.Class */
|
||||||
|
.highlight .vg {
|
||||||
|
color: #008080; }
|
||||||
|
|
||||||
|
/* Name.Variable.Global */
|
||||||
|
.highlight .vi {
|
||||||
|
color: #008080; }
|
||||||
|
|
||||||
|
/* Name.Variable.Instance */
|
||||||
|
.highlight .il {
|
||||||
|
color: #009999; }
|
||||||
|
|
||||||
|
/* Literal.Number.Integer.Long */
|
||||||
|
/*- Custom style -*/
|
||||||
|
.alert {
|
||||||
|
padding: 15px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
border: 1px solid transparent;
|
||||||
|
border-radius: 4px; }
|
||||||
|
|
||||||
|
.alert h4 {
|
||||||
|
margin-top: 0;
|
||||||
|
color: inherit; }
|
||||||
|
|
||||||
|
.alert .alert-link {
|
||||||
|
font-weight: 700; }
|
||||||
|
|
||||||
|
.alert > p, .alert > ul {
|
||||||
|
margin-bottom: 0; }
|
||||||
|
|
||||||
|
.alert > p + p {
|
||||||
|
margin-top: 5px; }
|
||||||
|
|
||||||
|
.alert-dismissable, .alert-dismissible {
|
||||||
|
padding-right: 35px; }
|
||||||
|
|
||||||
|
.alert-dismissable .close, .alert-dismissible .close {
|
||||||
|
position: relative;
|
||||||
|
top: -2px;
|
||||||
|
right: -21px;
|
||||||
|
color: inherit; }
|
||||||
|
|
||||||
|
.alert-success {
|
||||||
|
color: #3c763d;
|
||||||
|
background-color: #dff0d8;
|
||||||
|
border-color: #d6e9c6; }
|
||||||
|
|
||||||
|
.alert-success hr {
|
||||||
|
border-top-color: #c9e2b3; }
|
||||||
|
|
||||||
|
.alert-success .alert-link {
|
||||||
|
color: #2b542c; }
|
||||||
|
|
||||||
|
.alert-info {
|
||||||
|
color: #31708f;
|
||||||
|
background-color: #d9edf7;
|
||||||
|
border-color: #bce8f1; }
|
||||||
|
|
||||||
|
.alert-info hr {
|
||||||
|
border-top-color: #a6e1ec; }
|
||||||
|
|
||||||
|
.alert-info .alert-link {
|
||||||
|
color: #245269; }
|
||||||
|
|
||||||
|
.alert-warning {
|
||||||
|
color: #8a6d3b;
|
||||||
|
background-color: #fcf8e3;
|
||||||
|
border-color: #faebcc; }
|
||||||
|
|
||||||
|
.alert-warning hr {
|
||||||
|
border-top-color: #f7e1b5; }
|
||||||
|
|
||||||
|
.alert-warning .alert-link {
|
||||||
|
color: #66512c; }
|
||||||
|
|
||||||
|
.alert-danger {
|
||||||
|
color: #a94442;
|
||||||
|
background-color: #f2dede;
|
||||||
|
border-color: #ebccd1; }
|
||||||
|
|
||||||
|
.alert-danger hr {
|
||||||
|
border-top-color: #e4b9c0; }
|
||||||
|
|
||||||
|
.alert-danger .alert-link {
|
||||||
|
color: #843534; }
|
||||||
|
|
||||||
|
/*# sourceMappingURL=main.css.map */
|
7
public/css/main.css.map
Normal file
7
public/css/main.css.map
Normal file
File diff suppressed because one or more lines are too long
93
public/css/main.scss
Normal file
93
public/css/main.scss
Normal file
|
@ -0,0 +1,93 @@
|
||||||
|
//Import
|
||||||
|
@import "base", "mixin", "typography", "layout", "syntax.scss", "custom.scss";
|
||||||
|
|
||||||
|
.alert {
|
||||||
|
padding: 15px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
border: 1px solid transparent;
|
||||||
|
border-radius: 4px
|
||||||
|
}
|
||||||
|
|
||||||
|
.alert h4 {
|
||||||
|
margin-top: 0;
|
||||||
|
color: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
.alert .alert-link {
|
||||||
|
font-weight: 700
|
||||||
|
}
|
||||||
|
|
||||||
|
.alert > p, .alert > ul {
|
||||||
|
margin-bottom: 0
|
||||||
|
}
|
||||||
|
|
||||||
|
.alert > p + p {
|
||||||
|
margin-top: 5px
|
||||||
|
}
|
||||||
|
|
||||||
|
.alert-dismissable, .alert-dismissible {
|
||||||
|
padding-right: 35px
|
||||||
|
}
|
||||||
|
|
||||||
|
.alert-dismissable .close, .alert-dismissible .close {
|
||||||
|
position: relative;
|
||||||
|
top: -2px;
|
||||||
|
right: -21px;
|
||||||
|
color: inherit
|
||||||
|
}
|
||||||
|
|
||||||
|
.alert-success {
|
||||||
|
color: #3c763d;
|
||||||
|
background-color: #dff0d8;
|
||||||
|
border-color: #d6e9c6
|
||||||
|
}
|
||||||
|
|
||||||
|
.alert-success hr {
|
||||||
|
border-top-color: #c9e2b3
|
||||||
|
}
|
||||||
|
|
||||||
|
.alert-success .alert-link {
|
||||||
|
color: #2b542c
|
||||||
|
}
|
||||||
|
|
||||||
|
.alert-info {
|
||||||
|
color: #31708f;
|
||||||
|
background-color: #d9edf7;
|
||||||
|
border-color: #bce8f1
|
||||||
|
}
|
||||||
|
|
||||||
|
.alert-info hr {
|
||||||
|
border-top-color: #a6e1ec
|
||||||
|
}
|
||||||
|
|
||||||
|
.alert-info .alert-link {
|
||||||
|
color: #245269
|
||||||
|
}
|
||||||
|
|
||||||
|
.alert-warning {
|
||||||
|
color: #8a6d3b;
|
||||||
|
background-color: #fcf8e3;
|
||||||
|
border-color: #faebcc
|
||||||
|
}
|
||||||
|
|
||||||
|
.alert-warning hr {
|
||||||
|
border-top-color: #f7e1b5
|
||||||
|
}
|
||||||
|
|
||||||
|
.alert-warning .alert-link {
|
||||||
|
color: #66512c
|
||||||
|
}
|
||||||
|
|
||||||
|
.alert-danger {
|
||||||
|
color: #a94442;
|
||||||
|
background-color: #f2dede;
|
||||||
|
border-color: #ebccd1
|
||||||
|
}
|
||||||
|
|
||||||
|
.alert-danger hr {
|
||||||
|
border-top-color: #e4b9c0
|
||||||
|
}
|
||||||
|
|
||||||
|
.alert-danger .alert-link {
|
||||||
|
color: #843534
|
||||||
|
}
|
41
public/css/mixin.scss
Normal file
41
public/css/mixin.scss
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
// -- Mixins -- //
|
||||||
|
|
||||||
|
// Nav menu
|
||||||
|
|
||||||
|
@mixin icon-position($position) {
|
||||||
|
display: block;
|
||||||
|
position: fixed;
|
||||||
|
top: 35px;
|
||||||
|
#{$position}: 25px;
|
||||||
|
z-index: 10;
|
||||||
|
height: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@mixin open($x) {
|
||||||
|
-webkit-transform: translateX($x);
|
||||||
|
-moz-transform: translateX($x);
|
||||||
|
-ms-transform: translateX($x);
|
||||||
|
transform: translateX($x);
|
||||||
|
width: 100%;
|
||||||
|
@media (min-width: 940px) {
|
||||||
|
width: 30%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@mixin nav-position($position) {
|
||||||
|
width: 14rem;
|
||||||
|
position: fixed;
|
||||||
|
background-color: $main-color;
|
||||||
|
top: 0;
|
||||||
|
bottom: 0;
|
||||||
|
#{$position}: -14rem;
|
||||||
|
color: $background-color;
|
||||||
|
opacity: 0.95;
|
||||||
|
-webkit-transition: all 0.3s ease-in;
|
||||||
|
-moz-transition: all 0.3s ease-in;
|
||||||
|
-ms-transition: all 0.3s ease-in;
|
||||||
|
transition: all 0.3s ease-in;
|
||||||
|
z-index: 1;
|
||||||
|
padding: 72px 0;
|
||||||
|
text-align: center;
|
||||||
|
}
|
66
public/css/syntax.scss
Normal file
66
public/css/syntax.scss
Normal file
|
@ -0,0 +1,66 @@
|
||||||
|
/*
|
||||||
|
* A Github stylesheet to highlight code snippet
|
||||||
|
* https://github.com/mojombo/tpw/blob/master/css/syntax.css
|
||||||
|
*/
|
||||||
|
|
||||||
|
// .highlight { background-color: #FFF; }
|
||||||
|
.lineno { color: darken($background-color, 25%); margin-right: 1em; }
|
||||||
|
.highlight .c { color: #999988; font-style: italic } /* Comment */
|
||||||
|
.highlight .err { color: #a61717; background-color: #e3d2d2 } /* Error */
|
||||||
|
.highlight .k { font-weight: bold } /* Keyword */
|
||||||
|
.highlight .o { font-weight: bold } /* Operator */
|
||||||
|
.highlight .cm { color: #999988; font-style: italic } /* Comment.Multiline */
|
||||||
|
.highlight .cp { color: #999999; font-weight: bold } /* Comment.Preproc */
|
||||||
|
.highlight .c1 { color: #999988; font-style: italic } /* Comment.Single */
|
||||||
|
.highlight .cs { color: #999999; font-weight: bold; font-style: italic } /* Comment.Special */
|
||||||
|
.highlight .gd { color: #000000; background-color: #ffdddd } /* Generic.Deleted */
|
||||||
|
.highlight .gd .x { color: #000000; background-color: #ffaaaa } /* Generic.Deleted.Specific */
|
||||||
|
.highlight .ge { font-style: italic } /* Generic.Emph */
|
||||||
|
.highlight .gr { color: #aa0000 } /* Generic.Error */
|
||||||
|
.highlight .gh { color: #999999 } /* Generic.Heading */
|
||||||
|
.highlight .gi { color: #000000; background-color: #ddffdd } /* Generic.Inserted */
|
||||||
|
.highlight .gi .x { color: #000000; background-color: #aaffaa } /* Generic.Inserted.Specific */
|
||||||
|
.highlight .go { color: #888888 } /* Generic.Output */
|
||||||
|
.highlight .gp { color: #555555 } /* Generic.Prompt */
|
||||||
|
.highlight .gs { font-weight: bold } /* Generic.Strong */
|
||||||
|
.highlight .gu { color: #aaaaaa } /* Generic.Subheading */
|
||||||
|
.highlight .gt { color: #aa0000 } /* Generic.Traceback */
|
||||||
|
.highlight .kc { font-weight: bold } /* Keyword.Constant */
|
||||||
|
.highlight .kd { font-weight: bold } /* Keyword.Declaration */
|
||||||
|
.highlight .kp { font-weight: bold } /* Keyword.Pseudo */
|
||||||
|
.highlight .kr { font-weight: bold } /* Keyword.Reserved */
|
||||||
|
.highlight .kt { color: #445588; font-weight: bold } /* Keyword.Type */
|
||||||
|
.highlight .m { color: #009999 } /* Literal.Number */
|
||||||
|
.highlight .s { color: #d14 } /* Literal.String */
|
||||||
|
.highlight .na { color: #008080 } /* Name.Attribute */
|
||||||
|
.highlight .nb { color: #0086B3 } /* Name.Builtin */
|
||||||
|
.highlight .nc { color: #445588; font-weight: bold } /* Name.Class */
|
||||||
|
.highlight .no { color: #008080 } /* Name.Constant */
|
||||||
|
.highlight .ni { color: #800080 } /* Name.Entity */
|
||||||
|
.highlight .ne { color: #990000; font-weight: bold } /* Name.Exception */
|
||||||
|
.highlight .nf { color: #990000; font-weight: bold } /* Name.Function */
|
||||||
|
.highlight .nn { color: #555555 } /* Name.Namespace */
|
||||||
|
.highlight .nt { color: #000080 } /* Name.Tag */
|
||||||
|
.highlight .nv { color: #008080 } /* Name.Variable */
|
||||||
|
.highlight .ow { font-weight: bold } /* Operator.Word */
|
||||||
|
.highlight .w { color: #bbbbbb } /* Text.Whitespace */
|
||||||
|
.highlight .mf { color: #009999 } /* Literal.Number.Float */
|
||||||
|
.highlight .mh { color: #009999 } /* Literal.Number.Hex */
|
||||||
|
.highlight .mi { color: #009999 } /* Literal.Number.Integer */
|
||||||
|
.highlight .mo { color: #009999 } /* Literal.Number.Oct */
|
||||||
|
.highlight .sb { color: #d14 } /* Literal.String.Backtick */
|
||||||
|
.highlight .sc { color: #d14 } /* Literal.String.Char */
|
||||||
|
.highlight .sd { color: #d14 } /* Literal.String.Doc */
|
||||||
|
.highlight .s2 { color: #d14 } /* Literal.String.Double */
|
||||||
|
.highlight .se { color: #d14 } /* Literal.String.Escape */
|
||||||
|
.highlight .sh { color: #d14 } /* Literal.String.Heredoc */
|
||||||
|
.highlight .si { color: #d14 } /* Literal.String.Interpol */
|
||||||
|
.highlight .sx { color: #d14 } /* Literal.String.Other */
|
||||||
|
.highlight .sr { color: #009926 } /* Literal.String.Regex */
|
||||||
|
.highlight .s1 { color: #d14 } /* Literal.String.Single */
|
||||||
|
.highlight .ss { color: #990073 } /* Literal.String.Symbol */
|
||||||
|
.highlight .bp { color: #999999 } /* Name.Builtin.Pseudo */
|
||||||
|
.highlight .vc { color: #008080 } /* Name.Variable.Class */
|
||||||
|
.highlight .vg { color: #008080 } /* Name.Variable.Global */
|
||||||
|
.highlight .vi { color: #008080 } /* Name.Variable.Instance */
|
||||||
|
.highlight .il { color: #009999 } /* Literal.Number.Integer.Long */
|
93
public/css/typography.scss
Normal file
93
public/css/typography.scss
Normal file
|
@ -0,0 +1,93 @@
|
||||||
|
/*- Typography -*/
|
||||||
|
//
|
||||||
|
// Based on the typographic scale: 12, 14, 16, 18, 21, 24, 36, 48, 60, 72.
|
||||||
|
//
|
||||||
|
|
||||||
|
body {
|
||||||
|
font-family: 'Source Sans Pro', sans-serif;
|
||||||
|
letter-spacing: 0.01em;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*- Typography for medium and small screen, based on 16px font-size -*/
|
||||||
|
|
||||||
|
p, ul, ol {
|
||||||
|
font-size: 1em; /* 16px */
|
||||||
|
line-height: 1.5em; /* 24px/16px */
|
||||||
|
margin-bottom: 1.5em; /* 24px/16px */
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
font-size: 2.25em; /* 36px/16px */
|
||||||
|
line-height: 1.3333em; /* 48px/36px */
|
||||||
|
padding: 0.33335em 0; /* 12px/36px * 2 (Use padding instead of margin to maintain proximity with paragraph) */
|
||||||
|
}
|
||||||
|
|
||||||
|
h2 {
|
||||||
|
font-size: 1.5em; /* 24px/16px */
|
||||||
|
line-height: 1em; /* 24px/24px */
|
||||||
|
padding: 1em 0 0 0; /* 12px/24px * 2, only top (Use padding instead of margin to maintain proximity with paragwithph) */
|
||||||
|
}
|
||||||
|
|
||||||
|
h3, h4, h5, h6 {
|
||||||
|
font-size: 1.125em; /* 18px/16px */
|
||||||
|
line-height: 1.3334em; /* 24px/18px */
|
||||||
|
padding: 0.66667em 0; /* 12px/18px * 2 (Use padding instead of margin to maintain proximity with paragraph) */
|
||||||
|
}
|
||||||
|
|
||||||
|
blockquote {
|
||||||
|
font-style: italic;
|
||||||
|
margin: 1.5em; /* 24px/18px */
|
||||||
|
-webkit-border-radius: 4px;
|
||||||
|
-moz-border-radius: 4px;
|
||||||
|
-ms-border-radius: 4px;
|
||||||
|
border-radius: 4px;
|
||||||
|
background-color: darken($background-color, 5%);
|
||||||
|
padding: 0 1.5em; /* 24px/18px */
|
||||||
|
p, ul, ol {
|
||||||
|
padding: 1.5em 0; /* 24px/18px */
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*- Typography for big screen, based on 18px font-size -*/
|
||||||
|
|
||||||
|
@media (min-width: 940px) { //Breakpoint set to 940px
|
||||||
|
|
||||||
|
p, ul, ol {
|
||||||
|
font-size: 1em; /* 18px */
|
||||||
|
line-height: 1.3334em; /* 24px/18px */
|
||||||
|
margin-bottom: 1.3334em; /* 24px/18px */
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
font-size: 2.6667em; /* 48px/18px */
|
||||||
|
line-height: 1em; /* 48px/48px */
|
||||||
|
padding: 0.25em 0; /* 12px/48px * 2 (Use padding instead of margin to maintain proximity with paragraph) */
|
||||||
|
}
|
||||||
|
|
||||||
|
h2 {
|
||||||
|
font-size: 2em; /* 36px/18px */
|
||||||
|
line-height: 1.3334em; /* 48px/36px */
|
||||||
|
padding: 0.66667em 0 0 0; /* 12px/36px * 2, pnly top (Use padding instead of margin to maintain proximity with paragraph) */
|
||||||
|
}
|
||||||
|
|
||||||
|
h3, h4, h5, h6 {
|
||||||
|
font-size: 1.3334em; /* 24px/18px */
|
||||||
|
line-height: 1em; /* 24px/24px */
|
||||||
|
padding: 0.5em 0; /* 12px/24px * 2 (Use padding instead of margin to maintain proximity with paragraph) */
|
||||||
|
}
|
||||||
|
|
||||||
|
blockquote {
|
||||||
|
font-style: italic;
|
||||||
|
margin: 1.3334em; /* 24px/18px */
|
||||||
|
-webkit-border-radius: 4px;
|
||||||
|
-moz-border-radius: 4px;
|
||||||
|
-ms-border-radius: 4px;
|
||||||
|
border-radius: 4px;
|
||||||
|
background-color: darken($background-color, 5%);
|
||||||
|
padding: 0 1.33334em; /* 24px/18px */
|
||||||
|
p, ul, ol {
|
||||||
|
padding: 1.33334em 0; /* 24px/18px */
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
0
public/images/.gitkeep
Normal file
0
public/images/.gitkeep
Normal file
103
public/index.php
Normal file
103
public/index.php
Normal file
|
@ -0,0 +1,103 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
use Slim\Http\Request;
|
||||||
|
use Slim\Http\Response;
|
||||||
|
|
||||||
|
// To help the built-in PHP dev server, check if the request was actually for
|
||||||
|
// something which should probably be served as a static file
|
||||||
|
if (PHP_SAPI === 'cli-server' && $_SERVER['SCRIPT_FILENAME'] !== __FILE__) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
require __DIR__ . DIRECTORY_SEPARATOR . '../vendor/autoload.php';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Step 2: Bootstrap database, ACL, Twig, FlashMessages, Logger
|
||||||
|
*/
|
||||||
|
$app = new Slim\App(['settings' => Config::$CONFIG['slim_settings']]);
|
||||||
|
|
||||||
|
// Dependencies/Container
|
||||||
|
$container = $app->getContainer();
|
||||||
|
|
||||||
|
// Environment
|
||||||
|
$env = EnvironmentHelper::getAppEnvironment();
|
||||||
|
$container['env'] = function() use ($env) {
|
||||||
|
return $env;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Config
|
||||||
|
$container['config'] = function() {
|
||||||
|
return Config::$CONFIG;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Database
|
||||||
|
$capsule = DatabaseHelper::bootORM();
|
||||||
|
$container['db'] = function () use ($capsule) {
|
||||||
|
return $capsule;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Translation
|
||||||
|
$translator = TranslationHelper::getAppTranslator();
|
||||||
|
$container['translator'] = function () use ($translator) {
|
||||||
|
return $translator;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Logger
|
||||||
|
$container['logger'] = function () {
|
||||||
|
$logger = LoggerHelper::getAppLogger();
|
||||||
|
return $logger;
|
||||||
|
};
|
||||||
|
|
||||||
|
// View
|
||||||
|
$container['flash'] = function () {
|
||||||
|
return new Slim\Flash\Messages;
|
||||||
|
};
|
||||||
|
$container['view'] = function ($container) use ($translator) {
|
||||||
|
$view = new \Slim\Views\Twig(Config::$CONFIG['twig_settings']['twig_dir'], [
|
||||||
|
'cache' => Config::$CONFIG['twig_settings']['twig_cache_dir']
|
||||||
|
]);
|
||||||
|
$view->addExtension(new \Slim\Views\TwigExtension(
|
||||||
|
$container['router'],
|
||||||
|
$container['request']->getUri()
|
||||||
|
));
|
||||||
|
$view->addExtension(new \Symfony\Bridge\Twig\Extension\TranslationExtension($translator));
|
||||||
|
$view->getEnvironment()->addFunction(new Twig_SimpleFunction('getenv', function($value) {
|
||||||
|
$res = getenv($value);
|
||||||
|
return $res;
|
||||||
|
}));
|
||||||
|
$view['flash'] = $container['flash'];
|
||||||
|
$view['config'] = $container['config'];
|
||||||
|
return $view;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Error handling
|
||||||
|
$container['notFoundHandler'] = function ($container) {
|
||||||
|
return function (Request $request, Response $response) use ($container) {
|
||||||
|
return $response->withRedirect('404');
|
||||||
|
};
|
||||||
|
};
|
||||||
|
$container['errorHandler'] = function ($container) {
|
||||||
|
return function (Request $request, Response $response, $exception) use ($container) {
|
||||||
|
$container['logger']->error($container['translator']->trans('log.internal.application.error'), [
|
||||||
|
'file' => $exception->getFile(),
|
||||||
|
'line' => $exception->getLine(),
|
||||||
|
'code' => $exception->getCode(),
|
||||||
|
'message' => $exception->getMessage(),
|
||||||
|
'previous' => $exception->getPrevious(),
|
||||||
|
'trace' => $exception->getTraceAsString(),
|
||||||
|
]);
|
||||||
|
|
||||||
|
return $response->withRedirect('500');
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Step 3: Define the Slim application routes
|
||||||
|
*/
|
||||||
|
require_once __DIR__ . DIRECTORY_SEPARATOR . '../config/Routes.php';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Step 4: Start a session (flash messages) and run the Slim application
|
||||||
|
*/
|
||||||
|
session_start();
|
||||||
|
$app->run();
|
0
public/js/.gitkeep
Normal file
0
public/js/.gitkeep
Normal file
6
public/js/jquery-1.10.2.min.js
vendored
Normal file
6
public/js/jquery-1.10.2.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
41
public/js/main.js
Normal file
41
public/js/main.js
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
var normal = document.getElementById("nav-menu");
|
||||||
|
var reverse = document.getElementById("nav-menu-left");
|
||||||
|
|
||||||
|
var icon = normal !== null ? normal : reverse;
|
||||||
|
|
||||||
|
// Toggle the "menu-open" % "menu-opn-left" classes
|
||||||
|
function toggle() {
|
||||||
|
var navRight = document.getElementById("nav");
|
||||||
|
var navLeft = document.getElementById("nav-left");
|
||||||
|
var nav = navRight !== null ? navRight : navLeft;
|
||||||
|
|
||||||
|
var button = document.getElementById("menu");
|
||||||
|
var site = document.getElementById("wrap");
|
||||||
|
|
||||||
|
if (nav.className == "menu-open" || nav.className == "menu-open-left") {
|
||||||
|
nav.className = "";
|
||||||
|
button.className = "";
|
||||||
|
site.className = "";
|
||||||
|
} else if (reverse !== null) {
|
||||||
|
nav.className += "menu-open-left";
|
||||||
|
button.className += "btn-close";
|
||||||
|
site.className += "fixed";
|
||||||
|
} else {
|
||||||
|
nav.className += "menu-open";
|
||||||
|
button.className += "btn-close";
|
||||||
|
site.className += "fixed";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensures backward compatibility with IE old versions
|
||||||
|
function menuClick() {
|
||||||
|
if (document.addEventListener && icon !== null) {
|
||||||
|
icon.addEventListener('click', toggle);
|
||||||
|
} else if (document.attachEvent && icon !== null) {
|
||||||
|
icon.attachEvent('onclick', toggle);
|
||||||
|
} else {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
menuClick();
|
32
src/Control/Actions/ForbiddenAction.php
Normal file
32
src/Control/Actions/ForbiddenAction.php
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
use Slim\Flash\Messages;
|
||||||
|
use Slim\Views\Twig;
|
||||||
|
use Psr\Log\LoggerInterface;
|
||||||
|
use Slim\Http\Request;
|
||||||
|
use Slim\Http\Response;
|
||||||
|
use Symfony\Component\Translation\Translator;
|
||||||
|
|
||||||
|
final class ForbiddenAction
|
||||||
|
{
|
||||||
|
private $view;
|
||||||
|
private $translator;
|
||||||
|
private $logger;
|
||||||
|
private $flash;
|
||||||
|
|
||||||
|
public function __construct(Twig $view, LoggerInterface $logger, Messages $flash, Translator $translator)
|
||||||
|
{
|
||||||
|
$this->view = $view;
|
||||||
|
$this->translator = $translator;
|
||||||
|
$this->logger = $logger;
|
||||||
|
$this->flash = $flash;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function __invoke(Request $request, Response $response, $args)
|
||||||
|
{
|
||||||
|
return $this->view->render($response, 'error.twig', [
|
||||||
|
'title' => $this->translator->trans('error.403.title'),
|
||||||
|
'content' => $this->translator->trans('error.403.content')
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
29
src/Control/Actions/IndexAction.php
Normal file
29
src/Control/Actions/IndexAction.php
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
use Slim\Flash\Messages;
|
||||||
|
use Slim\Views\Twig;
|
||||||
|
use Psr\Log\LoggerInterface;
|
||||||
|
use Slim\Http\Request;
|
||||||
|
use Slim\Http\Response;
|
||||||
|
use Symfony\Component\Translation\Translator;
|
||||||
|
|
||||||
|
final class IndexAction
|
||||||
|
{
|
||||||
|
private $view;
|
||||||
|
private $translator;
|
||||||
|
private $logger;
|
||||||
|
private $flash;
|
||||||
|
|
||||||
|
public function __construct(Twig $view, LoggerInterface $logger, Messages $flash, Translator $translator)
|
||||||
|
{
|
||||||
|
$this->view = $view;
|
||||||
|
$this->translator = $translator;
|
||||||
|
$this->logger = $logger;
|
||||||
|
$this->flash = $flash;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function __invoke(Request $request, Response $response, $args)
|
||||||
|
{
|
||||||
|
return $response->withRedirect('/signup');
|
||||||
|
}
|
||||||
|
}
|
32
src/Control/Actions/InternalApplicationErrorAction.php
Normal file
32
src/Control/Actions/InternalApplicationErrorAction.php
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
use Slim\Flash\Messages;
|
||||||
|
use Slim\Views\Twig;
|
||||||
|
use Psr\Log\LoggerInterface;
|
||||||
|
use Slim\Http\Request;
|
||||||
|
use Slim\Http\Response;
|
||||||
|
use Symfony\Component\Translation\Translator;
|
||||||
|
|
||||||
|
final class InternalApplicationError
|
||||||
|
{
|
||||||
|
private $view;
|
||||||
|
private $translator;
|
||||||
|
private $logger;
|
||||||
|
private $flash;
|
||||||
|
|
||||||
|
public function __construct(Twig $view, LoggerInterface $logger, Messages $flash, Translator $translator)
|
||||||
|
{
|
||||||
|
$this->view = $view;
|
||||||
|
$this->translator = $translator;
|
||||||
|
$this->logger = $logger;
|
||||||
|
$this->flash = $flash;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function __invoke(Request $request, Response $response, $args)
|
||||||
|
{
|
||||||
|
return $this->view->render($response, 'error.twig', [
|
||||||
|
'title' => $this->translator->trans('error.500.title'),
|
||||||
|
'content' => $this->translator->trans('error.500.content')
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
32
src/Control/Actions/NotAuthorizedAction.php
Normal file
32
src/Control/Actions/NotAuthorizedAction.php
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
use Slim\Flash\Messages;
|
||||||
|
use Slim\Views\Twig;
|
||||||
|
use Psr\Log\LoggerInterface;
|
||||||
|
use Slim\Http\Request;
|
||||||
|
use Slim\Http\Response;
|
||||||
|
use Symfony\Component\Translation\Translator;
|
||||||
|
|
||||||
|
final class NotAuthorizedAction
|
||||||
|
{
|
||||||
|
private $view;
|
||||||
|
private $translator;
|
||||||
|
private $logger;
|
||||||
|
private $flash;
|
||||||
|
|
||||||
|
public function __construct(Twig $view, LoggerInterface $logger, Messages $flash, Translator $translator)
|
||||||
|
{
|
||||||
|
$this->view = $view;
|
||||||
|
$this->translator = $translator;
|
||||||
|
$this->logger = $logger;
|
||||||
|
$this->flash = $flash;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function __invoke(Request $request, Response $response, $args)
|
||||||
|
{
|
||||||
|
return $this->view->render($response, 'error.twig', [
|
||||||
|
'title' => $this->translator->trans('error.401.title'),
|
||||||
|
'content' => $this->translator->trans('error.401.content')
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
32
src/Control/Actions/NotFoundAction.php
Normal file
32
src/Control/Actions/NotFoundAction.php
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
use Slim\Flash\Messages;
|
||||||
|
use Slim\Views\Twig;
|
||||||
|
use Psr\Log\LoggerInterface;
|
||||||
|
use Slim\Http\Request;
|
||||||
|
use Slim\Http\Response;
|
||||||
|
use Symfony\Component\Translation\Translator;
|
||||||
|
|
||||||
|
final class NotFoundAction
|
||||||
|
{
|
||||||
|
private $view;
|
||||||
|
private $translator;
|
||||||
|
private $logger;
|
||||||
|
private $flash;
|
||||||
|
|
||||||
|
public function __construct(Twig $view, LoggerInterface $logger, Messages $flash, Translator $translator)
|
||||||
|
{
|
||||||
|
$this->view = $view;
|
||||||
|
$this->translator = $translator;
|
||||||
|
$this->logger = $logger;
|
||||||
|
$this->flash = $flash;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function __invoke(Request $request, Response $response, $args)
|
||||||
|
{
|
||||||
|
return $this->view->render($response, 'error.twig', [
|
||||||
|
'title' => $this->translator->trans('error.404.title'),
|
||||||
|
'content' => $this->translator->trans('error.404.content')
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
123
src/Control/Actions/SignUpAction.php
Normal file
123
src/Control/Actions/SignUpAction.php
Normal file
|
@ -0,0 +1,123 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
use Curl\Curl;
|
||||||
|
use Slim\Flash\Messages;
|
||||||
|
use Slim\Views\Twig;
|
||||||
|
use Psr\Log\LoggerInterface;
|
||||||
|
use Slim\Http\Request;
|
||||||
|
use Slim\Http\Response;
|
||||||
|
use Symfony\Component\Translation\Translator;
|
||||||
|
|
||||||
|
final class SignUpAction
|
||||||
|
{
|
||||||
|
private $view;
|
||||||
|
private $translator;
|
||||||
|
private $logger;
|
||||||
|
private $flash;
|
||||||
|
|
||||||
|
public function __construct(Twig $view, LoggerInterface $logger, Messages $flash, Translator $translator)
|
||||||
|
{
|
||||||
|
$this->view = $view;
|
||||||
|
$this->translator = $translator;
|
||||||
|
$this->logger = $logger;
|
||||||
|
$this->flash = $flash;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function __invoke(Request $request, Response $response, $args)
|
||||||
|
{
|
||||||
|
$body = $request->getParsedBody();
|
||||||
|
|
||||||
|
if ($request->isPost()) {
|
||||||
|
|
||||||
|
// Form validation
|
||||||
|
$validator = new Validator();
|
||||||
|
$validator->filter_rules([
|
||||||
|
'username' => 'trim|sanitize_string',
|
||||||
|
'email' => 'trim|sanitize_email',
|
||||||
|
]);
|
||||||
|
$validator->validation_rules([
|
||||||
|
'username' => 'required|alpha_numeric|max_len,64|min_len,3',
|
||||||
|
'email' => 'required|valid_email|max_len,64|min_len,5',
|
||||||
|
'password' => 'required|max_len,255|min_len,8',
|
||||||
|
]);
|
||||||
|
if (!$validator->run($body)) {
|
||||||
|
$validator->addErrorsToFlashMessage($this->flash);
|
||||||
|
return $response->withRedirect('/signup');
|
||||||
|
}
|
||||||
|
|
||||||
|
$username = $body['username'];
|
||||||
|
$email = $body['email'];
|
||||||
|
$password = $body['password'];
|
||||||
|
|
||||||
|
// waiting queue
|
||||||
|
if ((UserAwaitingVerification::with([])->where('email', $email)->get()->count() > 0)) {
|
||||||
|
$this->flash->addMessage('error', $this->translator->trans('sign.up.flash.already_in_use_email', ['%email%' => $email]));
|
||||||
|
return $response->withRedirect('/signup');
|
||||||
|
}
|
||||||
|
if ((UserAwaitingVerification::with([])->where('username', $username)->get()->count() > 0)) {
|
||||||
|
$this->flash->addMessage('error', $this->translator->trans('sign.up.flash.already_in_use_username', ['%username%' => $username]));
|
||||||
|
return $response->withRedirect('/signup');
|
||||||
|
}
|
||||||
|
|
||||||
|
// xmpp accounts
|
||||||
|
$curl = new Curl();
|
||||||
|
$curl->setBasicAuthentication(getenv('xmpp_curl_auth_admin_username'), getenv('xmpp_curl_auth_admin_password'));
|
||||||
|
$curl->get(getenv('xmpp_curl_uri') . '/user/' . $username);
|
||||||
|
$curl->close();
|
||||||
|
|
||||||
|
if ($curl->http_status_code != 404) {
|
||||||
|
$this->flash->addMessage('error', $this->translator->trans('sign.up.flash.already_in_use_email_and_username', ['%email%' => $email, '%username%' => $username]));
|
||||||
|
return $response->withRedirect('/signup');
|
||||||
|
}
|
||||||
|
|
||||||
|
$userAwaiting = new UserAwaitingVerification();
|
||||||
|
$userAwaiting->username = $username;
|
||||||
|
$userAwaiting->email = $email;
|
||||||
|
$userAwaiting->password = $password;
|
||||||
|
|
||||||
|
$generatedCode = NULL;
|
||||||
|
$found = false;
|
||||||
|
|
||||||
|
while (!$found) {
|
||||||
|
$generatedCode = hash('crc32', time() . $email . rand());
|
||||||
|
if (UserAwaitingVerification::with([])->where('verification_code', '=', $generatedCode)->get()->count() === 0) $found = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
$userAwaiting->verification_code = $generatedCode;
|
||||||
|
$userAwaiting->save();
|
||||||
|
|
||||||
|
$mailer = new PHPMailer();
|
||||||
|
$mailer->CharSet = 'UTF-8';
|
||||||
|
$mailer->ContentType = 'text/plain';
|
||||||
|
$mailer->isSMTP();
|
||||||
|
$mailer->SMTPSecure = getenv('mail_secure');
|
||||||
|
$mailer->SMTPAuth = getenv('mail_auth');
|
||||||
|
|
||||||
|
$mailer->Host = getenv('mail_host');
|
||||||
|
$mailer->Port = getenv('mail_port');
|
||||||
|
$mailer->Username = getenv('mail_username');
|
||||||
|
$mailer->Password = getenv('mail_password');
|
||||||
|
$mailer->From = getenv('mail_from');
|
||||||
|
$mailer->FromName = getenv('mail_from_name');
|
||||||
|
|
||||||
|
$mailer->addAddress($userAwaiting->email);
|
||||||
|
|
||||||
|
$verificationLink = $request->getUri()->getScheme() . '://' . $request->getUri()->getHost() . (!empty($p = $request->getUri()->getPort()) ? ':' .$p : '') .'/verification/' . $userAwaiting->verification_code;
|
||||||
|
|
||||||
|
$mailer->Subject = $this->translator->trans('verification.mail.subject', ['%server%' => getenv('site_xmpp_server_displayname')]);
|
||||||
|
$mailer->Body = $this->translator->trans('verification.mail.body', ['%username%' => $userAwaiting->username, '%verificationLink%' => $verificationLink, '%server%' => getenv('site_xmpp_server_displayname')]);
|
||||||
|
$mailer->send();
|
||||||
|
|
||||||
|
$this->flash->addMessage('success', $this->translator->trans('sign.up.flash.success'));
|
||||||
|
$this->logger->info($this->translator->trans('log.signed.up', ['%username%' => $userAwaiting->username]));
|
||||||
|
return $response->withRedirect('/signup');
|
||||||
|
}
|
||||||
|
|
||||||
|
// render GET
|
||||||
|
$this->view->render($response, 'signup.twig', [
|
||||||
|
'title' => $this->translator->trans('sign.up.title'),
|
||||||
|
]);
|
||||||
|
|
||||||
|
return $response;
|
||||||
|
}
|
||||||
|
}
|
83
src/Control/Actions/VerificationAction.php
Normal file
83
src/Control/Actions/VerificationAction.php
Normal file
|
@ -0,0 +1,83 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
use Curl\Curl;
|
||||||
|
use Psr\Log\LoggerInterface;
|
||||||
|
use Slim\Flash\Messages;
|
||||||
|
use Slim\Http\Request;
|
||||||
|
use Slim\Http\Response;
|
||||||
|
use Slim\Views\Twig;
|
||||||
|
use Symfony\Component\Translation\Translator;
|
||||||
|
|
||||||
|
final class VerificationAction
|
||||||
|
{
|
||||||
|
private $view;
|
||||||
|
private $translator;
|
||||||
|
private $logger;
|
||||||
|
private $flash;
|
||||||
|
|
||||||
|
public function __construct(Twig $view, LoggerInterface $logger, Messages $flash, Translator $translator)
|
||||||
|
{
|
||||||
|
$this->view = $view;
|
||||||
|
$this->translator = $translator;
|
||||||
|
$this->logger = $logger;
|
||||||
|
$this->flash = $flash;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function __invoke(Request $request, Response $response, $args)
|
||||||
|
{
|
||||||
|
$verificationCode = $args['verificationCode'];
|
||||||
|
|
||||||
|
$usersAwaiting = UserAwaitingVerification::with([])->where('verification_code', $verificationCode)->get();
|
||||||
|
|
||||||
|
if (empty($usersAwaiting) || $usersAwaiting->count() == 0) {
|
||||||
|
$this->flash->addMessage('error', $this->translator->trans('verification.code.invalid', ['%verificationCode%' => $verificationCode]));
|
||||||
|
$this->logger->info($this->translator->trans('log.verification.invalid', ['%verificationCode%' => $verificationCode]));
|
||||||
|
return $response->withRedirect('/signup');
|
||||||
|
}
|
||||||
|
|
||||||
|
$userAwaiting = $usersAwaiting->pop();
|
||||||
|
|
||||||
|
$curl = new Curl();
|
||||||
|
$curl->setBasicAuthentication(getenv('xmpp_curl_auth_admin_username'), getenv('xmpp_curl_auth_admin_password'));
|
||||||
|
$curl->setHeader('Content-Type', 'application/json');
|
||||||
|
$curl->post(getenv('xmpp_curl_uri') . '/user/' . $userAwaiting->username, json_encode(['password' => $userAwaiting->password]));
|
||||||
|
$curl->close();
|
||||||
|
|
||||||
|
if ($curl->http_status_code == 409) {
|
||||||
|
$this->flash->addMessage('error', $this->translator->trans('verification.flash.already_in_use_username', ['%username%' => $userAwaiting->username]));
|
||||||
|
|
||||||
|
$userAwaiting->delete();
|
||||||
|
return $response->withRedirect('/signup');
|
||||||
|
} else if ($curl->http_status_code == 201) {
|
||||||
|
$this->flash->addMessage('success', $this->translator->trans('verification.flash.success', ['%username%' => $userAwaiting->username]));
|
||||||
|
$this->logger->info($this->translator->trans('log.verification.sucess', ['%username%' => $userAwaiting->username]));
|
||||||
|
|
||||||
|
$mailer = new PHPMailer();
|
||||||
|
$mailer->CharSet = 'UTF-8';
|
||||||
|
$mailer->ContentType = 'text/plain';
|
||||||
|
$mailer->isSMTP();
|
||||||
|
$mailer->SMTPSecure = getenv('mail_secure');
|
||||||
|
$mailer->SMTPAuth = getenv('mail_auth');
|
||||||
|
|
||||||
|
$mailer->Host = getenv('mail_host');
|
||||||
|
$mailer->Port = getenv('mail_port');
|
||||||
|
$mailer->Username = getenv('mail_username');
|
||||||
|
$mailer->Password = getenv('mail_password');
|
||||||
|
$mailer->From = getenv('mail_from');
|
||||||
|
$mailer->FromName = getenv('mail_from_name');
|
||||||
|
|
||||||
|
$mailer->addAddress($userAwaiting->email);
|
||||||
|
|
||||||
|
$mailer->Subject = $this->translator->trans('verification.mail.success.subject', ['%server%' => getenv('site_xmpp_server_displayname')]);
|
||||||
|
$mailer->Body = $this->translator->trans('verification.mail.success.body', ['%username%' => $userAwaiting->username, '%server%' => getenv('site_xmpp_server_displayname'), '%password%' => $userAwaiting->password]);
|
||||||
|
$mailer->send();
|
||||||
|
|
||||||
|
$userAwaiting->delete();
|
||||||
|
return $response->withRedirect('/signup');
|
||||||
|
} else {
|
||||||
|
$this->flash->addMessage('error', $this->translator->trans('verification.flash.unknown_error', ['%username%' => $userAwaiting->username]));
|
||||||
|
$this->logger->warning($this->translator->trans('verification.flash.unknown_error'), ['code' => $curl->http_status_code, 'message' => $curl->http_error_message]);
|
||||||
|
return $response->withRedirect('/signup');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
7
src/Model/UserAwaitingVerification.php
Normal file
7
src/Model/UserAwaitingVerification.php
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
<?php
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
|
||||||
|
class UserAwaitingVerification extends Model
|
||||||
|
{
|
||||||
|
public $table = 'users_awaiting_verification';
|
||||||
|
}
|
32
src/Util/DatabaseHelper.php
Normal file
32
src/Util/DatabaseHelper.php
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Capsule\Manager;
|
||||||
|
|
||||||
|
class DatabaseHelper
|
||||||
|
{
|
||||||
|
public static function bootORM()
|
||||||
|
{
|
||||||
|
$config = Config::$CONFIG['db_settings'];
|
||||||
|
$path = $config['database'];
|
||||||
|
|
||||||
|
// create database file of non-existent
|
||||||
|
if (!file_exists($path)) {
|
||||||
|
fopen($path, 'w') or die('Unable to write database file.');
|
||||||
|
}
|
||||||
|
|
||||||
|
$capsule = new Manager();
|
||||||
|
|
||||||
|
$capsule->addConnection([
|
||||||
|
'driver' => $config['driver'],
|
||||||
|
'database' => $path,
|
||||||
|
'charset' => 'utf8',
|
||||||
|
'collation' => 'utf8_unicode_ci',
|
||||||
|
'prefix' => '',
|
||||||
|
], 'default');
|
||||||
|
|
||||||
|
$capsule->setAsGlobal();
|
||||||
|
$capsule->bootEloquent();
|
||||||
|
|
||||||
|
return $capsule;
|
||||||
|
}
|
||||||
|
}
|
18
src/Util/DateHelper.php
Normal file
18
src/Util/DateHelper.php
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
use Carbon\Carbon;
|
||||||
|
|
||||||
|
class DateHelper
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Returns param if already carbon, else formats with Y-m-d H:i:s
|
||||||
|
* @param $date
|
||||||
|
* @return Carbon|false
|
||||||
|
*/
|
||||||
|
public static function convertToCarbon($date)
|
||||||
|
{
|
||||||
|
if ($date instanceof Carbon) return $date;
|
||||||
|
elseif (is_string($date)) return Carbon::createFromFormat('Y-m-d H:i:s', $date);
|
||||||
|
else return false;
|
||||||
|
}
|
||||||
|
}
|
25
src/Util/EnvironmentHelper.php
Normal file
25
src/Util/EnvironmentHelper.php
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
use Dotenv\Dotenv;
|
||||||
|
|
||||||
|
class EnvironmentHelper
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @return array
|
||||||
|
* @throws Exception
|
||||||
|
*/
|
||||||
|
public static function getAppEnvironment()
|
||||||
|
{
|
||||||
|
$envPath = __DIR__ . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . 'config';
|
||||||
|
$envFile = 'env';
|
||||||
|
|
||||||
|
$exists = is_file($envPath . DIRECTORY_SEPARATOR . $envFile);
|
||||||
|
if (!$exists) {
|
||||||
|
die('Configure your environment in ' . $envPath . '.');
|
||||||
|
} else {
|
||||||
|
$env = new Dotenv($envPath, $envFile);
|
||||||
|
$res = $env->load();
|
||||||
|
return $res;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
14
src/Util/LoggerHelper.php
Normal file
14
src/Util/LoggerHelper.php
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
class LoggerHelper
|
||||||
|
{
|
||||||
|
public static function getAppLogger()
|
||||||
|
{
|
||||||
|
$config = Config::$CONFIG['logger_settings'];
|
||||||
|
$logger = new Monolog\Logger($config['name']);
|
||||||
|
$logger->pushProcessor(new Monolog\Processor\UidProcessor());
|
||||||
|
$logger->pushHandler(new Monolog\Handler\StreamHandler($config['path'], Config::$CONFIG['logger_settings']['level']));
|
||||||
|
$logger->pushHandler(new \Monolog\Handler\ErrorLogHandler(NULL, Config::$CONFIG['logger_settings']['level']));
|
||||||
|
return $logger;
|
||||||
|
}
|
||||||
|
}
|
21
src/Util/TranslationHelper.php
Normal file
21
src/Util/TranslationHelper.php
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
use Symfony\Component\Translation\Loader\YamlFileLoader;
|
||||||
|
use Symfony\Component\Translation\MessageSelector;
|
||||||
|
use Symfony\Component\Translation\Translator;
|
||||||
|
|
||||||
|
class TranslationHelper
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @return Translator
|
||||||
|
*/
|
||||||
|
public static function getAppTranslator()
|
||||||
|
{
|
||||||
|
$translator = new Translator('en_EN', new MessageSelector());
|
||||||
|
$translator->addLoader('yaml', new YamlFileLoader());
|
||||||
|
$translator->addResource('yaml', __DIR__ . DIRECTORY_SEPARATOR . '../../data/locale/messages.en.yml', 'en_EN');
|
||||||
|
$translator->setFallbackLocales(['en']);
|
||||||
|
|
||||||
|
return $translator;
|
||||||
|
}
|
||||||
|
}
|
287
src/Util/Validator.php
Normal file
287
src/Util/Validator.php
Normal file
|
@ -0,0 +1,287 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class Validator
|
||||||
|
* @see Gump for use
|
||||||
|
*/
|
||||||
|
class Validator extends GUMP
|
||||||
|
{
|
||||||
|
private $translator;
|
||||||
|
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
$this->translator = TranslationHelper::getAppTranslator();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Validates if $field content is equal to $param
|
||||||
|
* @param $field
|
||||||
|
* @param $input
|
||||||
|
* @param $param
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
protected function validate_equals($field, $input, $param)
|
||||||
|
{
|
||||||
|
$err = [
|
||||||
|
'field' => $field,
|
||||||
|
'value' => $input[$field],
|
||||||
|
'rule' => __FUNCTION__,
|
||||||
|
'param' => $param,
|
||||||
|
];
|
||||||
|
|
||||||
|
if (!isset($input[$field]) || empty($input[$field]) || empty($param) || !isset($param)) {
|
||||||
|
return $err;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($input[$field] != $param || $input[$field] !== $param) {
|
||||||
|
return $err;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validates if array has min size, defaults to size = 1
|
||||||
|
* @param $field
|
||||||
|
* @param $input
|
||||||
|
* @param null $param
|
||||||
|
* @return array|bool
|
||||||
|
*/
|
||||||
|
protected function validate_set_min_len($field, $input, $param = NULL)
|
||||||
|
{
|
||||||
|
|
||||||
|
$err = [
|
||||||
|
'field' => $field,
|
||||||
|
'value' => $input[$field],
|
||||||
|
'rule' => __FUNCTION__,
|
||||||
|
'param' => $param,
|
||||||
|
];
|
||||||
|
|
||||||
|
if (!is_array($input[$field])) {
|
||||||
|
return $err;
|
||||||
|
}
|
||||||
|
|
||||||
|
// default value
|
||||||
|
if (empty($param)) $param = 1;
|
||||||
|
|
||||||
|
if (count($input[$field]) < $param) return $err;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns error array without HTML
|
||||||
|
* @param null $convert_to_string
|
||||||
|
* @return array|null
|
||||||
|
*/
|
||||||
|
public function get_errors_array($convert_to_string = NULL)
|
||||||
|
{
|
||||||
|
if (empty($this->errors)) {
|
||||||
|
return ($convert_to_string) ? NULL : [];
|
||||||
|
}
|
||||||
|
|
||||||
|
$resp = [];
|
||||||
|
|
||||||
|
foreach ($this->errors as $e) {
|
||||||
|
|
||||||
|
$field = ucwords(str_replace(['_', '-'], chr(32), $e['field']));
|
||||||
|
$param = $e['param'];
|
||||||
|
|
||||||
|
// Let's fetch explicit field names if they exist
|
||||||
|
if (array_key_exists($e['field'], self::$fields)) {
|
||||||
|
$field = self::$fields[$e['field']];
|
||||||
|
}
|
||||||
|
|
||||||
|
switch ($e['rule']) {
|
||||||
|
case 'mismatch' :
|
||||||
|
$resp[$field] = $this->translator->trans($e['rule'], ['%field%' => $field, '%param' => $param]);
|
||||||
|
break;
|
||||||
|
case 'validate_required':
|
||||||
|
$resp[$field] = $this->translator->trans($e['rule'], ['%field%' => $field, '%param' => $param]);
|
||||||
|
break;
|
||||||
|
case 'validate_valid_json_string':
|
||||||
|
$resp[$field] = $this->translator->trans($e['rule'], ['%field%' => $field, '%param' => $param]);
|
||||||
|
break;
|
||||||
|
case 'validate_valid_email':
|
||||||
|
$resp[$field] = $this->translator->trans($e['rule'], ['%field%' => $field, '%param' => $param]);
|
||||||
|
break;
|
||||||
|
case 'validate_max_len':
|
||||||
|
$resp[$field] = $this->translator->trans($e['rule'], ['%field%' => $field, '%param' => $param]);
|
||||||
|
break;
|
||||||
|
case 'validate_min_len':
|
||||||
|
$resp[$field] = $this->translator->trans($e['rule'], ['%field%' => $field, '%param' => $param]);
|
||||||
|
break;
|
||||||
|
case 'validate_exact_len':
|
||||||
|
$resp[$field] = $this->translator->trans($e['rule'], ['%field%' => $field, '%param' => $param]);
|
||||||
|
break;
|
||||||
|
case 'validate_alpha':
|
||||||
|
$resp[$field] = $this->translator->trans($e['rule'], ['%field%' => $field, '%param' => $param]);
|
||||||
|
break;
|
||||||
|
case 'validate_alpha_numeric':
|
||||||
|
$resp[$field] = $this->translator->trans($e['rule'], ['%field%' => $field, '%param' => $param]);
|
||||||
|
break;
|
||||||
|
case 'validate_alpha_dash':
|
||||||
|
$resp[$field] = $this->translator->trans($e['rule'], ['%field%' => $field, '%param' => $param]);
|
||||||
|
break;
|
||||||
|
case 'validate_numeric':
|
||||||
|
$resp[$field] = $this->translator->trans($e['rule'], ['%field%' => $field, '%param' => $param]);
|
||||||
|
break;
|
||||||
|
case 'validate_integer':
|
||||||
|
$resp[$field] = $this->translator->trans($e['rule'], ['%field%' => $field, '%param' => $param]);
|
||||||
|
break;
|
||||||
|
case 'validate_boolean':
|
||||||
|
$resp[$field] = $this->translator->trans($e['rule'], ['%field%' => $field, '%param' => $param]);
|
||||||
|
break;
|
||||||
|
case 'validate_float':
|
||||||
|
$resp[$field] = $this->translator->trans($e['rule'], ['%field%' => $field, '%param' => $param]);
|
||||||
|
break;
|
||||||
|
case 'validate_valid_url':
|
||||||
|
$resp[$field] = $this->translator->trans($e['rule'], ['%field%' => $field, '%param' => $param]);
|
||||||
|
break;
|
||||||
|
case 'validate_url_exists':
|
||||||
|
$resp[$field] = $this->translator->trans($e['rule'], ['%field%' => $field, '%param' => $param]);
|
||||||
|
break;
|
||||||
|
case 'validate_valid_ip':
|
||||||
|
$resp[$field] = $this->translator->trans($e['rule'], ['%field%' => $field, '%param' => $param]);
|
||||||
|
break;
|
||||||
|
case 'validate_valid_cc':
|
||||||
|
$resp[$field] = $this->translator->trans($e['rule'], ['%field%' => $field, '%param' => $param]);
|
||||||
|
break;
|
||||||
|
case 'validate_valid_name':
|
||||||
|
$resp[$field] = $this->translator->trans($e['rule'], ['%field%' => $field, '%param' => $param]);
|
||||||
|
break;
|
||||||
|
case 'validate_contains':
|
||||||
|
$resp[$field] = $this->translator->trans($e['rule'], ['%field%' => $field, '%param' => implode(', ', $param)]);
|
||||||
|
break;
|
||||||
|
case 'validate_street_address':
|
||||||
|
$resp[$field] = $this->translator->trans($e['rule'], ['%field%' => $field, '%param' => $param]);
|
||||||
|
break;
|
||||||
|
case 'validate_date':
|
||||||
|
$resp[$field] = $this->translator->trans($e['rule'], ['%field%' => $field, '%param' => $param]);
|
||||||
|
break;
|
||||||
|
case 'validate_min_numeric':
|
||||||
|
$resp[$field] = $this->translator->trans($e['rule'], ['%field%' => $field, '%param' => $param]);
|
||||||
|
break;
|
||||||
|
case 'validate_max_numeric':
|
||||||
|
$resp[$field] = $this->translator->trans($e['rule'], ['%field%' => $field, '%param' => $param]);
|
||||||
|
break;
|
||||||
|
case 'validate_equals':
|
||||||
|
$resp[$field] = $this->translator->trans($e['rule'], ['%field%' => $field, '%param' => $param]);
|
||||||
|
break;
|
||||||
|
case 'validate_set_min_len':
|
||||||
|
$resp[$field] = $this->translator->trans($e['rule'], ['%field%' => $field, '%param' => $param]);
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
$resp[$field] = $this->translator->trans($e['rule'], ['%field%' => $field, '%param' => $param]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $resp;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Perform data validation against the provided ruleset
|
||||||
|
*
|
||||||
|
* Arrays as FIELDS are added here as a custom feature
|
||||||
|
*
|
||||||
|
* @access public
|
||||||
|
* @param mixed $input
|
||||||
|
* @param array $ruleset
|
||||||
|
* @return mixed
|
||||||
|
* @throws \Exception
|
||||||
|
*/
|
||||||
|
public function validate(array $input, array $ruleset)
|
||||||
|
{
|
||||||
|
$this->errors = [];
|
||||||
|
|
||||||
|
foreach ($ruleset as $field => $rules) {
|
||||||
|
#if(!array_key_exists($field, $input))
|
||||||
|
#{
|
||||||
|
# continue;
|
||||||
|
#}
|
||||||
|
|
||||||
|
$rules = explode('|', $rules);
|
||||||
|
|
||||||
|
if (in_array("required", $rules) || (isset($input[$field]) && (is_array($input[$field]) || trim($input[$field]) != ''))) {
|
||||||
|
|
||||||
|
foreach ($rules as $rule) {
|
||||||
|
$method = NULL;
|
||||||
|
$param = NULL;
|
||||||
|
|
||||||
|
if (strstr($rule, ',') !== false) // has params
|
||||||
|
{
|
||||||
|
$rule = explode(',', $rule);
|
||||||
|
$method = 'validate_' . $rule[0];
|
||||||
|
$param = $rule[1];
|
||||||
|
$rule = $rule[0];
|
||||||
|
} else {
|
||||||
|
$method = 'validate_' . $rule;
|
||||||
|
}
|
||||||
|
|
||||||
|
// array required
|
||||||
|
if ($rule === "required" && !isset($input[$field])) {
|
||||||
|
$result = $this->$method($field, $input, $param);
|
||||||
|
$this->errors[] = $result;
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (is_callable([$this, $method])) {
|
||||||
|
$result = $this->$method($field, $input, $param);
|
||||||
|
|
||||||
|
if (is_array($result)) // Validation Failed
|
||||||
|
{
|
||||||
|
$this->errors[] = $result;
|
||||||
|
|
||||||
|
return $this->errors;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (isset(self::$validation_methods[$rule])) {
|
||||||
|
if (isset($input[$field])) {
|
||||||
|
$result = call_user_func(self::$validation_methods[$rule], $field, $input, $param);
|
||||||
|
|
||||||
|
$result = $this->$method($field, $input, $param);
|
||||||
|
|
||||||
|
if (is_array($result)) // Validation Failed
|
||||||
|
{
|
||||||
|
$this->errors[] = $result;
|
||||||
|
|
||||||
|
return $this->errors;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw new \Exception("Validator method '$method' does not exist.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (count($this->errors) > 0) ? $this->errors : true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function filter_upper($value, $param = NULL)
|
||||||
|
{
|
||||||
|
return strtoupper($value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function filter_lower($value, $param = NULL)
|
||||||
|
{
|
||||||
|
return strtolower($value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts all error array into a single string
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function addErrorsToFlashMessage($flash)
|
||||||
|
{
|
||||||
|
$errors = $this->get_errors_array(true);
|
||||||
|
|
||||||
|
if (!empty($errors)) {
|
||||||
|
foreach ($errors as $error) {
|
||||||
|
$flash->addMessage('error', $error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
108
src/View/base.twig
Normal file
108
src/View/base.twig
Normal file
|
@ -0,0 +1,108 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>{{ title }}</title>
|
||||||
|
<!-- Meta -->
|
||||||
|
<meta charset="UTF-8"/>
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1">
|
||||||
|
|
||||||
|
<!-- CSS & fonts -->
|
||||||
|
<link rel="stylesheet" href="{{ base_url() }}/css/main.css">
|
||||||
|
<link href='https://fonts.googleapis.com/css?family=Source+Sans+Pro:300,400,700,900,400italic%7CSignika:700,300,400,600' rel='stylesheet' type='text/css'>
|
||||||
|
|
||||||
|
<script src="https://use.fontawesome.com/82b8dead7b.js"></script>
|
||||||
|
|
||||||
|
<!-- jQuery -->
|
||||||
|
<script src="{{ base_url() }}/js/jquery-1.10.2.min.js"></script>
|
||||||
|
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<div id="wrap">
|
||||||
|
|
||||||
|
<!-- Navigation -->
|
||||||
|
<nav id="nav-left">
|
||||||
|
<div id="nav-list">
|
||||||
|
<a href="/">{{ getenv('site_navbar_index_displayname') }}</a>
|
||||||
|
|
||||||
|
{% if getenv('site_navbar_backlink_enabled') %}
|
||||||
|
<br />
|
||||||
|
<br />
|
||||||
|
<a href="{{ getenv('site_navbar_backlink_uri') }}">{{ getenv('site_navbar_backlink_displayname') }}</a>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Nav footer -->
|
||||||
|
<footer>
|
||||||
|
</footer>
|
||||||
|
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
|
||||||
|
<!-- Icon menu -->
|
||||||
|
<a id="nav-menu-left">
|
||||||
|
<div id="menu"></div>
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<!-- Header -->
|
||||||
|
<header id="header">
|
||||||
|
|
||||||
|
<a href="/">
|
||||||
|
<h1>{{ getenv('site_title') }}</h1>
|
||||||
|
</a>
|
||||||
|
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<!-- Main content -->
|
||||||
|
<div id="container">
|
||||||
|
|
||||||
|
<main>
|
||||||
|
<!-- flash messages -->
|
||||||
|
{% if flash is not empty %}
|
||||||
|
<div id="flashMessage">
|
||||||
|
{% if flash.getMessage('info').0 %}
|
||||||
|
<div class="alert alert-info">
|
||||||
|
{{ flash.getMessage('info').0 }}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if flash.getMessage('success').0 %}
|
||||||
|
<div class="alert alert-success">
|
||||||
|
{{ flash.getMessage('success').0 }}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if flash.getMessage('error') %}
|
||||||
|
{% for error in flash.getMessage('error') %}
|
||||||
|
<div class="alert alert-danger">
|
||||||
|
{{ error }}
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<!-- content -->
|
||||||
|
{% block content %}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
</main>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Footer -->
|
||||||
|
<!--<footer><span></span></footer>-->
|
||||||
|
|
||||||
|
<!-- Script -->
|
||||||
|
<script src="{{ base_url() }}/js/main.js"></script>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
$(".alert-danger" ).delay(20000).fadeOut(300);
|
||||||
|
$(".alert-success" ).delay(5000).fadeOut(300);
|
||||||
|
$(".alert-info" ).delay(5000).fadeOut(300);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
8
src/View/error.twig
Normal file
8
src/View/error.twig
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
{% extends 'base.twig' %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<h1 class="header">{{ title }}</h1>
|
||||||
|
|
||||||
|
{{ content }}
|
||||||
|
|
||||||
|
{% endblock %}
|
17
src/View/signup.twig
Normal file
17
src/View/signup.twig
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
{% extends 'base.twig' %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<form class="" role="form" name="register" id="register" method="post">
|
||||||
|
<h1>{{ title }}</h1>
|
||||||
|
<label for="username">{% trans %}sign.up.form.username{% endtrans %}</label><br/>
|
||||||
|
<input type="text" id="username" name="username" class="" placeholder="{% trans %}sign.up.form.username.placeholder{% endtrans %}" value="" autofocus required> @{{ getenv('site_xmpp_server_displayname') }}
|
||||||
|
<br/><br/>
|
||||||
|
<label for="email">{% trans %}sign.up.form.email{% endtrans %}</label><br/>
|
||||||
|
<input type="text" id="email" name="email" class="" placeholder="{% trans %}sign.up.form.email.placeholder{% endtrans %}" value="" autofocus required>
|
||||||
|
<br/><br/>
|
||||||
|
<label for="username">{% trans %}sign.up.form.password{% endtrans %}</label><br/>
|
||||||
|
<input type="password" id="password" name="password" class="form-control" placeholder="{% trans %}sign.up.form.password.placeholder{% endtrans %}" required>
|
||||||
|
<br/><br/>
|
||||||
|
<input class="" type="submit" name="signup" value="{% trans %}sign.up.form.button{% endtrans %}"/>
|
||||||
|
</form>
|
||||||
|
{% endblock %}
|
Reference in a new issue