mirror of
https://github.com/getgrav/grav-plugin-admin.git
synced 2025-11-15 17:56:07 +01:00
Merge branch 'feature/admin_registration' into develop
This commit is contained in:
194
admin.php
194
admin.php
@@ -1,13 +1,16 @@
|
||||
<?php
|
||||
namespace Grav\Plugin;
|
||||
|
||||
use Grav\Common\File\CompiledYamlFile;
|
||||
use Grav\Common\GPM\GPM;
|
||||
use Grav\Common\Grav;
|
||||
use Grav\Common\Inflector;
|
||||
use Grav\Common\Language\Language;
|
||||
use Grav\Common\Page\Page;
|
||||
use Grav\Common\Page\Pages;
|
||||
use Grav\Common\Plugin;
|
||||
use Grav\Common\Uri;
|
||||
use Grav\Common\User\User;
|
||||
use RocketTheme\Toolbox\File\File;
|
||||
use RocketTheme\Toolbox\Event\Event;
|
||||
use RocketTheme\Toolbox\Session\Session;
|
||||
@@ -66,11 +69,14 @@ class AdminPlugin extends Plugin
|
||||
{
|
||||
if (!Grav::instance()['config']->get('plugins.admin-pro.enabled')) {
|
||||
return [
|
||||
'onPluginsInitialized' => [['login', 100000], ['onPluginsInitialized', 1000]],
|
||||
'onShutdown' => ['onShutdown', 1000]
|
||||
'onPluginsInitialized' => [['setup', 100000], ['onPluginsInitialized', 1000]],
|
||||
'onShutdown' => ['onShutdown', 1000],
|
||||
'onFormProcessed' => ['onFormProcessed', 0]
|
||||
];
|
||||
} else {
|
||||
return [];
|
||||
return [
|
||||
'onFormProcessed' => ['onFormProcessed', 0]
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -78,7 +84,7 @@ class AdminPlugin extends Plugin
|
||||
* If the admin path matches, initialize the Login plugin configuration and set the admin
|
||||
* as active.
|
||||
*/
|
||||
public function login()
|
||||
public function setup()
|
||||
{
|
||||
$route = $this->config->get('plugins.admin.route');
|
||||
if (!$route) {
|
||||
@@ -87,14 +93,156 @@ class AdminPlugin extends Plugin
|
||||
|
||||
$this->base = '/' . trim($route, '/');
|
||||
$this->uri = $this->grav['uri'];
|
||||
$register_check = CACHE_DIR . 'register-check-' . $this->grav['cache']->getKey();
|
||||
|
||||
// See if we have performed register check recently
|
||||
if (!file_exists($register_check)) {
|
||||
|
||||
// check for existence of a user account
|
||||
$account_dir = $file_path = $this->grav['locator']->findResource('account://');
|
||||
$user_check = (array) glob($account_dir . '/*.yaml');
|
||||
|
||||
// If no users found, go to register
|
||||
if (!count($user_check) > 0) {
|
||||
if (!$this->isAdminPath()) {
|
||||
$this->grav->redirect($this->base);
|
||||
}
|
||||
|
||||
$this->template = 'register';
|
||||
|
||||
} else {
|
||||
touch($register_check);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Only activate admin if we're inside the admin path.
|
||||
if ($this->uri->route() == $this->base ||
|
||||
substr($this->uri->route(), 0, strlen($this->base) + 1) == $this->base . '/') {
|
||||
if ($this->isAdminPath()) {
|
||||
$this->active = true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate a value. Currently validates
|
||||
*
|
||||
* - 'user' for username format and username availability.
|
||||
* - 'password1' for password format
|
||||
* - 'password2' for equality to password1
|
||||
*
|
||||
* @param object $form The form
|
||||
* @param string $type The field type
|
||||
* @param string $value The field value
|
||||
* @param string $extra Any extra value required
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
protected function validate($type, $value, $extra = '')
|
||||
{
|
||||
switch ($type) {
|
||||
case 'username_format':
|
||||
if (!preg_match('/^[a-z0-9_-]{3,16}$/', $value)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
break;
|
||||
|
||||
case 'password1':
|
||||
if (!preg_match('/(?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{8,}/', $value)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
break;
|
||||
|
||||
case 'password2':
|
||||
if (strcmp($value, $extra)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Process the admin registration form.
|
||||
*
|
||||
* @param Event $event
|
||||
*/
|
||||
public function onFormProcessed(Event $event)
|
||||
{
|
||||
$form = $event['form'];
|
||||
$action = $event['action'];
|
||||
|
||||
switch ($action) {
|
||||
|
||||
case 'register_admin_user':
|
||||
|
||||
if (!$this->config->get('plugins.login.enabled')) {
|
||||
throw new \RuntimeException($this->grav['language']->translate('PLUGIN_LOGIN.PLUGIN_LOGIN_DISABLED'));
|
||||
}
|
||||
|
||||
$data = [];
|
||||
$username = $form->value('username');
|
||||
|
||||
if ($form->value('password1') != $form->value('password2')) {
|
||||
$this->grav->fireEvent('onFormValidationError',
|
||||
new Event([
|
||||
'form' => $form,
|
||||
'message' => $this->grav['language']->translate('PLUGIN_LOGIN.PASSWORDS_DO_NOT_MATCH')
|
||||
]));
|
||||
$event->stopPropagation();
|
||||
return;
|
||||
}
|
||||
|
||||
$data['password'] = $form->value('password1');
|
||||
|
||||
$fields = [
|
||||
'email',
|
||||
'fullname',
|
||||
'title'
|
||||
];
|
||||
|
||||
foreach($fields as $field) {
|
||||
// Process value of field if set in the page process.register_user
|
||||
if (!isset($data[$field]) && $form->value($field)) {
|
||||
$data[$field] = $form->value($field);
|
||||
}
|
||||
}
|
||||
|
||||
unset($data['password1']);
|
||||
unset($data['password2']);
|
||||
|
||||
// Don't store the username: that is part of the filename
|
||||
unset($data['username']);
|
||||
|
||||
$inflector = new Inflector();
|
||||
|
||||
$data['fullname'] = isset($data['fullname']) ? $data['fullname'] : $inflector->titleize($username);
|
||||
$data['title'] = isset($data['title']) ? $data['title'] : 'Administrator';
|
||||
$data['state'] = 'enabled';
|
||||
$data['access'] = ['admin' => ['login' => true, 'super' => true], 'site' => ['login' => true]];
|
||||
|
||||
// Create user object and save it
|
||||
$user = new User($data);
|
||||
$file = CompiledYamlFile::instance($this->grav['locator']->findResource('user://accounts/' . $username . YAML_EXT, true, true));
|
||||
$user->file($file);
|
||||
$user->save();
|
||||
$user = User::load($username);
|
||||
|
||||
//Login user
|
||||
$this->grav['session']->user = $user;
|
||||
unset($this->grav['user']);
|
||||
$this->grav['user'] = $user;
|
||||
$user->authenticated = $user->authorize('site.login');
|
||||
|
||||
$messages = $this->grav['messages'];
|
||||
$messages->add($this->grav['language']->translate('PLUGIN_ADMIN.LOGIN_LOGGED_IN'), 'info');
|
||||
$this->grav->redirect($this->base);
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* If the admin plugin is set as active, initialize the admin
|
||||
*/
|
||||
@@ -153,13 +301,6 @@ class AdminPlugin extends Plugin
|
||||
$this->session->expert = false;
|
||||
}
|
||||
|
||||
// check for existence of a user account
|
||||
$account_dir = $file_path = $this->grav['locator']->findResource('account://');
|
||||
$user_check = (array) glob($account_dir . '/*.yaml');
|
||||
if (!count($user_check) > 0) {
|
||||
$this->admin->setMessage($this->admin->translate('PLUGIN_ADMIN.NO_USER_ACCOUNTS'), 'info');
|
||||
}
|
||||
|
||||
/** @var Pages $pages */
|
||||
$pages = $this->grav['pages'];
|
||||
|
||||
@@ -192,6 +333,9 @@ class AdminPlugin extends Plugin
|
||||
|
||||
$self = $this;
|
||||
|
||||
// make sure page is not frozen!
|
||||
unset($this->grav['page']);
|
||||
|
||||
// Replace page service with admin.
|
||||
$this->grav['page'] = function () use ($self) {
|
||||
$page = new Page;
|
||||
@@ -374,6 +518,9 @@ class AdminPlugin extends Plugin
|
||||
'onTask.GPM' => ['onTaskGPM', 0]
|
||||
]);
|
||||
|
||||
// Initialize admin class.
|
||||
require_once __DIR__ . '/classes/admin.php';
|
||||
|
||||
// Check for required plugins
|
||||
if (!$this->grav['config']->get('plugins.login.enabled') ||
|
||||
!$this->grav['config']->get('plugins.form.enabled') ||
|
||||
@@ -397,19 +544,20 @@ class AdminPlugin extends Plugin
|
||||
$this->grav['session']->admin_lang = $language->getLanguage();
|
||||
}
|
||||
|
||||
|
||||
// Decide admin template and route.
|
||||
$path = trim(substr($this->uri->route(), strlen($this->base)), '/');
|
||||
$this->template = 'dashboard';
|
||||
|
||||
if ($path) {
|
||||
if (empty($this->template)) {
|
||||
$this->template = 'dashboard';
|
||||
}
|
||||
|
||||
// Can't access path directly...
|
||||
if ($path && $path != 'register') {
|
||||
$array = explode('/', $path, 2);
|
||||
$this->template = array_shift($array);
|
||||
$this->route = array_shift($array);
|
||||
}
|
||||
|
||||
// Initialize admin class.
|
||||
require_once __DIR__ . '/classes/admin.php';
|
||||
$this->admin = new Admin($this->grav, $this->base, $this->template, $this->route);
|
||||
|
||||
// And store the class into DI container.
|
||||
@@ -466,4 +614,14 @@ class AdminPlugin extends Plugin
|
||||
require_once(__DIR__.'/twig/AdminTwigExtension.php');
|
||||
$this->grav['twig']->twig->addExtension(new AdminTwigExtension());
|
||||
}
|
||||
|
||||
public function isAdminPath()
|
||||
{
|
||||
if ($this->uri->route() == $this->base ||
|
||||
substr($this->uri->route(), 0, strlen($this->base) + 1) == $this->base . '/') {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -7,6 +7,8 @@ en:
|
||||
LOGIN_BTN_FORGOT: "Forgot"
|
||||
LOGIN_BTN_RESET: "Reset Password"
|
||||
LOGIN_BTN_SEND_INSTRUCTIONS: "Send Reset Instructions"
|
||||
LOGIN_BTN_CLEAR: "Clear Form"
|
||||
LOGIN_BTN_CREATE_USER: "Create User"
|
||||
LOGIN_LOGGED_IN: "You have been successfully logged in"
|
||||
LOGIN_FAILED: "Login failed"
|
||||
LOGGED_OUT: "You have been logged out"
|
||||
@@ -171,6 +173,7 @@ en:
|
||||
USERNAME: "Username"
|
||||
EMAIL: "Email"
|
||||
PASSWORD: "Password"
|
||||
PASSWORD_CONFIRM: "Confirm Password"
|
||||
TITLE: "Title"
|
||||
LANGUAGE: "Language"
|
||||
ACCOUNT: "Account"
|
||||
|
||||
57
pages/admin/register.md
Normal file
57
pages/admin/register.md
Normal file
@@ -0,0 +1,57 @@
|
||||
---
|
||||
form:
|
||||
fields:
|
||||
- name: spacer
|
||||
title: Required
|
||||
type: spacer
|
||||
|
||||
- name: username
|
||||
type: text
|
||||
placeholder: PLUGIN_ADMIN.USERNAME
|
||||
validate:
|
||||
required: true
|
||||
message: PLUGIN_LOGIN.USERNAME_NOT_VALID
|
||||
pattern: '^[a-z0-9_-]{3,16}$'
|
||||
|
||||
- name: email
|
||||
type: text
|
||||
placeholder: PLUGIN_ADMIN.EMAIL
|
||||
validate:
|
||||
type: email
|
||||
message: PLUGIN_ADMIN.EMAIL_VALIDATION_MESSAGE
|
||||
required: true
|
||||
|
||||
- name: password1
|
||||
type: password
|
||||
placeholder: PLUGIN_ADMIN.PASSWORD
|
||||
validate:
|
||||
required: true
|
||||
message: PLUGIN_ADMIN.PASSWORD_VALIDATION_MESSAGE
|
||||
pattern: '(?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{8,}'
|
||||
|
||||
- name: password2
|
||||
type: password
|
||||
placeholder: PLUGIN_ADMIN.PASSWORD_CONFIRM
|
||||
validate:
|
||||
required: true
|
||||
message: PLUGIN_ADMIN.PASSWORD_VALIDATION_MESSAGE
|
||||
pattern: '(?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{8,}'
|
||||
|
||||
- name: spacer
|
||||
title: Optional
|
||||
type: spacer
|
||||
|
||||
- name: fullname
|
||||
type: text
|
||||
placeholder: PLUGIN_ADMIN.FULL_NAME
|
||||
|
||||
- name: title
|
||||
type: text
|
||||
placeholder: PLUGIN_ADMIN.TITLE
|
||||
|
||||
process:
|
||||
register_admin_user: true
|
||||
|
||||
---
|
||||
|
||||
The Admin plugin has been installed, but no **admin accounts** could be found. Please create an admin account to ensure your Grav install is secure...
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -4,6 +4,30 @@
|
||||
max-width: 24rem;
|
||||
margin: 0 auto;
|
||||
|
||||
&.wide {
|
||||
max-width: 50rem;
|
||||
|
||||
h1 {
|
||||
height: 100px;
|
||||
}
|
||||
|
||||
form {
|
||||
> .padding {
|
||||
padding: 3rem 2rem 6rem 2rem;
|
||||
> div {
|
||||
width: 50%;
|
||||
display: inline-block;
|
||||
margin-right: -2px;
|
||||
}
|
||||
|
||||
.form-field {
|
||||
padding: 0 1rem;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
.form-field {
|
||||
padding-left: 0;
|
||||
margin-bottom: 0;
|
||||
@@ -17,6 +41,25 @@
|
||||
padding-right: 0;
|
||||
}
|
||||
|
||||
.wrapper-spacer {
|
||||
width: 100% !important;
|
||||
display: block !important;
|
||||
padding: 0 1rem;
|
||||
h3 {
|
||||
padding-left: 1rem;
|
||||
color: rgba(white, 0.4);
|
||||
border-bottom: 3px solid rgba(white, 0.1);
|
||||
}
|
||||
}
|
||||
|
||||
.instructions {
|
||||
display: block;
|
||||
padding: 2rem 4rem 0;
|
||||
margin: 0;
|
||||
font-size: 1.3rem;
|
||||
color: rgba(white,0.8);
|
||||
}
|
||||
|
||||
h1 {
|
||||
background: $darker-accent-bg url(../images/logo.png) 50% 50% no-repeat;
|
||||
font-size: 0;
|
||||
@@ -26,7 +69,12 @@
|
||||
}
|
||||
|
||||
form {
|
||||
padding: 2rem 3rem;
|
||||
position: relative;
|
||||
|
||||
.padding {
|
||||
padding: 3rem 3rem 6rem 3rem;
|
||||
}
|
||||
|
||||
input {
|
||||
margin-bottom: 2rem;
|
||||
background: $page-bg;
|
||||
@@ -43,8 +91,13 @@
|
||||
}
|
||||
|
||||
.form-actions {
|
||||
display: block !important;
|
||||
width: 100% !important;
|
||||
text-align: center;
|
||||
margin: 0 -3rem -3rem -3rem;
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
padding: 1.5rem 3rem;
|
||||
|
||||
button:first-child {
|
||||
|
||||
@@ -1,26 +1,26 @@
|
||||
{% embed 'partials/login.html.twig' with {title:'Grav Login'} %}
|
||||
{% embed 'partials/login.html.twig' with {title:'Grav Admin Login'} %}
|
||||
|
||||
{% block form %}
|
||||
{% for field in page.header.form.fields %}
|
||||
{% if field.type %}
|
||||
<div>
|
||||
{% include ["forms/fields/#{field.type}/#{field.type}.html.twig", 'forms/fields/text/text.html.twig'] %}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% if field.type %}
|
||||
<div>
|
||||
{% include ["forms/fields/#{field.type}/#{field.type}.html.twig", 'forms/fields/text/text.html.twig'] %}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
|
||||
<div class="form-actions secondary-accent">
|
||||
{% if notAuthorized %}
|
||||
<a class="button secondary" onclick="window.history.back()"><i class="fa fa-reply"></i> {{ 'PLUGIN_ADMIN.BACK'|tu }}</a>
|
||||
<div class="form-actions secondary-accent">
|
||||
{% if notAuthorized %}
|
||||
<a class="button secondary" onclick="window.history.back()"><i class="fa fa-reply"></i> {{ 'PLUGIN_ADMIN.BACK'|tu }}</a>
|
||||
{% else %}
|
||||
{% if not authenticated %}
|
||||
<a class="button secondary" href="{{ base_url_relative }}/forgot"><i class="fa fa-exclamation-circle"></i> {{ 'PLUGIN_ADMIN.LOGIN_BTN_FORGOT'|tu }}</a>
|
||||
<button type="submit" class="button primary" name="task" value="login"><i class="fa fa-sign-in"></i> {{ 'PLUGIN_ADMIN.LOGIN_BTN'|tu }}</button>
|
||||
{% else %}
|
||||
{% if not authenticated %}
|
||||
<a class="button secondary" href="{{ base_url_relative }}/forgot"><i class="fa fa-exclamation-circle"></i> {{ 'PLUGIN_ADMIN.LOGIN_BTN_FORGOT'|tu }}</a>
|
||||
<button type="submit" class="button primary" name="task" value="login"><i class="fa fa-sign-in"></i> {{ 'PLUGIN_ADMIN.LOGIN_BTN'|tu }}</button>
|
||||
{% else %}
|
||||
<button type="submit" class="button primary" name="task" value="logout"><i class="fa fa-sign-in"></i> {{ 'PLUGIN_ADMIN.LOGOUT'|tu }}</button>
|
||||
{% endif %}
|
||||
<button type="submit" class="button primary" name="task" value="logout"><i class="fa fa-sign-in"></i> {{ 'PLUGIN_ADMIN.LOGOUT'|tu }}</button>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
|
||||
|
||||
@@ -1,17 +1,21 @@
|
||||
{% extends 'partials/base.html.twig' %}
|
||||
|
||||
{% block page %}
|
||||
<section id="admin-login" class="default-glow-shadow">
|
||||
<section id="admin-login" class="default-glow-shadow {{ classes }}">
|
||||
<h1>
|
||||
{{ title }}
|
||||
</h1>
|
||||
|
||||
{% include 'partials/messages.html.twig' %}
|
||||
|
||||
{% block instructions %}{% endblock %}
|
||||
|
||||
<form method="post" action="{{ base_url_relative }}">
|
||||
<div class="padding">
|
||||
{% block form %}{% endblock %}
|
||||
|
||||
{{ nonce_field('admin-form', 'admin-nonce') }}
|
||||
</div>
|
||||
</form>
|
||||
</section>
|
||||
{% endblock %}
|
||||
@@ -1,3 +1,7 @@
|
||||
{% for message in admin.messages %}
|
||||
<div class="{{ message.scope|e }} alert">{{ message.message }}</div>
|
||||
{% endfor %}
|
||||
|
||||
{% if form.message %}
|
||||
<div class="error alert">{{ form.message }}</div>
|
||||
{% endif %}
|
||||
21
themes/grav/templates/partials/register.html.twig
Normal file
21
themes/grav/templates/partials/register.html.twig
Normal file
@@ -0,0 +1,21 @@
|
||||
{% extends 'partials/base.html.twig' %}
|
||||
|
||||
{% block page %}
|
||||
<section id="admin-login" class="default-glow-shadow {{ classes }}">
|
||||
<h1>
|
||||
{{ title }}
|
||||
</h1>
|
||||
|
||||
{% include 'partials/messages.html.twig' %}
|
||||
|
||||
{% block instructions %}{% endblock %}
|
||||
|
||||
<form method="post" action="{{ base_url_relative }}">
|
||||
<div class="padding">
|
||||
{% block form %}{% endblock %}
|
||||
|
||||
{{ nonce_field('form', 'form-nonce') }}
|
||||
</div>
|
||||
</form>
|
||||
</section>
|
||||
{% endblock %}
|
||||
28
themes/grav/templates/register.html.twig
Normal file
28
themes/grav/templates/register.html.twig
Normal file
@@ -0,0 +1,28 @@
|
||||
{% embed 'partials/register.html.twig' with {title:'Grav Register Admin User', classes:'wide'} %}
|
||||
|
||||
{% block instructions %}
|
||||
<div class="instructions">
|
||||
{{ page.content }}
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block form %}
|
||||
{% for field in page.header.form.fields %}
|
||||
{% if field.type %}
|
||||
{% set value = form.value(field.name) %}
|
||||
<div class="wrapper-{{ field.name }}">
|
||||
{% include ["forms/fields/#{field.type}/#{field.type}.html.twig", 'forms/fields/text/text.html.twig'] %}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
|
||||
<div class="form-actions secondary-accent">
|
||||
|
||||
<button type="reset" class="button secondary"><i class="fa fa-exclamation-circle"></i> {{ 'PLUGIN_ADMIN.LOGIN_BTN_CLEAR'|tu }}</button>
|
||||
<button type="submit" class="button primary"><i class="fa fa-sign-in"></i> {{ 'PLUGIN_ADMIN.LOGIN_BTN_CREATE_USER'|tu }}</button>
|
||||
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
|
||||
{% endembed %}
|
||||
Reference in New Issue
Block a user