Scrollreveal+cat+datenews

This commit is contained in:
2019-01-07 11:43:39 +01:00
parent e4cac3d01e
commit 762a1ad170
3158 changed files with 140699 additions and 73915 deletions
+1 -1
View File
@@ -6,7 +6,7 @@ themes/grav/.sass-cache
themes/grav/js/admin.js
themes/grav/js/vendor.js
themes/grav/js/*.map
/.idea
.idea
tests/_output/*
tests/_support/_generated/*
+63
View File
@@ -1,3 +1,66 @@
# v1.8.15
## 12/14/2018
1. [](#improved)
* Fire `onAdminSave()` event during `AdminController::taskSaveAs()` [#1544](https://github.com/getgrav/grav-plugin-admin/issues/1544)
1. [](#bugfix)
* Clean user post to ensure dynamically added form fields are not saved
# v1.8.14
## 11/12/2018
1. [](#bugfix)
* Fixed Grav core update potentially spinning forever because of an error which happens after a successful upgrade
* Saving in expert mode can cause `undefined index: header` error [#1537](https://github.com/getgrav/grav-plugin-admin/issues/1537)
# v1.8.13
## 11/05/2018
1. [](#new)
* Added new `|nested()` Twig filter to access array objects with dot notation syntax
1. [](#bugfix)
* Fixed issue with complex lists structure and nested dot-notation [admin#2236](https://github.com/getgrav/grav/issues/2236)
# v1.8.12
## 10/24/2018
1. [](#improved)
* Updated various lang strings
* Removed duplicate lang strings
1. [](#bugfix)
* Fix XSS checking when empty content [#1533](https://github.com/getgrav/grav-plugin-admin/issues/1533)
* Fix DirectInstall not working [#1535](https://github.com/getgrav/grav-plugin-admin/issues/1535)
# v1.8.11
## 10/08/2018
1. [](#improved)
* Change usage of basename where possible [#1480](https://github.com/getgrav/grav-plugin-admin/pull/1480)
* Improved filename validation (requires Grav 1.5.3)
* Updated various lang codes
1. [](#bugfix)
* File Uploads: Do not trust mimetype sent by the browser
* Fixed file extension detection
* Fix for HTML entities in page slug [#1524](https://github.com/getgrav/grav-plugin-admin/issues/1524)
* Fix for port in backup download links [#1521](https://github.com/getgrav/grav-plugin-admin/issues/1521)
# v1.8.10
## 10/01/2018
1. [](#new)
* IMPORTANT: Non `admin.super` users are now subject to XSS validation in Page content. Configurable via Configuration / Security
* New XSS content warnings and integration into page save
* Added new event `onAdminPage()` which allows plugins to customize `Page` object in `$event['page']`
1. [](#improved)
* Use `Url:post()` to get the `$_POST` variable (allows common security checks/filtering for the POST data)
* Requires Grav 1.5.2
1. [](#bugfix)
* Fixed redirect to correct URL after failed login
* Fixed issue in `filepicker` where missing images would cause a loop to try to load them
* Twig 2 compatibility fixes for macros
* Updated `composer.json` to better match Grav 1.5
* Remove `package-lock.json` as it was referencing an insecure JS package
# v1.8.9
## 08/23/2018
+15 -2
View File
@@ -360,7 +360,10 @@ class AdminPlugin extends Plugin
}
// Make local copy of POST.
$post = !empty($_POST) ? $_POST : [];
$post = $this->grav['uri']->post();
// Initialize Page Types
Pages::types();
// Handle tasks.
$this->admin->task = $task = !empty($post['task']) ? $post['task'] : $this->uri->param('task');
@@ -384,7 +387,17 @@ class AdminPlugin extends Plugin
$page = new Page;
$page->expires(0);
// First look in the pages provided by the Admin plugin itself
if ($this->grav['user']->authorize('admin.login')) {
$event = new Event(['page' => $page]);
$event = $this->grav->fireEvent('onAdminPage', $event);
$page = $event['page'];
if ($page->slug()) {
return $page;
}
}
// Look in the pages provided by the Admin plugin itself
if (file_exists(__DIR__ . "/pages/admin/{$self->template}.md")) {
$page->init(new \SplFileInfo(__DIR__ . "/pages/admin/{$self->template}.md"));
$page->slug(basename($self->template));
+2 -2
View File
@@ -1,5 +1,5 @@
name: Admin Panel
version: 1.8.9
version: 1.8.15
description: Adds an advanced administration panel to manage your site
icon: empire
author:
@@ -13,7 +13,7 @@ docs: https://github.com/getgrav/grav-plugin-admin/blob/develop/README.md
license: MIT
dependencies:
- { name: grav, version: '>=1.5.1' }
- { name: grav, version: '>=1.5.3' }
- { name: form, version: '>=2.14.0' }
- { name: login, version: '>=2.7.0' }
- { name: email, version: '>=2.7.0' }
@@ -18,6 +18,9 @@ form:
title: PLUGIN_ADMIN.CONTENT
fields:
xss_check:
type: xss
frontmatter:
classes: frontmatter
type: editor
@@ -31,6 +31,7 @@ class AdminTwigExtension extends \Twig_Extension
new \Twig_SimpleFilter('toYaml', [$this, 'toYamlFilter']),
new \Twig_SimpleFilter('fromYaml', [$this, 'fromYamlFilter']),
new \Twig_SimpleFilter('adminNicetime', [$this, 'adminNicetimeFilter']),
new \Twig_SimpleFilter('nested', [$this, 'nestedFilter']),
];
}
@@ -42,6 +43,23 @@ class AdminTwigExtension extends \Twig_Extension
];
}
public function nestedFilter($current, $name)
{
$path = explode('.', trim($name, '.'));
foreach ($path as $field) {
if (is_object($current) && isset($current->{$field})) {
$current = $current->{$field};
} elseif (is_array($current) && isset($current[$field])) {
$current = $current[$field];
} else {
return null;
}
}
return $current;
}
public function cloneFunc($obj)
{
return clone $obj;
+34 -11
View File
@@ -170,11 +170,11 @@ class Admin
/** @var \DirectoryIterator $directory */
foreach (new \DirectoryIterator($path) as $file) {
if ($file->isDir() || $file->isDot() || Utils::startsWith($file->getBasename(), '.')) {
if ($file->isDir() || $file->isDot() || Utils::startsWith($file->getFilename(), '.')) {
continue;
}
$lang = basename($file->getBasename(), '.yaml');
$lang = $file->getBasename('.yaml');
$languages[$lang] = LanguageCodes::getNativeName($lang);
@@ -202,7 +202,7 @@ class Admin
if ($file->isDir() || !preg_match('/^[^.].*.yaml$/', $file->getFilename())) {
continue;
}
$configurations[] = basename($file->getBasename(), '.yaml');
$configurations[] = $file->getBasename('.yaml');
}
return $configurations;
@@ -365,7 +365,7 @@ class Admin
$userKey = isset($credentials['username']) ? (string)$credentials['username'] : '';
$ipKey = Uri::ip();
$redirect = $this->base . $this->route;
$redirect = isset($post['redirect']) ? $post['redirect'] : $this->base . $this->route;
// Check if the current IP has been used in failed login attempts.
$attempts = count($rateLimiter->getAttempts($ipKey, 'ip'));
@@ -595,7 +595,8 @@ class Admin
}
if (!$post) {
$post = isset($_POST['data']) ? $_POST['data'] : [];
$post = $this->grav['uri']->post();
$post = isset($post['data']) ? $post['data'] : [];
}
// Check to see if a data type is plugin-provided, before looking into core ones
@@ -637,12 +638,12 @@ class Admin
$data[$type] = $obj;
} elseif (preg_match('|users/|', $type)) {
$obj = User::load(preg_replace('|users/|', '', $type));
$obj->merge($post);
$obj->merge($this->cleanUserPost($post));
$data[$type] = $obj;
} elseif (preg_match('|user/|', $type)) {
$obj = User::load(preg_replace('|user/|', '', $type));
$obj->merge($post);
$obj->merge($this->cleanUserPost($post));
$data[$type] = $obj;
} elseif (preg_match('|config/|', $type)) {
@@ -669,10 +670,10 @@ class Admin
$obj->file = $file;
$obj->page = $this->grav['pages']->get(dirname($obj->path));
$filename = pathinfo($obj->title)['filename'];
$filename = str_replace(['@3x', '@2x'], '', $filename);
if (isset(pathinfo($obj->title)['extension'])) {
$filename .= '.' . pathinfo($obj->title)['extension'];
$fileInfo = pathinfo($obj->title);
$filename = str_replace(['@3x', '@2x'], '', $fileInfo['filename']);
if (isset($fileInfo['extension'])) {
$filename .= '.' . $fileInfo['extension'];
}
if ($obj->page && isset($obj->page->media()[$filename])) {
@@ -687,6 +688,25 @@ class Admin
return $data[$type];
}
/**
* Clean user form post and remove extra stuff that may be passed along
*
* @param $post
* @return array
*/
protected function cleanUserPost($post)
{
// Clean fields for all users
unset($post['hashed_password']);
// Clean field for users who shouldn't be able to modify these fields
if (!$this->authorize(['admin.user', 'admin.super'])) {
unset($post['access']);
}
return $post;
}
protected function hasErrorMessage()
{
$msgs = $this->grav['messages']->all();
@@ -1418,6 +1438,9 @@ class Admin
$path = "/{$path}";
}
// Fix for entities in path causing looping...
$path = urldecode($path);
$page = $path ? $pages->dispatch($path, true) : $pages->root();
if (!$page) {
@@ -7,6 +7,7 @@ use Grav\Common\Filesystem\Folder;
use Grav\Common\Grav;
use Grav\Common\Media\Interfaces\MediaInterface;
use Grav\Common\Page\Media;
use Grav\Common\Page\Pages;
use Grav\Common\Utils;
use Grav\Common\Plugin;
use Grav\Common\Theme;
@@ -201,6 +202,29 @@ class AdminBaseController
$this->redirectCode = $code;
}
/**
* Sends JSON response and terminates the call.
*
* @param array $response
* @param int $code
* @return bool
*/
protected function sendJsonResponse(array $response, $code = 200)
{
// Make sure nothing extra gets written to the response.
while (ob_get_level()) {
ob_end_clean();
}
// JSON response.
http_response_code($code);
header('Content-Type: application/json');
header('Cache-Control: no-cache, no-store, must-revalidate');
echo json_encode($response);
exit();
}
/**
* Handles ajax upload for files.
* Stores in a flash object the temporary file and deals with potential file errors.
@@ -227,10 +251,10 @@ class AdminBaseController
$upload = $this->normalizeFiles($_FILES['data'], $settings->name);
$filename = trim($upload->file->name);
$filename = $upload->file->name;
// Handle bad filenames.
if (strtr($filename, "\t\n\r\0\x0b", '_____') !== $filename || rtrim($filename, '. ') !== $filename || preg_match('|\.php|', $filename)) {
if (!Utils::checkFilename($filename)) {
$this->admin->json_response = [
'status' => 'error',
'message' => sprintf($this->admin->translate('PLUGIN_ADMIN.FILEUPLOAD_UNABLE_TO_UPLOAD', null),
@@ -287,6 +311,9 @@ class AdminBaseController
$accepted = false;
$errors = [];
// Do not trust mimetype sent by the browser
$mime = Utils::getMimeByFilename($upload->file->name);
foreach ((array)$settings->accept as $type) {
// Force acceptance of any file when star notation
if ($type === '*') {
@@ -295,15 +322,24 @@ class AdminBaseController
}
$isMime = strstr($type, '/');
$find = str_replace('*', '.*', $type);
$find = str_replace(['.', '*'], ['\.', '.*'], $type);
$match = preg_match('#' . $find . '$#', $isMime ? $upload->file->type : $upload->file->name);
if (!$match) {
$message = $isMime ? 'The MIME type "' . $upload->file->type . '"' : 'The File Extension';
$errors[] = $message . ' for the file "' . $upload->file->name . '" is not an accepted.';
$accepted |= false;
if ($isMime) {
$match = preg_match('#' . $find . '$#', $mime);
if (!$match) {
$errors[] = 'The MIME type "' . $mime . '" for the file "' . $upload->file->name . '" is not an accepted.';
} else {
$accepted = true;
break;
}
} else {
$accepted |= true;
$match = preg_match('#' . $find . '$#', $upload->file->name);
if (!$match) {
$errors[] = 'The File Extension for the file "' . $upload->file->name . '" is not an accepted.';
} else {
$accepted = true;
break;
}
}
}
@@ -368,7 +404,7 @@ class AdminBaseController
// Generate random name if required
if ($settings->random_name) { // TODO: document
$extension = pathinfo($upload->file->name)['extension'];
$extension = pathinfo($upload->file->name, PATHINFO_EXTENSION);
$upload->file->name = Utils::generateRandomString(15) . '.' . $extension;
}
@@ -875,7 +911,29 @@ class AdminBaseController
$type = $uri->param('type');
$field = $uri->param('field');
$this->taskRemoveMedia();
// Get Blueprint
$settings = (object) $this->admin->blueprints($blueprint)->schema()->getProperty($field);
// Get destination
if ($this->grav['locator']->isStream($settings->destination)) {
$destination = $this->grav['locator']->findResource($settings->destination, false, true);
} else {
$destination = Folder::getRelativePath(rtrim($settings->destination, '/'));
$destination = $this->admin->getPagePathFromToken($destination);
}
// Not in path
if (!Utils::startsWith($path, $destination)) {
$this->admin->json_response = [
'status' => 'error',
'message' => 'Path not valid for this data type'
];
return false;
}
// Only remove files from correct destination...
$this->taskRemoveMedia($destination . '/' . basename($path));
if ($type === 'pages') {
$page = $this->admin->page(true, $proute);
@@ -892,9 +950,11 @@ class AdminBaseController
$page->save();
} else {
$blueprint_prefix = $type === 'config' ? '' : $type . '.';
$blueprint_name = str_replace(['config/', '/blueprints'], '', $blueprint);
$blueprint_field = $blueprint_prefix . $blueprint_name . '.' . $field;
$files = $this->grav['config']->get($blueprint_field);
if ($files) {
@@ -935,15 +995,17 @@ class AdminBaseController
*
* @return bool True if the action was performed
*/
public function taskRemoveMedia()
public function taskRemoveMedia($filename = null)
{
if (!$this->canEditMedia()) {
return false;
}
$filename = base64_decode($this->grav['uri']->param('route'));
if (!$filename) {
$filename = base64_decode($this->route);
if (is_null($filename)) {
$filename = base64_decode($this->grav['uri']->param('route'));
if (!$filename) {
$filename = base64_decode($this->route);
}
}
$file = File::instance($filename);
+86 -37
View File
@@ -14,6 +14,7 @@ use Grav\Common\Page\Medium\Medium;
use Grav\Common\Page\Page;
use Grav\Common\Page\Pages;
use Grav\Common\Page\Collection;
use Grav\Common\Security;
use Grav\Common\User\User;
use Grav\Common\Utils;
use Grav\Common\Backup\ZipBackup;
@@ -465,7 +466,7 @@ class AdminController extends AdminBaseController
/**
* Handles updating Grav
*
* @return bool True if the action was performed
* @return bool False if user has no permissions.
*/
public function taskUpdategrav()
{
@@ -478,14 +479,14 @@ class AdminController extends AdminBaseController
$result = Gpm::selfupgrade();
if ($result) {
$this->admin->json_response = [
$json_response = [
'status' => 'success',
'type' => 'updategrav',
'version' => $version,
'message' => $this->admin->translate('PLUGIN_ADMIN.GRAV_WAS_SUCCESSFULLY_UPDATED_TO') . ' ' . $version
];
} else {
$this->admin->json_response = [
$json_response = [
'status' => 'error',
'type' => 'updategrav',
'version' => GRAV_VERSION,
@@ -493,7 +494,7 @@ class AdminController extends AdminBaseController
];
}
return true;
return $this->sendJsonResponse($json_response);
}
/**
@@ -611,6 +612,8 @@ class AdminController extends AdminBaseController
$reorder = true;
$data = (array)$this->data;
$this->grav['twig']->twig_vars['current_form_data'] = $data;
// Special handler for user data.
if ($this->view === 'user') {
if (!$this->grav['user']->exists()) {
@@ -645,13 +648,25 @@ class AdminController extends AdminBaseController
// Ensure route is prefixed with a forward slash.
$route = '/' . ltrim($route, '/');
// Check for valid frontmatter
if (isset($data['frontmatter']) && !$this->checkValidFrontmatter($data['frontmatter'])) {
$this->admin->setMessage($this->admin->translate('PLUGIN_ADMIN.INVALID_FRONTMATTER_COULD_NOT_SAVE'),
'error');
return false;
}
// XSS Checks for page content
$xss_whitelist = $this->grav['config']->get('security.xss_whitelist', 'admin.super');
if (!$this->admin->authorize($xss_whitelist)) {
$check_what = ['header' => isset($data['header']) ? $data['header'] : '', 'frontmatter' => isset($data['frontmatter']) ? $data['frontmatter'] : '', 'content' => isset($data['content']) ? $data['content'] : ''];
$results = Security::detectXssFromArray($check_what);
if (!empty($results)) {
$this->admin->setMessage('<i class="fa fa-ban"></i> ' . $this->admin->translate('PLUGIN_ADMIN.XSS_ONSAVE_ISSUE'),
'error');
return false;
}
}
$parent = $route && $route !== '/' && $route !== '.' && $route !== '/.' ? $pages->dispatch($route, true) : $pages->root();
$original_order = (int)trim($obj->order(), '.');
@@ -908,11 +923,13 @@ class AdminController extends AdminBaseController
} catch (\Exception $e) {
$this->admin->json_response = ['status' => 'error', 'message' => $e->getMessage()];
return;
return false;
}
}
$this->admin->json_response = ['status' => 'success', 'feed_data' => $feed_data];
return true;
}
/**
@@ -956,12 +973,17 @@ class AdminController extends AdminBaseController
];
} else {
$this->admin->json_response = ['status' => 'error', 'message' => 'Cannot connect to the GPM'];
return false;
}
} catch (\Exception $e) {
$this->admin->json_response = ['status' => 'error', 'message' => $e->getMessage()];
return false;
}
return true;
}
/**
@@ -979,7 +1001,7 @@ class AdminController extends AdminBaseController
//No notifications cache (first time)
$this->admin->json_response = ['status' => 'success', 'notifications' => [], 'need_update' => true];
return;
return true;
}
$need_update = false;
@@ -996,7 +1018,7 @@ class AdminController extends AdminBaseController
} catch (\Exception $e) {
$this->admin->json_response = ['status' => 'error', 'message' => $e->getMessage()];
return;
return false;
}
$this->admin->json_response = [
@@ -1004,6 +1026,8 @@ class AdminController extends AdminBaseController
'notifications' => $notifications,
'need_update' => $need_update
];
return true;
}
/**
@@ -1166,8 +1190,8 @@ class AdminController extends AdminBaseController
'status' => 'error',
'message' => $this->admin->translate('PLUGIN_ADMIN.INSUFFICIENT_PERMISSIONS_FOR_TASK')
];
echo json_encode($json_response);
exit;
return $this->sendJsonResponse($json_response, 403);
}
//check if there are packages that have this as a dependency. Abort and show which ones
@@ -1182,8 +1206,8 @@ class AdminController extends AdminBaseController
}
$json_response = ['status' => 'error', 'message' => $message];
echo json_encode($json_response);
exit;
return $this->sendJsonResponse($json_response, 200);
}
try {
@@ -1191,8 +1215,8 @@ class AdminController extends AdminBaseController
$result = Gpm::uninstall($package, []);
} catch (\Exception $e) {
$json_response = ['status' => 'error', 'message' => $e->getMessage()];
echo json_encode($json_response);
exit;
return $this->sendJsonResponse($json_response, 200);
}
if ($result) {
@@ -1201,16 +1225,16 @@ class AdminController extends AdminBaseController
'dependencies' => $dependencies,
'message' => $this->admin->translate(is_string($result) ? $result : 'PLUGIN_ADMIN.UNINSTALL_SUCCESSFUL')
];
echo json_encode($json_response);
exit;
return $this->sendJsonResponse($json_response, 200);
}
$json_response = [
'status' => 'error',
'message' => $this->admin->translate('PLUGIN_ADMIN.UNINSTALL_FAILED')
];
echo json_encode($json_response);
exit;
return $this->sendJsonResponse($json_response, 200);
}
/**
@@ -1230,8 +1254,8 @@ class AdminController extends AdminBaseController
'status' => 'error',
'message' => $this->admin->translate('PLUGIN_ADMIN.INSUFFICIENT_PERMISSIONS_FOR_TASK')
];
echo json_encode($json_response);
exit;
$this->sendJsonResponse($json_response, 403);
}
$url = "https://getgrav.org/download/{$type}s/$slug/$current_version";
@@ -1363,7 +1387,7 @@ class AdminController extends AdminBaseController
}
$download = urlencode(base64_encode($backup));
$url = rtrim($this->grav['uri']->rootUrl(true), '/') . '/' . trim($this->admin->base,
$url = rtrim($this->grav['uri']->rootUrl(false), '/') . '/' . trim($this->admin->base,
'/') . '/task' . $param_sep . 'backup/download' . $param_sep . $download . '/admin-nonce' . $param_sep . Utils::getNonce('admin-form');
$log->content([
@@ -1670,6 +1694,19 @@ class AdminController extends AdminBaseController
return false;
}
$filename = $_FILES['file']['name'];
// Handle bad filenames.
if (!Utils::checkFilename($filename)) {
$this->admin->json_response = [
'status' => 'error',
'message' => sprintf($this->admin->translate('PLUGIN_ADMIN.FILEUPLOAD_UNABLE_TO_UPLOAD'),
$filename, 'Bad filename')
];
return false;
}
$grav_limit = $config->get('system.media.upload_limit', 0);
// You should also check filesize here.
if ($grav_limit > 0 && $_FILES['file']['size'] > $grav_limit) {
@@ -1683,18 +1720,13 @@ class AdminController extends AdminBaseController
// Check extension
$fileParts = pathinfo($_FILES['file']['name']);
$fileExt = '';
if (isset($fileParts['extension'])) {
$fileExt = strtolower($fileParts['extension']);
}
$extension = strtolower(pathinfo($filename, PATHINFO_EXTENSION));
// If not a supported type, return
if (!$fileExt || !$config->get("media.types.{$fileExt}")) {
if (!$extension || !$config->get("media.types.{$extension}")) {
$this->admin->json_response = [
'status' => 'error',
'message' => $this->admin->translate('PLUGIN_ADMIN.UNSUPPORTED_FILE_TYPE') . ': ' . $fileExt
'message' => $this->admin->translate('PLUGIN_ADMIN.UNSUPPORTED_FILE_TYPE') . ': ' . $extension
];
return false;
@@ -1720,7 +1752,7 @@ class AdminController extends AdminBaseController
// Upload it
if (!move_uploaded_file($_FILES['file']['tmp_name'],
sprintf('%s/%s', $path, $_FILES['file']['name']))
sprintf('%s/%s', $path, $filename))
) {
$this->admin->json_response = [
'status' => 'error',
@@ -1732,13 +1764,12 @@ class AdminController extends AdminBaseController
// Add metadata if needed
$include_metadata = Grav::instance()['config']->get('system.media.auto_metadata_exif', false);
$filename = $fileParts['basename'];
$filename = str_replace(['@3x', '@2x'], '', $filename);
$basename = str_replace(['@3x', '@2x'], '', pathinfo($filename, PATHINFO_BASENAME));
$metadata = [];
if ($include_metadata && isset($media[$filename])) {
$img_metadata = $media[$filename]->metadata();
if ($include_metadata && isset($media[$basename])) {
$img_metadata = $media[$basename]->metadata();
if ($img_metadata) {
$metadata = $img_metadata;
}
@@ -1781,6 +1812,11 @@ class AdminController extends AdminBaseController
$filename = !empty($this->post['filename']) ? $this->post['filename'] : null;
// Handle bad filenames.
if (!Utils::checkFilename($filename)) {
$filename = null;
}
if (!$filename) {
$this->admin->json_response = [
'status' => 'error',
@@ -1869,7 +1905,7 @@ class AdminController extends AdminBaseController
protected function taskProcessMarkdown()
{
if (!$this->authorizeTask('process markdown', ['admin.pages', 'admin.super'])) {
return;
return false;
}
try {
@@ -2224,6 +2260,8 @@ class AdminController extends AdminBaseController
$admin_route = $this->admin->base;
$this->setRedirect('/' . $language . $admin_route . '/' . $redirect);
return true;
}
/**
@@ -2259,6 +2297,16 @@ class AdminController extends AdminBaseController
}
$file_path = $_FILES['uploaded_file']['tmp_name'];
// Handle bad filenames.
if (!Utils::checkFilename(basename($file_path))) {
$this->admin->json_response = [
'status' => 'error',
'message' => $this->admin->translate('PLUGIN_ADMIN.UNKNOWN_ERRORS')
];
return false;
}
}
@@ -2313,9 +2361,10 @@ class AdminController extends AdminBaseController
$aPage->template($obj->template());
$aPage->validate();
$aPage->filter();
$aPage->save();
$this->grav->fireEvent('onAdminAfterSave', new Event(['page' => $obj]));
$this->grav->fireEvent('onAdminSave', new Event(['page' => &$aPage]));
$aPage->save();
$this->grav->fireEvent('onAdminAfterSave', new Event(['page' => $aPage]));
}
$this->admin->setMessage($this->admin->translate('PLUGIN_ADMIN.SUCCESSFULLY_SWITCHED_LANGUAGE'), 'info');
+24 -4
View File
@@ -1,15 +1,35 @@
{
"name": "grav-plugin-admin",
"type": "grav-plugin",
"description": "Admin plugin for Grav CMS",
"keywords": ["admin", "plugin", "manager", "panel"],
"homepage": "https://github.com/trilbymedia/grav-plugin-admin",
"license": "MIT",
"authors": [
{
"name": "Team Grav",
"email": "devs@getgrav.org",
"homepage": "http://getgrav.org",
"role": "Developer"
}
],
"require": {
"php": ">=5.6.4",
"composer/semver": "^1.4",
"fguillot/picofeed": "@stable"
},
"require-dev": {
"codeception/codeception": "^2.1",
"fzaninotto/faker": "^1.5",
"symfony/yaml": "~2.8",
"symfony/console": "~2.8",
"symfony/finder": "~2.8",
"symfony/event-dispatcher": "~2.8"
"symfony/yaml": "~3.4",
"symfony/console": "~3.4",
"symfony/finder": "~3.4",
"symfony/event-dispatcher": "~3.4"
},
"config": {
"platform": {
"php": "5.6.4"
}
},
"scripts": {
"test": "vendor/bin/codecept run unit",
+372 -478
View File
File diff suppressed because it is too large Load Diff
+17 -2
View File
@@ -507,7 +507,6 @@ PLUGIN_ADMIN:
ADD_FOLDER: "Add Folder"
PROXY_URL: "Proxy URL"
PROXY_URL_HELP: "Enter the proxy HOST or IP and PORT"
INVALID_FRONTMATTER_COULD_NOT_SAVE: "Invalid frontmatter, could not save"
NOTHING_TO_SAVE: "Nothing to Save"
FILE_ERROR_ADD: "An error occurred while trying to add the file"
FILE_ERROR_UPLOAD: "An error occurred while trying to upload the file"
@@ -559,7 +558,6 @@ PLUGIN_ADMIN:
CANNOT_ADD_FILES_PAGE_NOT_SAVED: "NOTE: Page must be saved before you can upload files to it."
DROP_FILES_HERE_TO_UPLOAD: "Drop your files here or <strong>click in this area</strong>"
INSERT: "Insert"
METADATA: "Metadata"
UNDO: "Undo"
REDO: "Redo"
HEADERS: "Headers"
@@ -724,3 +722,20 @@ PLUGIN_ADMIN:
STRICT_YAML_COMPAT_HELP: "Falls back to Symfony 2.4 YAML parser if Native or 3.4 parser fails"
STRICT_TWIG_COMPAT: "Twig Compatibility"
STRICT_TWIG_COMPAT_HELP: "Enables deprecated Twig autoescape setting. When disabled, |raw filter is required to output HTML as Twig will autoescape output"
SECURITY: "Security"
XSS_SECURITY: "XSS Security"
XSS_WHITELIST_PERMISSIONS: "Whitelist Permissions"
XSS_WHITELIST_PERMISSIONS_HELP: "Users with these permissions will skip the XSS rules when saving content"
XSS_ON_EVENTS: "On-events"
XSS_INVALID_PROTOCOLS: "Enable Invalid protocols"
XSS_MOZ_BINDINGS: "Moz bindings"
XSS_HTML_INLINE_STYLES: "HTML inline styles"
XSS_DANGEROUS_TAGS: "Dangerous HTML tags"
XSS_DANGEROUS_TAGS_LIST: "Dangerous HTML tags list"
XSS_ONSAVE_ISSUE: "Save failed: XSS issue detected..."
XSS_ISSUE: "<strong>NOTICE:</strong> Grav found potential XSS issues in <strong>%s</strong>"
UPLOADS_SECURITY: "Uploads Security"
UPLOADS_DANGEROUS_EXTENSIONS: "Dangerous Extensions"
UPLOADS_DANGEROUS_EXTENSIONS_HELP: "Block these extensions from being uploaded no matter the accepted MIME types"
@@ -2,7 +2,7 @@ import $ from 'jquery';
import { config, uri_params } from 'grav-config';
import request from '../../utils/request';
const insertTextAt = (string, index, text) => [string.slice(0, index), text, string.slice(index)].join('');
// const insertTextAt = (string, index, text) => [string.slice(0, index), text, string.slice(index)].join('');
export default class FilePickerField {
@@ -79,14 +79,12 @@ export default class FilePickerField {
let renderOption = function renderOption(item, escape) {
let image = '';
if (imagesPreview && folder && (!item.status || item.status === 'available') && item.name.match(/\.(jpg|jpeg|png|gif)$/i)) {
const fallback2x = insertTextAt(`${config.base_url_relative}/../${folder}/${item.name}`, -4, '@2x');
const fallback3x = insertTextAt(`${config.base_url_relative}/../${folder}/${item.name}`, -4, '@3x');
// const fallback2x = insertTextAt(`${config.base_url_relative}/../${folder}/${item.name}`, -4, '@2x');
// const fallback3x = insertTextAt(`${config.base_url_relative}/../${folder}/${item.name}`, -4, '@3x');
const source = thumbs[item.name] || `${config.base_url_relative}/../${folder}/${item.name}`;
image = `
<img class="filepicker-field-image"
src="${source}"
onerror="if(this.src==='${fallback2x}'){this.src='${fallback3x}';this.onerror='';}else{this.src='${fallback2x}'}" />`;
// onerror="if(this.src==='${fallback2x}'){this.src='${fallback3x}';}else{this.src='${fallback2x}'}"
image = `<img class="filepicker-field-image" src="${source}" />`;
}
return `<div>
+2 -3
View File
@@ -14,9 +14,8 @@ import request from './utils/request';
import './utils/2fa';
// bootstrap jQuery extensions
import 'bootstrap/js/transition';
import 'bootstrap/js/dropdown';
import 'bootstrap/js/collapse';
import './utils/bootstrap-transition';
import './utils/bootstrap-collapse';
// tabs memory
import './utils/tabs-memory';
@@ -113,7 +113,7 @@ export default class Updates {
<p>
<a href="#" class="button button-small secondary" data-remodal-target="update-packages" data-packages-slugs="${Object.keys(resources).join()}" data-${singles[index]}-action="start-packages-update">${translations.PLUGIN_ADMIN.UPDATE} ${translations.PLUGIN_ADMIN.ALL} ${type_translation}</a>
<i class="fa fa-bullhorn"></i>
${length} ${translations.PLUGIN_ADMIN.OF_YOUR} ${type} ${translations.PLUGIN_ADMIN.HAVE_AN_UPDATE_AVAILABLE}
${length} ${translations.PLUGIN_ADMIN.OF_YOUR} ${type_translation} ${translations.PLUGIN_ADMIN.HAVE_AN_UPDATE_AVAILABLE}
</p>
`);
@@ -0,0 +1,210 @@
import jQuery from 'jquery';
/* ========================================================================
* Bootstrap: collapse.js v3.4.0
* http://getbootstrap.com/javascript/#collapse
* ========================================================================
* Copyright 2011-2016 Twitter, Inc.
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
* ======================================================================== */
/* jshint latedef: false */
+(function($) {
'use strict';
// COLLAPSE PUBLIC CLASS DEFINITION
// ================================
var Collapse = function(element, options) {
this.$element = $(element);
this.options = $.extend({}, Collapse.DEFAULTS, options);
this.$trigger = $('[data-toggle="collapse"][href="#' + element.id + '"],' +
'[data-toggle="collapse"][data-target="#' + element.id + '"]');
this.transitioning = null;
if (this.options.parent) {
this.$parent = this.getParent();
} else {
this.addAriaAndCollapsedClass(this.$element, this.$trigger);
}
if (this.options.toggle) this.toggle();
};
Collapse.VERSION = '3.4.0';
Collapse.TRANSITION_DURATION = 350;
Collapse.DEFAULTS = {
toggle: true
};
Collapse.prototype.dimension = function() {
var hasWidth = this.$element.hasClass('width');
return hasWidth ? 'width' : 'height';
};
Collapse.prototype.show = function() {
if (this.transitioning || this.$element.hasClass('in')) return;
var activesData;
var actives = this.$parent && this.$parent.children('.panel').children('.in, .collapsing');
if (actives && actives.length) {
activesData = actives.data('bs.collapse');
if (activesData && activesData.transitioning) return;
}
var startEvent = $.Event('show.bs.collapse');
this.$element.trigger(startEvent);
if (startEvent.isDefaultPrevented()) return;
if (actives && actives.length) {
Plugin.call(actives, 'hide');
activesData || actives.data('bs.collapse', null);
}
var dimension = this.dimension();
this.$element
.removeClass('collapse')
.addClass('collapsing')[dimension](0)
.attr('aria-expanded', true);
this.$trigger
.removeClass('collapsed')
.attr('aria-expanded', true);
this.transitioning = 1;
var complete = function() {
this.$element
.removeClass('collapsing')
.addClass('collapse in')[dimension]('');
this.transitioning = 0;
this.$element
.trigger('shown.bs.collapse');
};
if (!$.support.transition) return complete.call(this);
var scrollSize = $.camelCase(['scroll', dimension].join('-'));
this.$element
.one('bsTransitionEnd', $.proxy(complete, this))
.emulateTransitionEnd(Collapse.TRANSITION_DURATION)[dimension](this.$element[0][scrollSize]);
};
Collapse.prototype.hide = function() {
if (this.transitioning || !this.$element.hasClass('in')) return;
var startEvent = $.Event('hide.bs.collapse');
this.$element.trigger(startEvent);
if (startEvent.isDefaultPrevented()) return;
var dimension = this.dimension();
this.$element[dimension](this.$element[dimension]())[0].offsetHeight;
this.$element
.addClass('collapsing')
.removeClass('collapse in')
.attr('aria-expanded', false);
this.$trigger
.addClass('collapsed')
.attr('aria-expanded', false);
this.transitioning = 1;
var complete = function() {
this.transitioning = 0;
this.$element
.removeClass('collapsing')
.addClass('collapse')
.trigger('hidden.bs.collapse');
};
if (!$.support.transition) return complete.call(this);
this.$element[dimension](0)
.one('bsTransitionEnd', $.proxy(complete, this))
.emulateTransitionEnd(Collapse.TRANSITION_DURATION);
};
Collapse.prototype.toggle = function() {
this[this.$element.hasClass('in') ? 'hide' : 'show']();
};
Collapse.prototype.getParent = function() {
return $(this.options.parent)
.find('[data-toggle="collapse"][data-parent="' + this.options.parent + '"]')
.each($.proxy(function(i, element) {
var $element = $(element);
this.addAriaAndCollapsedClass(getTargetFromTrigger($element), $element);
}, this))
.end();
};
Collapse.prototype.addAriaAndCollapsedClass = function($element, $trigger) {
var isOpen = $element.hasClass('in');
$element.attr('aria-expanded', isOpen);
$trigger
.toggleClass('collapsed', !isOpen)
.attr('aria-expanded', isOpen);
};
function getTargetFromTrigger($trigger) {
var href;
var target = $trigger.attr('data-target') ||
(href = $trigger.attr('href')) && href.replace(/.*(?=#[^\s]+$)/, ''); // strip for ie7
return $(target);
}
// COLLAPSE PLUGIN DEFINITION
// ==========================
function Plugin(option) {
return this.each(function() {
var $this = $(this);
var data = $this.data('bs.collapse');
var options = $.extend({}, Collapse.DEFAULTS, $this.data(), typeof option === 'object' && option);
if (!data && options.toggle && /show|hide/.test(option)) options.toggle = false;
if (!data) $this.data('bs.collapse', (data = new Collapse(this, options)));
if (typeof option === 'string') data[option]();
});
}
var old = $.fn.collapse;
$.fn.collapse = Plugin;
$.fn.collapse.Constructor = Collapse;
// COLLAPSE NO CONFLICT
// ====================
$.fn.collapse.noConflict = function() {
$.fn.collapse = old;
return this;
};
// COLLAPSE DATA-API
// =================
$(document).on('click.bs.collapse.data-api', '[data-toggle="collapse"]', function(e) {
var $this = $(this);
if (!$this.attr('data-target')) e.preventDefault();
var $target = getTargetFromTrigger($this);
var data = $target.data('bs.collapse');
var option = data ? 'toggle' : $this.data();
Plugin.call($target, option);
});
}(jQuery));
@@ -0,0 +1,52 @@
import jQuery from 'jquery';
+(function($) {
'use strict';
// CSS TRANSITION SUPPORT (Shoutout: http://www.modernizr.com/)
// ============================================================
function transitionEnd() {
var el = document.createElement('bootstrap');
var transEndEventNames = {
WebkitTransition: 'webkitTransitionEnd',
MozTransition: 'transitionend',
OTransition: 'oTransitionEnd otransitionend',
transition: 'transitionend'
};
for (var name in transEndEventNames) {
if (el.style[name] !== undefined) {
return { end: transEndEventNames[name] };
}
}
return false; // explicit for ie8 ( ._.)
}
// http://blog.alexmaccaw.com/css-transitions
$.fn.emulateTransitionEnd = function(duration) {
var called = false;
var $el = this;
$(this).one('bsTransitionEnd', function() { called = true; });
var callback = function() { if (!called) $($el).trigger($.support.transition.end); };
setTimeout(callback, duration);
return this;
};
$(function() {
$.support.transition = transitionEnd();
if (!$.support.transition) return;
$.event.special.bsTransitionEnd = {
bindType: $.support.transition.end,
delegateType: $.support.transition.end,
handle: function(e) {
if ($(e.target).is(this)) return e.handleObj.handler.apply(this, arguments);
}
};
});
}(jQuery));
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+80
View File
@@ -0,0 +1,80 @@
(function($) {
$(function() {
/**
* polyfill for html5 form attr
*/
// detect if browser supports this
var sampleElement = $('[form]').get(0);
var isIE11 = !(window.ActiveXObject) && "ActiveXObject" in window;
if (sampleElement && window.HTMLFormElement && sampleElement.form instanceof HTMLFormElement && !isIE11) {
// browser supports it, no need to fix
return;
}
/**
* Append a field to a form
*
*/
$.fn.appendField = function(data) {
// for form only
if (!this.is('form')) return;
// wrap data
if (!$.isArray(data) && data.name && data.value) {
data = [data];
}
var $form = this;
// attach new params
$.each(data, function(i, item) {
$('<input/>')
.attr('type', 'hidden')
.attr('name', item.name)
.val(item.value).appendTo($form);
});
return $form;
};
/**
* Find all input fields with form attribute point to jQuery object
*
*/
$('form[id]').submit(function(e) {
// serialize data
var data = $('[form=' + this.id + ']').serializeArray();
// append data to form
$(this).appendField(data);
}).each(function() {
var form = this,
$fields = $('[form=' + this.id + ']');
$fields.filter('button, input')
.filter('[type=reset],[type=submit],[type=image],button')
.click(function() {
var type = this.type.toLowerCase();
if (type === 'reset') {
// reset form
form.reset();
// for elements outside form
$fields.each(function() {
this.value = this.defaultValue;
this.checked = this.defaultChecked;
}).filter('select').each(function() {
$(this).find('option').each(function() {
this.selected = this.defaultSelected;
});
});
} else {
$(form).appendField({
name: this.name,
value: this.value
}).submit();
}
});
});
});
})(jQuery);
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -13,7 +13,6 @@
"author": "RocketTheme, LLC",
"license": "MIT",
"dependencies": {
"bootstrap": "^3.3.7",
"chartist": "0.11.0",
"codemirror": "^5.30.0",
"cookies-js": "^1.2.3",
@@ -28,6 +27,7 @@
"immutablediff": "^0.4.4",
"js-yaml": "^3.10.0",
"mout": "^1.0.0",
"popper.js": "^1.14.4",
"rangetouch": "^1.0.5",
"remodal": "^1.1.1",
"selectize": "^0.12.4",
@@ -251,7 +251,7 @@ tr {
&:focus, &:hover {
color: $button-text;
background-color: $button-bg;
background-color: darken($button-bg, 5%);
}
}
@@ -1,13 +1,5 @@
{% extends "forms/field.html.twig" %}
{% block global_attributes %}
data-grav-array-name="{{ (scope ~ field.name)|fieldName }}"
data-grav-array-keyname="{{ field.placeholder_key|e|tu }}"
data-grav-array-valuename="{{ field.placeholder_value|e|tu }}"
data-grav-array-textarea="{{ field.value_type == 'textarea' }}"
{{ parent() }}
{% endblock %}
{% macro renderer(key, text, field, scope) %}
{% if text is not iterable %}
@@ -20,26 +12,26 @@
{% endif %}
<input
data-grav-array-type="key"
type="text" value="{{ key }}"
{% if field.disabled or isDisabledToggleable %}disabled="disabled"{% endif %}
placeholder="{{ field.placeholder_key|e|tu }}" />
data-grav-array-type="key"
type="text" value="{{ key }}"
{% if field.disabled or isDisabledToggleable %}disabled="disabled"{% endif %}
placeholder="{{ field.placeholder_key|e|tu }}" />
{% endif %}
{% if field.value_type == 'textarea' %}
<textarea
data-grav-array-type="value"
name="{{ ((scope ~ field.name)|fieldName) ~ '[' ~ key ~ ']' }}"
placeholder="{{ field.placeholder_value|e|tu }}"
{% if field.disabled or isDisabledToggleable %}disabled="disabled"{% endif %}>{{ text }}</textarea>
data-grav-array-type="value"
name="{{ ((scope ~ field.name)|fieldName) ~ '[' ~ key ~ ']' }}"
placeholder="{{ field.placeholder_value|e|tu }}"
{% if field.disabled or isDisabledToggleable %}disabled="disabled"{% endif %}>{{ text }}</textarea>
{% else %}
<input
data-grav-array-type="value"
type="text"
name="{{ ((scope ~ field.name)|fieldName) ~ '[' ~ key ~ ']' }}"
placeholder="{{ field.placeholder_value|e|tu }}"
{% if field.disabled or isDisabledToggleable %}disabled="disabled"{% endif %}
value={% if text == 'true' %}true{% elseif text == 'false' %}false{% else %}"{{ text|join(', ')|e }}"{% endif %} />
data-grav-array-type="value"
type="text"
name="{{ ((scope ~ field.name)|fieldName) ~ '[' ~ key ~ ']' }}"
placeholder="{{ field.placeholder_value|e|tu }}"
{% if field.disabled or isDisabledToggleable %}disabled="disabled"{% endif %}
value={% if text == 'true' %}true{% elseif text == 'false' %}false{% else %}"{{ text|join(', ')|e }}"{% endif %} />
{% endif %}
<span data-grav-array-action="rem" class="fa fa-minus"></span>
@@ -48,8 +40,17 @@
{% endif %}
{% endmacro %}
{% import _self as array_field %}
{% block global_attributes %}
data-grav-array-name="{{ (scope ~ field.name)|fieldName }}"
data-grav-array-keyname="{{ field.placeholder_key|e|tu }}"
data-grav-array-valuename="{{ field.placeholder_value|e|tu }}"
data-grav-array-textarea="{{ field.value_type == 'textarea' }}"
{{ parent() }}
{% endblock %}
{% block input %}
{% import _self as array_field %}
<div class="{{ field.size }}" data-grav-array-type="container"{% if field.value_only %} data-grav-array-mode="value_only"{% endif %}{{ value|length <= 1 ? ' class="one-child"' : '' }}>
{% if value|length %}
{% for key, text in value -%}
@@ -32,13 +32,13 @@
{% if field.resizer is not defined or field.resizer not in ['off', 'false', 0] %}<div class="grav-editor-resizer"></div>{% endif %}
{% if field.description %}
<div class="form-extra-wrapper {{ field.size }} {{ field.wrapper_classes }}">
<span class="form-description">
{% if field.markdown %}
{{ field.description|tu|markdown(false)|raw }}
{% else %}
{{ field.description|tu|raw }}
{% endif %}
</span>
<span class="form-description">
{% if field.markdown %}
{{ field.description|tu|markdown(false)|raw }}
{% else %}
{{ field.description|tu|raw }}
{% endif %}
</span>
</div>
{% endif %}
</div>
@@ -1,9 +1,4 @@
{% extends "forms/field.html.twig" %}
{% set defaults = config.plugins.form %}
{% set files = defaults.files|merge(field|default([])) %}
{% set limit = not field.multiple ? 1 : files.limit %}
{% macro bytesToSize(bytes) -%}
{% spaceless %}
@@ -43,20 +38,26 @@
{% set real_path = global.admin.getPagePathFromToken(path) %}
{% set remove = global.file_url_remove ? global.file_url_remove : (global.base_url_relative ~ '/media.json') %}
{% set remove = uri.addNonce(
remove ~
'/route' ~ config.system.param_sep ~ base64_encode(global.base_path ~ '/' ~ real_path) ~
'/task' ~ config.system.param_sep ~ 'removeFileFromBlueprint' ~
'/proute' ~ config.system.param_sep ~ base64_encode(route) ~
'/blueprint' ~ config.system.param_sep ~ blueprint ~
'/type' ~ config.system.param_sep ~ type ~
'/field' ~ config.system.param_sep ~ files.name ~
'/path' ~ config.system.param_sep ~ base64_encode(value.path), 'admin-form', 'admin-nonce') %}
remove ~
'/route' ~ config.system.param_sep ~ base64_encode(global.base_path ~ '/' ~ real_path) ~
'/task' ~ config.system.param_sep ~ 'removeFileFromBlueprint' ~
'/proute' ~ config.system.param_sep ~ base64_encode(route) ~
'/blueprint' ~ config.system.param_sep ~ blueprint ~
'/type' ~ config.system.param_sep ~ type ~
'/field' ~ config.system.param_sep ~ files.name ~
'/path' ~ config.system.param_sep ~ base64_encode(value.path), 'admin-form', 'admin-nonce') %}
{% set file = value|merge({remove: remove, path: (uri.rootUrl == '/' ? '/' : uri.rootUrl ~ '/' ~ real_path) }) %}
<div class="hidden" data-file="{{ file|json_encode|e('html_attr') }}"></div>
{% endif %}
{% endmacro %}
{% import _self as macro %}
{% set defaults = config.plugins.form %}
{% set files = defaults.files|merge(field|default([])) %}
{% set limit = not field.multiple ? 1 : files.limit %}
{% block input %}
{% set upload_limit = config.system.media.upload_limit / 1024 / 1024 %}
{% set page_can_upload = exists or (type == 'page' and not exists and not (field.destination starts with '@self' or field.destination starts with 'self@')) %}
@@ -78,7 +79,7 @@
/>
{% for path, file in value %}
{{ _self.preview(path, file, _context) }}
{{ macro.preview(path, file, _context) }}
{% endfor %}
{% include 'forms/fields/hidden/hidden.html.twig' with {field: {name: '_json.' ~ field.name}, value:value|raw|json_encode} %}
</div>
@@ -77,7 +77,7 @@
{% set childName = itemName -%}
{%- elseif childName starts with '.' -%}
{% set childKey = childName|trim('.') %}
{% set childValue = val[childName[1:]] %}
{% set childValue = val|nested(childName) %}
{% set childName = itemName ~ childName %}
{% else %}
{% set childKey = childName %}
@@ -1,13 +1,8 @@
{% extends "forms/field.html.twig" %}
{% block global_attributes %}
data-grav-array-name="{{ (scope ~ field.name)|fieldName }}"
data-grav-array-keyname="{{ field.placeholder_key|e|tu }}"
data-grav-array-valuename="{{ field.placeholder_value|e|tu }}"
{{ parent() }}
{% endblock %}
{% macro renderer(key, content, field, scope, level, parent_key, up_level) %}
{% import _self as self %}
{% macro field(value, key, level, globalvars, disable_name, hidden) %}
{% set name = 'data[' ~ globalvars.field.name|replace({'.': ']['}) ~ ']' ~ key %}
<div class="form-row array-field-value_only js__multilevel-field {{ level == 0 ? 'top' : '' }}"
@@ -28,14 +23,14 @@
{% if level == 0 %}
{{ _self.field(key, '', level, _context, true, (is_numeric(key) ? true : false)) }}
{{ self.field(key, '', level, _context, true, (is_numeric(key) ? true : false)) }}
{% if content is not iterable %}
{% set level2 = level + 1 %}
<div class="children-wrapper">
<div class="element-wrapper">
{{ _self.field(content, '[' ~ key ~ ']', level2, _context) }}
{{ self.field(content, '[' ~ key ~ ']', level2, _context) }}
</div>
</div>
{% endif %}
@@ -51,9 +46,9 @@
<div class="element-wrapper">
{% if not is_numeric(inner_key) %}
{% if (content|length > 1) %}
{{ _self.field(inner_key, parent_key, level, _context, true) }}
{{ self.field(inner_key, parent_key, level, _context, true) }}
{% else %}
{{ _self.field(inner_key, parent_key, level, _context) }}
{{ self.field(inner_key, parent_key, level, _context) }}
{% endif %}
{% set level2 = level + 1 %}
{% set up_level = true %}
@@ -70,7 +65,7 @@
{% endif %}
{% set last_key = (is_numeric(inner_key)) ? '' : inner_key %}
{{ _self.field(inner_content, parent_key ~ '[' ~ inner_key ~ ']', level2, _context) }}
{{ self.field(inner_content, parent_key ~ '[' ~ inner_key ~ ']', level2, _context) }}
{% if not is_numeric(inner_key) %}
</div>
@@ -79,19 +74,28 @@
{% else %}
{% set inner_parent_key = parent_key ~ '[' ~ inner_key ~ ']' %}
{{ _self.renderer(inner_key, inner_content, field, scope, level, inner_parent_key, up_level) }}
{{ self.renderer(inner_key, inner_content, field, scope, level, inner_parent_key, up_level) }}
{% endif %}
</div>
{% endfor %}
</div>
{% endmacro %}
{% import _self as macro %}
{% block global_attributes %}
data-grav-array-name="{{ (scope ~ field.name)|fieldName }}"
data-grav-array-keyname="{{ field.placeholder_key|e|tu }}"
data-grav-array-valuename="{{ field.placeholder_value|e|tu }}"
{{ parent() }}
{% endblock %}
{% block input %}
<div data-id="{{random_string()}}" data-grav-multilevel-field data-grav-array-type="container" data-grav-array-mode="value_only"{{ value|length <= 1 ? ' class="one-child"' : '' }}>
{% if value|length %}
{% for key, content in value -%}
<div class="element-wrapper">
{{ _self.renderer(key, content, field, scope, 0, '[' ~ key ~ ']', true) }}
{{ macro.renderer(key, content, field, scope, 0, '[' ~ key ~ ']', true) }}
</div>
{% endfor %}
{%- else -%}
@@ -14,6 +14,8 @@
{% endfor %}
{% endmacro %}
{% import _self as macro %}
{% block global_attributes %}
data-grav-selectize="{{ (field.selectize is defined ? field.selectize : {})|json_encode|e('html_attr') }}"
data-grav-field="select"
@@ -43,7 +45,7 @@
{% if field.show_root %}
<option value="/">/ (root)</option>
{% endif %}
{{ _self.page_options(_context, page_list) }}
{{ macro.page_options(_context, page_list) }}
</select>
</div>
{% endblock %}
@@ -1,5 +1,12 @@
{% extends "forms/field.html.twig" %}
{% macro spanToggle(input, length) %}
{% set space = repeat('&nbsp;&nbsp;', (length - input|length) / 2) %}
{{ (space ~ input ~ space)|raw }}
{% endmacro %}
{% import _self as macro %}
{% set value = (value is null ? field.default : value) %}
{% set value = (value is same as(false) ? 0 : value) %}
@@ -8,11 +15,6 @@
data-grav-default="{{ field.default|json_encode()|e('html_attr') }}"
{% endblock %}
{% macro spanToggle(input, length) %}
{% set space = repeat('&nbsp;&nbsp;', (length - input|length) / 2) %}
{{ (space ~ input ~ space)|raw }}
{% endmacro %}
{% block input %}
<div class="permissions-container">
{% set permissions = admin.getPermissions %}
@@ -56,7 +58,7 @@
{% if field.validate.required in ['on', 'true', 1] %}required="required"{% endif %}
/>
<label for="{{ id }}">{{ (_self.spanToggle(translation, maxLen)|trim)|raw }}</label>
<label for="{{ id }}">{{ (macro.spanToggle(translation, maxLen)|trim)|raw }}</label>
{% endfor %}
<a></a>
</div>
@@ -7,11 +7,11 @@
{% if field.validate.max %}max="{{ field.validate.max }}"{% endif %}
{% if field.validate.step %}step="{{ field.validate.step }}"{% endif %}
{% if field.id is defined %}
id="range_{{ field.id|e|replace('.', '_') }}"
oninput="number_{{ field.id|e|replace('.', '_') }}_output.value = this.value"
id="range_{{ field.id|e|replace({'.': '_'}) }}"
oninput="number_{{ field.id|e|replace({'.': '_'}) }}_output.value = this.value"
{% else %}
id="range_{{ field.name|e|replace('.', '_') }}"
oninput="number_{{ field.name|e|replace('.', '_') }}_output.value = this.value"
id="range_{{ field.name|e|replace({'.': '_'}) }}"
oninput="number_{{ field.name|e|replace({'.': '_'}) }}_output.value = this.value"
{% endif %}
{{ parent() }}
{% endblock %}
@@ -30,11 +30,11 @@
value="0"
{% endif %}
{% if field.id is defined %}
id="number_{{ field.id|e|replace('.', '_') }}_output"
oninput="range_{{ field.id|e|replace('.', '_') }}.value = this.value"
id="number_{{ field.id|e|replace({'.': '_'}) }}_output"
oninput="range_{{ field.id|e|replace({'.': '_'}) }}.value = this.value"
{% else %}
id="number_{{ field.name|e|replace('.', '_') }}_output"
oninput="range_{{ field.name|e|replace('.', '_') }}.value = this.value"
id="number_{{ field.name|e|replace({'.': '_'}) }}_output"
oninput="range_{{ field.name|e|replace({'.': '_'}) }}.value = this.value"
{% endif %}
/>
<span class="range-append">{{ field.append }}</span>
@@ -1,5 +1,12 @@
{% extends "forms/field.html.twig" %}
{% macro spanToggle(input, length) %}
{% set space = repeat('&nbsp;&nbsp;', (length - input|length) / 2) %}
{{ (space ~ input ~ space)|raw }}
{% endmacro %}
{% import _self as macro %}
{% set value = (value is null ? field.default : value) %}
{% set value = (value is same as(false) ? 0 : value) %}
@@ -15,11 +22,6 @@
data-grav-field-name="{{ (scope ~ field.name)|fieldName }}"
{% endblock %}
{% macro spanToggle(input, length) %}
{% set space = repeat('&nbsp;&nbsp;', (length - input|length) / 2) %}
{{ (space ~ input ~ space)|raw }}
{% endmacro %}
{% block input %}
<div class="switch-toggle switch-grav {{ field.size }} switch-{{ field.options|length }} {{ field.classes }}">
@@ -54,7 +56,7 @@
{% endif %}
{% if field.validate.required in ['on', 'true', 1] %}required="required"{% endif %}
/>
<label for="{{ id }}">{{ (_self.spanToggle(translation, maxLen)|trim)|raw }}</label>
<label for="{{ id }}">{{ (macro.spanToggle(translation, maxLen)|trim)|raw }}</label>
{% endfor %}
</div>
{% endblock %}
@@ -0,0 +1,6 @@
{% set xss_header = data.value('header')|array %}
{% set xss_content = data.value('content') %}
{% set xss_status = xss({header: xss_header, content: xss_content}) %}
{% if xss_status is not empty %}
<div class="notice alert">{{ "PLUGIN_ADMIN.XSS_ISSUE"|tu([xss_status])|raw }}</div>
{% endif %}
@@ -4,6 +4,87 @@
{{ (repeat('&nbsp;&nbsp;', (length - input|length) / 2) ~ input ~ repeat('&nbsp;&nbsp;', (length - input|length) / 2))|raw }}
{% endmacro %}
{% macro loop(page, depth, twig_vars) %}
{% import _self as self %}
{% set config = twig_vars['config'] %}
{% set separator = config.system.param_sep %}
{% set display_field = config.plugins.admin.pages_list_display_field %}
{% set base_url = twig_vars['base_url_relative'] %}
{% set base_url_relative_frontend = twig_vars['base_url_relative_frontend'] %}
{% set base_url_simple = twig_vars['base_url_simple'] %}
{% set admin_route = twig_vars['admin_route'] %}
{% set admin_lang = twig_vars['admin_lang'] %}
{% set warn = twig_vars['warn'] %}
{% set uri = twig_vars['uri'] %}
{% if page.header.admin.children_display_order == 'collection' and page.header.content.order.by %}
{% if page.header.content.order.custom %}
{% set pcol = page.children().order(page.header.content.order.by, page.header.content.order.dir|default('asc'), page.header.content.order.custom) %}
{% else %}
{% set pcol = page.children().order(page.header.content.order.by, page.header.content.order.dir|default('asc')) %}
{% endif %}
{% else %}
{% set pcol = page.children() %}
{% endif %}
{% for p in pcol %}
{% set description = (not p.page ? "PLUGIN_ADMIN.FOLDER"|tu ~ ' &bull; ' : "PLUGIN_ADMIN.PAGE"|tu ~ ' &bull; ') ~
(p.modular ? "PLUGIN_ADMIN.MODULAR"|tu ~ ' &bull; ' : '') ~
(p.routable ? "PLUGIN_ADMIN.ROUTABLE"|tu ~ ' &bull; ' : "PLUGIN_ADMIN.NON_ROUTABLE"|tu ~ ' &bull; ') ~
(p.visible ? "PLUGIN_ADMIN.VISIBLE"|tu ~ ' &bull; ' : "PLUGIN_ADMIN.NON_VISIBLE"|tu ~ ' &bull; ') ~
(p.published ? "PLUGIN_ADMIN.PUBLISHED"|tu ~ ' &bull; ' : "PLUGIN_ADMIN.NON_PUBLISHED"|tu ~ ' &bull; ') %}
{% set page_url = getPageUrl(p) %}
<li class="page-item" data-nav-id="{{ p.route }}">
<div class="row page-item__row">
<span class="page-item__toggle" {{ p.children(0).count > 0 ? 'data-toggle="children"' : ''}}>
<i class="page-icon fa fa-fw fa-circle-o {{ p.children(0).count > 0 ? 'children-closed' : ''}} {{ p.modular ? 'modular' : (not p.routable ? 'not-routable' : (not p.visible ? 'not-visible' : (not p.page ? 'folder' : ''))) }}"></i>
</span>
<div class="page-item__content">
<div class="page-item__content-name">
<span data-hint="{{ description|trim(' &bull; ')|raw }}" class="hint--top page-item__content-hint">
{% set page_label = attribute(p.header, display_field)|defined(attribute(p, display_field))|defined(p.title) %}
<a href="{{ page_url }}" class="page-edit">{{ page_label|e }}</a>
</span>
{% if p.language %}
<span class="badge lang {% if p.language == admin_lang %}info{% endif %}">{{p.language}}</span>
{% endif %}
{% if p.home %}
<span class="page-home"><i class="fa fa-home"></i></span>
{% endif %}
</div>
<p class="page-route">{{ p.header.routes.default ?: p.route }} <span class="spacer"><i class="fa fa-long-arrow-right"></i></span> {{ p.template() }}</p>
</div>
<span class="page-item__tools">
{% if config.plugins.admin.frontend_preview_target != 'inline' %}
{% set preview_target = config.plugins.admin.frontend_preview_target %}
{% set preview_html = (base_url_relative_frontend|rtrim('/') ~ (p.home ? '' : p.route)) ?: '/' %}
{% set preview_link = p.routable ? '<a class="page-view" target="' ~ preview_target ~ '" href="' ~ preview_html ~ '" title="' ~ "PLUGIN_ADMIN.PREVIEW"|tu ~ '"> <i class="fa fa-fw fa-eye"></i></a>' : '' %}
{% else %}
{% set preview_html = (base_url|rtrim('/') ~ '/preview' ~ (p.home ? '' : p.route)) ?: '/' %}
{% set preview_link = p.routable ? '<a class="page-view" href="' ~ preview_html ~ '" title="' ~ "PLUGIN_ADMIN.PREVIEW"|tu ~ '"> <i class="fa fa-fw fa-eye"></i></a>' : '' %}
{% endif %}
{{ preview_link|raw }}
{% if warn %}
<a href="#delete" data-remodal-target="delete" data-delete-url="{{ uri.addNonce(page_url ~ '/task' ~ separator ~ 'delete', 'admin-form', 'admin-nonce') }}" class="page-delete" ><i class="fa fa-close"></i></a>
{% else %}
<a href="{{ uri.addNonce(page_url ~ '/task' ~ separator ~ 'delete', 'admin-form', 'admin-nonce') }}" class="page-delete" ><i class="fa fa-close"></i></a>
{% endif %}
</span>
</div>
{% if p.children().count > 0 %}
<ul class="depth-{{ depth + 1 }}" style="display:none;">
{{ self.loop(p, depth + 1, twig_vars) }}
</ul>
{% endif %}
</li>
{% endfor %}
{% endmacro %}
{% import _self as macro %}
{% if admin.route %}
{% set context = admin.page(true) %}
{% endif %}
@@ -50,85 +131,8 @@
{% set preview_link = context.routable ? '<a class="button" href="' ~ preview_html ~ '" title="' ~ "PLUGIN_ADMIN.PREVIEW"|tu ~ '"> <i class="fa fa-fw fa-eye" style="font-size:18px;margin-right:0;"></i></a>' : '' %}
{% endif %}
{% macro loop(page, depth, twig_vars) %}
{% set config = twig_vars['config'] %}
{% set separator = config.system.param_sep %}
{% set display_field = config.plugins.admin.pages_list_display_field %}
{% set base_url = twig_vars['base_url_relative'] %}
{% set base_url_relative_frontend = twig_vars['base_url_relative_frontend'] %}
{% set base_url_simple = twig_vars['base_url_simple'] %}
{% set admin_route = twig_vars['admin_route'] %}
{% set admin_lang = twig_vars['admin_lang'] %}
{% set warn = twig_vars['warn'] %}
{% set uri = twig_vars['uri'] %}
{% if page.header.admin.children_display_order == 'collection' and page.header.content.order.by %}
{% if page.header.content.order.custom %}
{% set pcol = page.children().order(page.header.content.order.by, page.header.content.order.dir|default('asc'), page.header.content.order.custom) %}
{% else %}
{% set pcol = page.children().order(page.header.content.order.by, page.header.content.order.dir|default('asc')) %}
{% endif %}
{% else %}
{% set pcol = page.children() %}
{% endif %}
{% for p in pcol %}
{% set description = (not p.page ? "PLUGIN_ADMIN.FOLDER"|tu ~ ' &bull; ' : "PLUGIN_ADMIN.PAGE"|tu ~ ' &bull; ') ~
(p.modular ? "PLUGIN_ADMIN.MODULAR"|tu ~ ' &bull; ' : '') ~
(p.routable ? "PLUGIN_ADMIN.ROUTABLE"|tu ~ ' &bull; ' : "PLUGIN_ADMIN.NON_ROUTABLE"|tu ~ ' &bull; ') ~
(p.visible ? "PLUGIN_ADMIN.VISIBLE"|tu ~ ' &bull; ' : "PLUGIN_ADMIN.NON_VISIBLE"|tu ~ ' &bull; ') ~
(p.published ? "PLUGIN_ADMIN.PUBLISHED"|tu ~ ' &bull; ' : "PLUGIN_ADMIN.NON_PUBLISHED"|tu ~ ' &bull; ') %}
{% set page_url = getPageUrl(p) %}
<li class="page-item" data-nav-id="{{ p.route }}">
<div class="row page-item__row">
<span class="page-item__toggle" {{ p.children(0).count > 0 ? 'data-toggle="children"' : ''}}>
<i class="page-icon fa fa-fw fa-circle-o {{ p.children(0).count > 0 ? 'children-closed' : ''}} {{ p.modular ? 'modular' : (not p.routable ? 'not-routable' : (not p.visible ? 'not-visible' : (not p.page ? 'folder' : ''))) }}"></i>
</span>
<div class="page-item__content">
<div class="page-item__content-name">
<span data-hint="{{ description|trim(' &bull; ')|raw }}" class="hint--top page-item__content-hint">
{% set page_label = attribute(p.header, display_field)|defined(attribute(p, display_field))|defined(p.title) %}
<a href="{{ page_url }}" class="page-edit">{{ page_label|e }}</a>
</span>
{% if p.language %}
<span class="badge lang {% if p.language == admin_lang %}info{% endif %}">{{p.language}}</span>
{% endif %}
{% if p.home %}
<span class="page-home"><i class="fa fa-home"></i></span>
{% endif %}
</div>
<p class="page-route">{{ p.header.routes.default ?: p.route }} <span class="spacer"><i class="fa fa-long-arrow-right"></i></span> {{ p.template() }}</p>
</div>
<span class="page-item__tools">
{% if config.plugins.admin.frontend_preview_target != 'inline' %}
{% set preview_target = config.plugins.admin.frontend_preview_target %}
{% set preview_html = (base_url_relative_frontend|rtrim('/') ~ (p.home ? '' : p.route)) ?: '/' %}
{% set preview_link = p.routable ? '<a class="page-view" target="' ~ preview_target ~ '" href="' ~ preview_html ~ '" title="' ~ "PLUGIN_ADMIN.PREVIEW"|tu ~ '"> <i class="fa fa-fw fa-eye"></i></a>' : '' %}
{% else %}
{% set preview_html = (base_url|rtrim('/') ~ '/preview' ~ (p.home ? '' : p.route)) ?: '/' %}
{% set preview_link = p.routable ? '<a class="page-view" href="' ~ preview_html ~ '" title="' ~ "PLUGIN_ADMIN.PREVIEW"|tu ~ '"> <i class="fa fa-fw fa-eye"></i></a>' : '' %}
{% endif %}
{{ preview_link|raw }}
{% if warn %}
<a href="#delete" data-remodal-target="delete" data-delete-url="{{ uri.addNonce(page_url ~ '/task' ~ separator ~ 'delete', 'admin-form', 'admin-nonce') }}" class="page-delete" ><i class="fa fa-close"></i></a>
{% else %}
<a href="{{ uri.addNonce(page_url ~ '/task' ~ separator ~ 'delete', 'admin-form', 'admin-nonce') }}" class="page-delete" ><i class="fa fa-close"></i></a>
{% endif %}
</span>
</div>
{% if p.children().count > 0 %}
<ul class="depth-{{ depth + 1 }}" style="display:none;">
{{ _self.loop(p, depth + 1, twig_vars) }}
</ul>
{% endif %}
</li>
{% endfor %}
{% endmacro %}
{% block titlebar %}
<div class="button-bar">
{% if mode == 'list' %}
<a class="button" href="{{ base_url }}"><i class="fa fa-reply"></i> {{ "PLUGIN_ADMIN.BACK"|tu }}</a>
@@ -301,8 +305,8 @@
{% set normalText = "PLUGIN_ADMIN.NORMAL"|tu %}
{% set expertText = "PLUGIN_ADMIN.EXPERT"|tu %}
{% set maxLen = max([normalText|length, expertText|length]) %}
{% set normalText = _self.spanToggle(normalText, maxLen) %}
{% set expertText = _self.spanToggle(expertText, maxLen) %}
{% set normalText = macro.spanToggle(normalText, maxLen) %}
{% set expertText = macro.spanToggle(expertText, maxLen) %}
<form id="admin-mode-toggle">
<div class="switch-toggle switch-grav">
<input type="radio" value="normal" data-leave-url="{{ base_url }}/pages/{{ admin.route|trim('/') }}/mode{{ config.system.param_sep }}normal" id="normal" name="mode-switch" class="highlight" {% if admin.session.expert == '0' %} checked="checked"{% endif %}>
@@ -316,6 +320,11 @@
</div>
{# Set current form data back into page content #}
{% if current_form_data %}
{% do context.header(current_form_data.header) %}
{% do context.content(current_form_data.content) %}
{% endif %}
{% if context.blueprints.fields and admin.session.expert == '0' %}
{% include 'partials/blueprints.html.twig' with { blueprints: context.blueprints, data: context } %}
{% else %}
@@ -337,7 +346,7 @@
</form>
<div class="pages-list">
<ul class="depth-0">
{{ _self.loop(pages, 0, _context) }}
{{ macro.loop(pages, 0, _context) }}
</ul>
{% include 'partials/page-legend.html.twig' %}
</div>
@@ -1,13 +1,6 @@
<div class="pages-list-container clear block size-1-4">
<h5>{{ "PLUGIN_ADMIN.PAGES"|tu|e }}</h5>
<div class="mediapicker-scroll">
<ul class="pages-list depth-0">
{{ _self.loop(pages, 0, _context) }}
</ul>
</div>
</div>
{% macro loop(page, depth, twig_vars) %}
{% import _self as self %}
{% set separator = twig_vars['config'].system.param_sep %}
{% set base_url = twig_vars['base_url_relative'] %}
{% set base_url_simple = twig_vars['base_url_simple'] %}
@@ -37,10 +30,21 @@
</div>
{% if p.children().count > 0 %}
<ul class="depth-{{ depth + 1 }}" style="display:none;">
{{ _self.loop(p, depth + 1, twig_vars) }}
</ul>
<ul class="depth-{{ depth + 1 }}" style="display:none;">
{{ self.loop(p, depth + 1, twig_vars) }}
</ul>
{% endif %}
</li>
{% endfor %}
{% endmacro %}
{% endmacro %}
{% import _self as macro %}
<div class="pages-list-container clear block size-1-4">
<h5>{{ "PLUGIN_ADMIN.PAGES"|tu|e }}</h5>
<div class="mediapicker-scroll">
<ul class="pages-list depth-0">
{{ macro.loop(pages, 0, _context) }}
</ul>
</div>
</div>
File diff suppressed because it is too large Load Diff
+22 -1
View File
@@ -1,3 +1,24 @@
# v2.16.4
## 12/14/2018
1. [](#improved)
* Better handling of invalid file names during upload
* Better MIME type checking of files during file upload
* Do not rely on jQuery for merging languages from form fields [#290](https://github.com/getgrav/grav-plugin-form/issues/290) [#291](https://github.com/getgrav/grav-plugin-form/issues/291)
2. [](#bugfix)
* Remove jQuery dependency in form.html.twig (#290)
3. [](#new)
* Added Object.assign-polyfill (#291)
# v2.16.3
## 09/21/2018
1. [](#improved)
* Use `Url:post()` to get the `$_POST` variable (allows common security checks/filtering for the POST data)
* Various JS tweaks and enhancements
1. [](#bugfix)
* Fixed issue where `select` set up as `multiple` and with `selectize: create: true` would not properly merge newly created values on rendering.
# v2.16.2
## 08/23/2018
@@ -20,7 +41,7 @@
* Added new `form.keep_alive` option to keep session alive [#275](https://github.com/getgrav/grav-plugin-form/issues/275)
* Added `array` field for frontend use
1. [](#improved)
* Improving compatibility `autocomplete` spec [#274](https://github.com/getgrav/grav-plugin-form/pull/274)
* Improving compatibility `autocomplete` spec [#274](https://github.com/getgrav/grav-plugin-form/pull/274)
# v2.15.1
## 06/20/2018
+4 -2
View File
@@ -290,8 +290,10 @@ const addNode = (container) => {
};
export let Instances = (() => {
$('.dropzone.files-upload').each((i, container) => addNode(container));
$('body').on('mutation._grav', onAddedNodes);
$(document).ready(() => {
$('.dropzone.files-upload').each((i, container) => addNode(container));
$('body').on('mutation._grav', onAddedNodes);
});
return instances;
})();
+12 -11
View File
@@ -1,14 +1,15 @@
import $ from 'jquery';
import { config } from 'grav-form';
import {config} from 'grav-form';
const keepAlive = $('[data-grav-keepalive]');
if (keepAlive.length) {
const timeout = config.session_timeout / 1.5 * 1000;
setInterval(() => {
$.ajax({
url: `${config.base_url_relative}/task${config.param_sep}keep-alive`
});
}, timeout);
}
$(document).ready(() => {
const keepAlive = $('[data-grav-keepalive]');
if (keepAlive.length) {
const timeout = config.session_timeout / 1.5 * 1000;
setInterval(() => {
$.ajax({
url: `${config.base_url_relative}/task${config.param_sep}keep-alive`
});
}, timeout);
}
});
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1,29 @@
if (typeof Object.assign !== 'function') {
// Must be writable: true, enumerable: false, configurable: true
Object.defineProperty(Object, 'assign', {
value: function assign(target, varArgs) { // .length of function is 2
'use strict';
if (target == null) { // TypeError if undefined or null
throw new TypeError('Cannot convert undefined or null to object');
}
var to = Object(target);
for (var index = 1; index < arguments.length; index++) {
var nextSource = arguments[index];
if (nextSource != null) { // Skip over if undefined or null
for (var nextKey in nextSource) {
// Avoid bugs when hasOwnProperty is shadowed
if (Object.prototype.hasOwnProperty.call(nextSource, nextKey)) {
to[nextKey] = nextSource[nextKey];
}
}
}
}
return to;
},
writable: true,
configurable: true
});
}
+1 -1
View File
@@ -1,5 +1,5 @@
name: Form
version: 2.16.2
version: 2.16.4
description: Enables the forms handling
icon: check-square
author:
+45 -23
View File
@@ -384,11 +384,13 @@ class Form extends Iterator implements \Serializable
*/
public function uploadFiles()
{
$post = $_POST;
$grav = Grav::instance();
$uri = $grav['uri']->url;
$language = $grav['language'];
$config = $grav['config'];
$session = $grav['session'];
$uri = $grav['uri'];
$url = $uri->url;
$post = $uri->post();
$settings = $this->data->blueprints()->schema()->getProperty($post['name']);
$settings = (object) array_merge(
@@ -413,20 +415,26 @@ class Form extends Iterator implements \Serializable
// json_response
return [
'status' => 'error',
'message' => sprintf($grav['language']->translate('PLUGIN_FORM.FILEUPLOAD_UNABLE_TO_UPLOAD', null, true), $upload->file->name, $this->upload_errors[$upload->file->error])
'message' => sprintf($language->translate('PLUGIN_FORM.FILEUPLOAD_UNABLE_TO_UPLOAD', null, true), $upload->file->name, $this->upload_errors[$upload->file->error])
];
}
// Handle bad filenames.
$filename = $upload->file->name;
if (strtr($filename, "\t\n\r\0\x0b", '_____') !== $filename || rtrim($filename, ". ") !== $filename || preg_match('|\.php|', $filename)) {
$this->admin->json_response = [
// Handle bad filenames.
if (!Utils::checkFilename($filename)) {
return [
'status' => 'error',
'message' => sprintf($this->admin->translate('PLUGIN_ADMIN.FILEUPLOAD_UNABLE_TO_UPLOAD', null),
'message' => sprintf($language->translate('PLUGIN_FORM.FILEUPLOAD_UNABLE_TO_UPLOAD', null),
$filename, 'Bad filename')
];
}
return false;
if (!isset($settings->destination)) {
return [
'status' => 'error',
'message' => $language->translate('PLUGIN_FORM.DESTINATION_NOT_SPECIFIED', null)
];
}
// Remove the error object to avoid storing it
@@ -437,7 +445,11 @@ class Form extends Iterator implements \Serializable
// Accept can only be mime types (image/png | image/*) or file extensions (.pdf|.jpg)
$accepted = false;
$errors = [];
foreach ((array) $settings->accept as $type) {
// Do not trust mimetype sent by the browser
$mime = Utils::getMimeByFilename($upload->file->name);
foreach ((array)$settings->accept as $type) {
// Force acceptance of any file when star notation
if ($type === '*') {
$accepted = true;
@@ -445,15 +457,24 @@ class Form extends Iterator implements \Serializable
}
$isMime = strstr($type, '/');
$find = str_replace('*', '.*', $type);
$find = str_replace(['.', '*'], ['\.', '.*'], $type);
$match = preg_match('#'. $find .'$#', $isMime ? $upload->file->type : $upload->file->name);
if (!$match) {
$message = $isMime ? 'The MIME type "' . $upload->file->type . '"' : 'The File Extension';
$errors[] = $message . ' for the file "' . $upload->file->name . '" is not an accepted.';
$accepted |= false;
if ($isMime) {
$match = preg_match('#' . $find . '$#', $mime);
if (!$match) {
$errors[] = sprintf($language->translate('PLUGIN_FORM.INVALID_MIME_TYPE', null, true), $mime, $upload->file->name);
} else {
$accepted = true;
break;
}
} else {
$accepted |= true;
$match = preg_match('#' . $find . '$#', $upload->file->name);
if (!$match) {
$errors[] = sprintf($language->translate('PLUGIN_FORM.INVALID_FILE_EXTENSION', null, true), $upload->file->name);
} else {
$accepted = true;
break;
}
}
}
@@ -472,7 +493,7 @@ class Form extends Iterator implements \Serializable
// json_response
return [
'status' => 'error',
'message' => $grav['language']->translate('PLUGIN_FORM.EXCEEDED_GRAV_FILESIZE_LIMIT')
'message' => $language->translate('PLUGIN_FORM.EXCEEDED_GRAV_FILESIZE_LIMIT')
];
}
@@ -489,7 +510,7 @@ class Form extends Iterator implements \Serializable
// json_response
return [
'status' => 'error',
'message' => sprintf($grav['language']->translate('PLUGIN_FORM.FILEUPLOAD_UNABLE_TO_MOVE', null, true), '', $tmp)
'message' => sprintf($language->translate('PLUGIN_FORM.FILEUPLOAD_UNABLE_TO_MOVE', null, true), '', $tmp)
];
}
@@ -497,7 +518,7 @@ class Form extends Iterator implements \Serializable
// Retrieve the current session of the uploaded files for the field
// and initialize it if it doesn't exist
$sessionField = base64_encode($uri);
$sessionField = base64_encode($url);
$flash = $session->getFlashObject('files-upload');
if (!$flash) {
$flash = [];
@@ -547,7 +568,7 @@ class Form extends Iterator implements \Serializable
$json_response = [
'status' => 'success',
'session' => \json_encode([
'sessionField' => base64_encode($uri),
'sessionField' => base64_encode($url),
'path' => $upload->file->path,
'field' => $settings->name
])
@@ -567,8 +588,10 @@ class Form extends Iterator implements \Serializable
public function filesSessionRemove()
{
$grav = Grav::instance();
$post = $_POST;
$session = $grav['session'];
$uri = $grav['uri'];
$post = $uri->post();
// Retrieve the current session of the uploaded files for the field
// and initialize it if it doesn't exist
$sessionField = base64_encode($grav['uri']->url(true));
@@ -628,10 +651,9 @@ class Form extends Iterator implements \Serializable
public function post()
{
$grav = Grav::instance();
$session = $grav['session'];
$uri = $grav['uri'];
$url = $uri->url;
$session = $grav['session'];
$post = $uri->post();
if ($post) {
+3 -2
View File
@@ -193,9 +193,10 @@ class FormPlugin extends Plugin
// Post the form
if ($this->form) {
if ($uri->post('__form-file-uploader__') && $uri->extension() === 'json') {
$isJson = $uri->extension() === 'json';
if ($isJson && $uri->post('__form-file-uploader__')) {
$this->json_response = $this->form->uploadFiles();
} else if ($this->form && isset($_POST['__form-file-remover__']) && $this->grav['uri']->extension() === 'json') {
} elseif ($isJson && $uri->post('__form-file-remover__')) {
$this->json_response = $this->form->filesSessionRemove();
} else {
$this->form->post();
+165 -1
View File
@@ -53,11 +53,15 @@ en:
INLINE_ERRORS_HELP: "Useful with 'client-side validation' off, shows inline contextual form errors"
RESOLUTION_MIN: "The {{attr}} was less than the minimum of {{min}}px <br />"
RESOLUTION_MAX: "The {{attr}} was more than the maximum of {{max}}px <br />"
DESTINATION_NOT_SPECIFIED: "Destination not specified"
INVALID_MIME_TYPE: "The MIME type %s for the file %s is not an accepted."
INVALID_FILE_EXTENSION: "The File Extension for the file %s is not an accepted."
de:
PLUGIN_FORM:
NOT_VALIDATED: "Formularwerte nicht gültig. Für ein oder mehrere erforderliche Felder fehlen Werte."
NONCE_NOT_VALIDATED: "Ups, es gibt da ein Problem. Eingabewerte bitte noch mal prüfen und das Formular erneut absenden."
FILES: "Dateien hochladen"
FORM_ALREADY_SUBMITTED: "Formular wurde bereits versendet."
ALLOW_MULTIPLE: "Erlaube mehr als eine Datei"
ALLOW_MULTIPLE_HELP: "Erlaubt es, mehr als eine Datei zum Hochladen auszuwählen."
DESTINATION: "Ziel"
@@ -68,6 +72,40 @@ de:
DATA_SUMMARY: "Hier die Zusammenfassung von dem, was Sie geschrieben haben:"
YES: "Ja"
NO: "Nein"
NO_FORM_DATA: "Keine Formulardaten vorhanden"
RECAPTCHA: "ReCaptcha"
RECAPTCHA_SITE_KEY: "Seiten Schlüssel"
RECAPTCHA_SITE_KEY_HELP: "Für mehr Informationen besuche https://developers.google.com/recaptcha"
RECAPTCHA_SECRET_KEY: "Privater Schlüssel"
RECAPTCHA_SECRET_KEY_HELP: "Für mehr Informationen besuche https://developers.google.com/recaptcha"
GENERAL: "General"
USE_BUILT_IN_CSS: "Benutze built-in CSS"
USE_INLINE_CSS: "Benutze inline CSS"
FILEUPLOAD_PREVENT_SELF: '"%s" kann nicht ausserhalb der Seiten verwendet werden.'
FILEUPLOAD_UNABLE_TO_UPLOAD: 'Upload Fehler %s: %s'
FILEUPLOAD_UNABLE_TO_MOVE: 'Fehler beim verschieben von %s nach "%s"'
DROPZONE_CANCEL_UPLOAD: 'Upload abgebrochen'
DROPZONE_CANCEL_UPLOAD_CONFIRMATION: 'Bist du sicher das Du diesen Upload abbrechen möchtest??'
DROPZONE_DEFAULT_MESSAGE: 'Ziehe deine Dateien hierhin <strong>Klicke hier</strong>'
DROPZONE_FALLBACK_MESSAGE: 'Dein Browser unterstützt kein "drag and drop".'
DROPZONE_FILE_TOO_BIG: 'Datei zu gross ({{filesize}}MiB). Max Dateigrösse: {{maxFilesize}}MiB.'
DROPZONE_INVALID_FILE_TYPE: "Du kannst keine Dateien dieses Typs hochladen."
DROPZONE_MAX_FILES_EXCEEDED: "DU kannst keine weiteren Dateien hochladen."
DROPZONE_REMOVE_FILE: "Datei entfernen"
DROPZONE_REMOVE_FILE_CONFIRMATION: 'Möchtest du diese Datei wirklich löschen?'
DROPZONE_RESPONSE_ERROR: "Server antwortet mit {{statusCode}}."
LIMIT_HELP: "Maximale anzahl erlaubter Dateien (Mehrfachauswahl erforderlich) "
FILESIZE: "Max Dateigrösse"
FILESIZE_HELP: "Maximale Dateigrösse (in MB), 0 = Benutze Standart"
AVOID_OVERWRITING: "Überschreibungen verhindern"
AVOID_OVERWRITING_HELP: "Verhindert überschreibungen und fügt ein Prefix an."
RANDOM_NAME: "Zufälliger Name"
RANDOM_NAME_HELP: "Generiert einen 15 Zeichen langen Zufalls Name."
CLIENT_SIDE_VALIDATION: "Klientseitige Überprüfung"
INLINE_ERRORS: "Inline errors"
DESTINATION_NOT_SPECIFIED: "Zielort nicht definiert"
INVALID_MIME_TYPE: "Der MIME-Type %s für die Datei %s ist nicht erlaubt."
INVALID_FILE_EXTENSION: "Die Dateiendung %s ist nicht erlaubt."
es:
PLUGIN_FORM:
@@ -176,13 +214,15 @@ ru:
ERROR_VALIDATING_CAPTCHA: "Ошибка проверки Captcha"
DATA_SUMMARY: "Вот краткое изложение того, что вы нам написали:"
NO_FORM_DATA: "Данные формы отсутствуют"
RECAPTCHA: "ReCaptcha"
RECAPTCHA: "reCAPTCHA"
RECPATCHA_VERSION: "Версия"
RECAPTCHA_SITE_KEY: "Site key"
RECAPTCHA_SITE_KEY_HELP: "Для получения дополнительной информации посетите https://developers.google.com/recaptcha"
RECAPTCHA_SECRET_KEY: "Secret key"
RECAPTCHA_SECRET_KEY_HELP: "Для получения дополнительной информации посетите https://developers.google.com/recaptcha"
GENERAL: "Общие"
USE_BUILT_IN_CSS: "Использовать встроенный CSS"
USE_INLINE_CSS: "Использовать inline CSS"
FILEUPLOAD_PREVENT_SELF: 'Нельзя использовать "%s" за пределами страниц.'
FILEUPLOAD_UNABLE_TO_UPLOAD: 'Не удалось загрузить файл %s: %s'
FILEUPLOAD_UNABLE_TO_MOVE: 'Не удалось переместить файл %s в "%s"'
@@ -209,6 +249,75 @@ ru:
AVOID_OVERWRITING_HELP: "Не допускать перезаписи файлов с тем же именем. Будет добавлен префикс с датой"
RANDOM_NAME: "Случайное имя"
RANDOM_NAME_HELP: "Создавать случайное строковое имя из 15 символов для загруженных файлов"
CLIENT_SIDE_VALIDATION: "Проверка на стороне клиента"
CLIENT_SIDE_VALIDATION_HELP: "По умолчанию формы будут использовать HTML5 проверку клиента как первую степень защиты"
INLINE_ERRORS: "Встроенные ошибки"
INLINE_ERRORS_HELP: "Полезно с отключенной «проверкой на стороне клиента», отображает встроенные контекстные ошибки формы"
RESOLUTION_MIN: "{{attr}} меньше минимума {{min}}px <br />"
RESOLUTION_MAX: "The {{attr}} больше, чем максимум {{max}}px <br />"
DESTINATION_NOT_SPECIFIED: "Назначение не указано"
INVALID_MIME_TYPE: "Тип MIME %s для файла %s не принимается."
INVALID_FILE_EXTENSION: "Расширение файла %s не является допустимым для файла."
uk:
PLUGIN_FORM:
NOT_VALIDATED: "Форма не підтверджена. Відсутнє одне або кілька обов'язкових полів."
NONCE_NOT_VALIDATED: "Упс, у вас виникла проблема, перевірте свої дані і відправте форму ще раз."
FILES: "Завантаження файлів"
FORM_ALREADY_SUBMITTED: "Ця форма вже була відправлена."
ALLOW_MULTIPLE: "Дозволити декілька файлів"
ALLOW_MULTIPLE_HELP: "Дозволяє вибрати більше одного файлу для завантаження."
DESTINATION: "Місце призначення"
DESTINATION_HELP: "Місце, куди файли повинні бути завантажені в"
ACCEPT: "Дозволені MIME типи"
ACCEPT_HELP: "Перелік MIME типів, дозволених для завантаження"
ERROR_VALIDATING_CAPTCHA: "Помилка перевірки Captcha"
DATA_SUMMARY: "Ось стислий виклад того, що ви нам написали:"
NO_FORM_DATA: "Дані форми відсутні"
RECAPTCHA: "reCAPTCHA"
RECPATCHA_VERSION: "Версія"
RECAPTCHA_SITE_KEY: "Site key"
RECAPTCHA_SITE_KEY_HELP: "Для отримання додаткової інформації відвідайте https://developers.google.com/recaptcha"
RECAPTCHA_SECRET_KEY: "Secret key"
RECAPTCHA_SECRET_KEY_HELP: "Для отримання додаткової інформації відвідайте https://developers.google.com/recaptcha"
GENERAL: "Загальні"
USE_BUILT_IN_CSS: "Використовувати вбудований CSS"
USE_INLINE_CSS: "Використовувати inline CSS"
FILEUPLOAD_PREVENT_SELF: 'Не можна використовувати "%s" за межами сторінок.'
FILEUPLOAD_UNABLE_TO_UPLOAD: 'Не вдалося завантажити файл %s: %s'
FILEUPLOAD_UNABLE_TO_MOVE: 'Не вдалося перемістити файл %s до "%s"'
DROPZONE_CANCEL_UPLOAD: 'Скасувати завантаження'
DROPZONE_CANCEL_UPLOAD_CONFIRMATION: 'Ви дійсно хочете скасувати це завантаження?'
DROPZONE_DEFAULT_MESSAGE: 'Перетягніть свої файли сюди чи <strong>клацніть в цій області</strong>'
DROPZONE_FALLBACK_MESSAGE: 'Ваш браузер не підтримує завантаження файлів з перетягуванням.'
DROPZONE_FALLBACK_TEXT: 'Будь ласка, використовуйте цю форму для завантаження ваших файлів, як в старі часи.'
DROPZONE_FILE_TOO_BIG: 'Файл занадто великий ({{filesize}}мб). Максимальний розмір файлу: {{maxFilesize}}мб.'
DROPZONE_INVALID_FILE_TYPE: "Ви не можете завантажувати файли цього типу."
DROPZONE_MAX_FILES_EXCEEDED: "Ви не можете завантажувати більше файлів."
DROPZONE_REMOVE_FILE: "Видалити файл"
DROPZONE_REMOVE_FILE_CONFIRMATION: 'Ви дійсно хочете видалити цей файл?'
DROPZONE_RESPONSE_ERROR: "Сервер відповів кодом {{statusCode}}."
YES: "Так"
NO: "Ні"
REFRESH_PREVENTION: "Запобігання оновлення"
REFRESH_PREVENTION_HELP: "Використовуйте унікальний ідентифікатор форми, щоб гарантувати, що одна і та ж форма не буде відправлятися заново при оновленні сторінки браузера"
LIMIT: "Ліміт"
LIMIT_HELP: "Максимальна кількість дозволених файлів в поле (потрібно декілька)"
FILESIZE: "Максимальний розмір файлу"
FILESIZE_HELP: "Максимально допустимий розмір файлу (в МБ), 0 = Використовувати системні налаштування за замовчуванням"
AVOID_OVERWRITING: "Уникати перезапису"
AVOID_OVERWRITING_HELP: "Не допускати перезапису файлів з тим же ім'ям. Буде додано префікс з датою"
RANDOM_NAME: "Випадкове ім'я"
RANDOM_NAME_HELP: "Створювати випадкове строкове ім'я з 15 символів для завантажених файлів"
CLIENT_SIDE_VALIDATION: "Перевірка на стороні клієнта"
CLIENT_SIDE_VALIDATION_HELP: "За замовчуванням форми будуть використовувати HTML5 перевірку клієнта як першу ступінь захисту"
INLINE_ERRORS: "Вбудовані помилки"
INLINE_ERRORS_HELP: "Корисно з відключеною «перевіркою на стороні клієнта», відображає вбудовані контекстні помилки форми"
RESOLUTION_MIN: "{{attr}} менше, ніж мінімум {{min}}px <br />"
RESOLUTION_MAX: "The {{attr}} більше, ніж максимум {{max}}px <br />"
DESTINATION_NOT_SPECIFIED: "Призначення не вказано"
INVALID_MIME_TYPE: "Тип MIME %s для файла %s не приймається."
INVALID_FILE_EXTENSION: "Розширення файлу %s не є допустимим для файлу."
hr:
PLUGIN_FORM:
@@ -419,3 +528,58 @@ nl:
AVOID_OVERWRITING_HELP: "Voorkom dat bestanden met dezelfde naam worden overschreven. Datum prefix wordt toegevoegd"
RANDOM_NAME: "Willekeurige naam"
RANDOM_NAME_HELP: "Genereer een willekeurige naam van 15 karakters voor de geuploade bestanden"
pt:
PLUGIN_FORM:
NOT_VALIDATED: "Formulário não validado. Falta preencher um ou mais campos obrigatórios."
NONCE_NOT_VALIDATED: "Oops, houve um problema. Por favor verifique os dados informados e envie o formulário novamente."
FILES: "Envio de Arquivos"
FORM_ALREADY_SUBMITTED: "Este formulário já foi enviado."
ALLOW_MULTIPLE: "Permitir mais de um arquivo"
ALLOW_MULTIPLE_HELP: "Permite selecionar mais de um arquivo para enviar."
DESTINATION: "Destino"
DESTINATION_HELP: "O local para onde os arquivos devem ser enviados"
ACCEPT: "Tipos MIME Permitidos"
ACCEPT_HELP: "Uma lista de tipos MIME permitidos no upload"
ERROR_VALIDATING_CAPTCHA: "Erro ao validar o captcha"
DATA_SUMMARY: "Aqui está um resumo do que você escreveu:"
NO_FORM_DATA: "Nenhum dado de formulário disponível"
RECAPTCHA: "ReCaptcha"
RECAPTCHA_SITE_KEY: "Site key"
RECAPTCHA_SITE_KEY_HELP: "Para mais informações visite https://developers.google.com/recaptcha"
RECAPTCHA_SECRET_KEY: "Secret key"
RECAPTCHA_SECRET_KEY_HELP: "Para mais informações visite https://developers.google.com/recaptcha"
GENERAL: "Geral"
USE_BUILT_IN_CSS: "Usar CSS built-in"
USE_INLINE_CSS: "Usar CSS inline"
FILEUPLOAD_PREVENT_SELF: 'Não é permitido usar "%s" fora de páginas.'
FILEUPLOAD_UNABLE_TO_UPLOAD: 'Não foi possível enviar o arquivo %s: %s'
FILEUPLOAD_UNABLE_TO_MOVE: 'Não foi possível mover o arquivo %s para "%s"'
DROPZONE_CANCEL_UPLOAD: 'Cancelar envio'
DROPZONE_CANCEL_UPLOAD_CONFIRMATION: 'Tem certeza de que deseja cancelar este envio?'
DROPZONE_DEFAULT_MESSAGE: 'Arraste seus arquivos para cá ou <strong>clique nesta área</strong>'
DROPZONE_FALLBACK_MESSAGE: 'Seu navegador não suporta arrastar e soltar arquivos.'
DROPZONE_FALLBACK_TEXT: 'Por favor use o formulário abaixo para enviar seus arquivos como antigamente.'
DROPZONE_FILE_TOO_BIG: 'O arquivo é muito grande ({{filesize}}MiB). Tamanho máximo: {{maxFilesize}}MiB.'
DROPZONE_INVALID_FILE_TYPE: "Você não pode enviar arquivos deste tipo."
DROPZONE_MAX_FILES_EXCEEDED: "Você não pode enviar mais arquivos."
DROPZONE_REMOVE_FILE: "Remover arquivo"
DROPZONE_REMOVE_FILE_CONFIRMATION: 'Tem certeza de que deseja excluir este arquivo?'
DROPZONE_RESPONSE_ERROR: "O servidor respondeu com código {{statusCode}}."
YES: "Sim"
NO: "Não"
REFRESH_PREVENTION: "Evitar recarregamento"
REFRESH_PREVENTION_HELP: "Usar o ID do formulário para garantir que ele não seja reprocessado se o usuário recarregar a página"
LIMIT: "Limite"
LIMIT_HELP: "Número máximo de arquivos permitidos por campo (deve ser múltiplo)"
FILESIZE: "Tamanho máximo"
FILESIZE_HELP: "Tamanho máximo de arquivo permitido (em MB), 0 = Use o default do sistema"
AVOID_OVERWRITING: "Evitar sobrescrita"
AVOID_OVERWRITING_HELP: "Prevenir que arquivos com o mesmo nome sejam sobrescritos. Serão adicionados prefixos com a data"
RANDOM_NAME: "Nome aleatório"
RANDOM_NAME_HELP: "Gerar um nome aleatório de 15 caracteres para os arquivos recebidos"
CLIENT_SIDE_VALIDATION: "Validação no cliente"
CLIENT_SIDE_VALIDATION_HELP: "Por padrão os formulários usarão a validação HTML5 no cliente como primeira linha de defesa"
INLINE_ERRORS: "Erros inline"
INLINE_ERRORS_HELP: "Útil se a validação no cliente estiver desabilitada. Mostra os erros próximo aos respectivos campos"
RESOLUTION_MIN: "A {{attr}} é menor que o mínimo de {{min}}px <br />"
RESOLUTION_MAX: "A {{attr}} é maior que o máximo de {{max}}px <br />"
+11 -8
View File
@@ -7,12 +7,12 @@
"scripts": {
"watch": "NODE_ENV=development webpack --watch --progress --colors --mode development --config webpack.conf.js",
"dev": "NODE_ENV=development webpack --progress --colors --config webpack.conf.js",
"prod": "NODE_ENV=production webpack --mode production --config webpack.conf.js"
"prod": "NODE_ENV=production-wip webpack --mode development --config webpack.conf.js"
},
"author": "Team Grav",
"license": "MIT",
"dependencies": {
"dropzone": "^5.3.0",
"dropzone": "^5.5.1",
"exif-js": "^2.3.0",
"sortablejs": "^1.7.0"
},
@@ -24,20 +24,23 @@
"babel-preset-es2015": "^6.24.1",
"babel-preset-stage-3": "^6.24.1",
"css-loader": "^1.0.0",
"eslint": "^5.2.0",
"eslint-loader": "^2.1.0",
"eslint": "^5.6.0",
"eslint-loader": "^2.1.1",
"exports-loader": "^0.7.0",
"gulp": "latest",
"gulp-autoprefixer": "latest",
"gulp-clean-css": "^3.9.4",
"gulp-clean-css": "^3.10.0",
"gulp-csscomb": "^3.0.8",
"gulp-rename": "^1.3.0",
"gulp-rename": "^1.4.0",
"gulp-sass": "^4.0.1",
"gulp-sourcemaps": "^2.6.4",
"gulp-webpack": "^1.5.0",
"immutable": "^4.0.0-rc.12",
"imports-loader": "^0.8.0",
"json-loader": "^0.5.7",
"style-loader": "^0.21.0",
"webpack": "^4.16.2",
"style-loader": "^0.23.0",
"uglifyjs-webpack-plugin": "^2.0.1",
"webpack": "^4.19.1",
"webpack-cli": "^3.1.0"
}
}
@@ -23,21 +23,25 @@
{% endif %}
{% if form.keep_alive %}
{% do assets.addJs('plugin://form/assets/form.vendor.js', { 'loading': 'defer', 'group': 'bottom' }) %}
{% do assets.addJs('plugin://form/assets/form.min.js', { 'loading': 'defer', 'group': 'bottom' }) %}
{% if grav.browser.browser == 'msie' and grav.browser.version < 12 %}
{% do assets.addJs('plugin://form/assets/object.assign.polyfill.js') %}
{% endif %}
{% do assets.addJs('plugin://form/assets/form.vendor.js', { 'group': 'bottom', 'loading': 'defer' }) %}
{% do assets.addJs('plugin://form/assets/form.min.js', { 'group': 'bottom', 'loading': 'defer' }) %}
{% endif %}
{% do assets.addInlineJs("
window.GravForm = window.GravForm || {};
window.GravForm.config = {
current_url: '" ~ uri.route(true) ~"',
base_url_relative: '" ~ base_url_relative ~ "',
param_sep: '"~ config.system.param_sep ~ "',
form_nonce: '" ~ form.getNonce ~ "',
session_timeout: " ~ config.system.session.timeout ~ "
};
window.GravForm.translations = jQuery.extend({}, window.GravForm.translations || {}, { PLUGIN_FORM: {} });
", { 'group': 'bottom' }) %}
{% do assets.addInlineJs("
window.GravForm = window.GravForm || {};
window.GravForm.config = {
current_url: '" ~ uri.route(true) ~"',
base_url_relative: '" ~ base_url_relative ~ "',
param_sep: '"~ config.system.param_sep ~ "',
form_nonce: '" ~ form.getNonce ~ "',
session_timeout: " ~ config.system.session.timeout ~ "
};
window.GravForm.translations = Object.assign({}, window.GravForm.translations || {}, { PLUGIN_FORM: {} });
", {'group': 'bottom'}) %}
<form name="{{ form.name }}"
action="{{ action | trim('/', 'right') }}"
method="{{ method }}"{{ multipart }}
@@ -1,7 +1,7 @@
{% extends "forms/field.html.twig" %}
{% do assets.addJs('plugin://form/assets/form.vendor.js', { 'loading': 'defer', 'group': 'bottom' }) %}
{% do assets.addJs('plugin://form/assets/form.min.js', { 'loading': 'defer', 'group': 'bottom' }) %}
{% do assets.addJs('plugin://form/assets/form.vendor.js', { 'group': 'bottom', 'loading': 'defer' }) %}
{% do assets.addJs('plugin://form/assets/form.min.js', { 'group': 'bottom', 'loading': 'defer' }) %}
{% block global_attributes %}
data-grav-array-name="{{ (scope ~ field.name)|fieldName }}"
@@ -80,14 +80,17 @@
{% include 'forms/fields/hidden/hidden.html.twig' with {field: {name: '_json.' ~ field.name}, value:value|json_encode|e('html_attr')} %}
</div>
{% if grav.browser.browser == 'msie' and grav.browser.version < 12 %}
{% do assets.addJs('plugin://form/assets/object.assign.polyfill.js') %}
{% endif %}
{% do assets.addJs('jquery', 101) %}
{% do assets.addJs('plugin://form/assets/form.vendor.js', { 'loading': 'defer', 'group': 'bottom' }) %}
{% do assets.addJs('plugin://form/assets/form.min.js', { 'loading': 'defer', 'group': 'bottom' }) %}
{% do assets.addJs('plugin://form/assets/form.vendor.js', { 'group': 'bottom', 'loading': 'defer' }) %}
{% do assets.addJs('plugin://form/assets/form.min.js', { 'group': 'bottom', 'loading': 'defer' }) %}
{% do assets.addCss('plugin://form/assets/dropzone.min.css', { 'group': 'form'}) %}
{{ assets.css('form')|raw }}
{% do assets.addInlineJs("
window.GravForm = window.GravForm || {};
window.GravForm = jQuery.extend({}, window.GravForm, {
window.GravForm = Object.assign({}, window.GravForm, {
translations: {
PLUGIN_FORM: {
'DROPZONE_CANCEL_UPLOAD': " ~ 'PLUGIN_FORM.DROPZONE_CANCEL_UPLOAD'|t|json_encode ~ ",
@@ -106,5 +109,5 @@
}
}
});
", { 'group': 'bottom' }) %}
", {'group': 'bottom'}) %}
{% endblock %}
@@ -24,8 +24,20 @@
>
{% if field.placeholder %}<option value="" disabled selected>{% if grav.twig.twig.filters['tu'] is defined %}{{ field.placeholder|tu|raw }}{% else %}{{ field.placeholder|t|raw }}{% endif %}</option>{% endif %}
{% for key, item_value in field.options %}
{% if item_value is iterable %}
{% set options = field.options %}
{% if field.selectize.create and field.multiple %}
{% set options = options|merge(value|default([]))|array_unique %}
{% endif %}
{% for key, item_value in options %}
{% if item_value is iterable and item_value.value %}
{% set akey = field.selectize and field.multiple ? item_value : key %}
{% set avalue = grav.twig.twig.filters['tu'] is defined ? item_value.value|tu : item_value.value|t %}
<option {{ item_value.disabled ? 'disabled="disabled"' : '' }}
{{ item_value.selected ? 'selected="selected"' : '' }}
{{ item_value.label ? 'label=' ~ item_value.label : '' }}
value="{{ item_value.akey }}">{{ avalue|raw }}</option>
{% elseif item_value is iterable %}
<optgroup label="{{ key }}">
{%for subkey, suboption in item_value %}
{% set selected = field.selectize ? suboption : subkey %}
@@ -14,6 +14,9 @@
{% block input_attributes %}
type="text"
{% if field.size %}size="{{ field.size }}"{% endif %}
{% if field.minlength is defined or field.validate.min is defined %}minlength="{{ field.minlength | default(field.validate.min) }}"{% endif %}
{% if field.maxlength is defined or field.validate.max is defined %}maxlength="{{ field.maxlength | default(field.validate.max) }}"{% endif %}
{{ parent() }}
{% endblock %}
@@ -34,4 +37,4 @@
{% set field = field|merge({'wrapper_classes': 'form-input-addon-wrapper'}) %}
{% endif %}
{{ parent() }}
{% endblock %}
{% endblock %}
+1 -1
View File
@@ -1,7 +1,7 @@
var webpack = require('webpack'),
path = require('path'),
UglifyJsPlugin = require('uglifyjs-webpack-plugin'),
isProd = process.env.NODE_ENV === 'production';
isProd = process.env.NODE_ENV === 'production' || process.env.NODE_ENV === 'production-wip';
module.exports = {
entry: {
+201 -266
View File
@@ -8,6 +8,12 @@
dependencies:
"@babel/highlight" "7.0.0-beta.44"
"@babel/code-frame@^7.0.0":
version "7.0.0"
resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.0.0.tgz#06e2ab19bdb535385559aabb5ba59729482800f8"
dependencies:
"@babel/highlight" "^7.0.0"
"@babel/generator@7.0.0-beta.44":
version "7.0.0-beta.44"
resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.0.0-beta.44.tgz#c7e67b9b5284afcf69b309b50d7d37f3e5033d42"
@@ -46,6 +52,14 @@
esutils "^2.0.2"
js-tokens "^3.0.0"
"@babel/highlight@^7.0.0":
version "7.0.0"
resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.0.0.tgz#f710c38c8d458e6dd9a201afb637fcb781ce99e4"
dependencies:
chalk "^2.0.0"
esutils "^2.0.2"
js-tokens "^4.0.0"
"@babel/template@7.0.0-beta.44":
version "7.0.0-beta.44"
resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.0.0-beta.44.tgz#f8832f4fdcee5d59bf515e595fc5106c529b394f"
@@ -95,141 +109,142 @@
normalize-path "^2.0.1"
through2 "^2.0.3"
"@webassemblyjs/ast@1.5.13":
version "1.5.13"
resolved "https://registry.yarnpkg.com/@webassemblyjs/ast/-/ast-1.5.13.tgz#81155a570bd5803a30ec31436bc2c9c0ede38f25"
"@webassemblyjs/ast@1.7.6":
version "1.7.6"
resolved "https://registry.yarnpkg.com/@webassemblyjs/ast/-/ast-1.7.6.tgz#3ef8c45b3e5e943a153a05281317474fef63e21e"
dependencies:
"@webassemblyjs/helper-module-context" "1.5.13"
"@webassemblyjs/helper-wasm-bytecode" "1.5.13"
"@webassemblyjs/wast-parser" "1.5.13"
debug "^3.1.0"
"@webassemblyjs/helper-module-context" "1.7.6"
"@webassemblyjs/helper-wasm-bytecode" "1.7.6"
"@webassemblyjs/wast-parser" "1.7.6"
mamacro "^0.0.3"
"@webassemblyjs/floating-point-hex-parser@1.5.13":
version "1.5.13"
resolved "https://registry.yarnpkg.com/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.5.13.tgz#29ce0baa97411f70e8cce68ce9c0f9d819a4e298"
"@webassemblyjs/floating-point-hex-parser@1.7.6":
version "1.7.6"
resolved "https://registry.yarnpkg.com/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.7.6.tgz#7cb37d51a05c3fe09b464ae7e711d1ab3837801f"
"@webassemblyjs/helper-api-error@1.5.13":
version "1.5.13"
resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-api-error/-/helper-api-error-1.5.13.tgz#e49b051d67ee19a56e29b9aa8bd949b5b4442a59"
"@webassemblyjs/helper-api-error@1.7.6":
version "1.7.6"
resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-api-error/-/helper-api-error-1.7.6.tgz#99b7e30e66f550a2638299a109dda84a622070ef"
"@webassemblyjs/helper-buffer@1.5.13":
version "1.5.13"
resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-buffer/-/helper-buffer-1.5.13.tgz#873bb0a1b46449231137c1262ddfd05695195a1e"
"@webassemblyjs/helper-buffer@1.7.6":
version "1.7.6"
resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-buffer/-/helper-buffer-1.7.6.tgz#ba0648be12bbe560c25c997e175c2018df39ca3e"
"@webassemblyjs/helper-code-frame@1.7.6":
version "1.7.6"
resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-code-frame/-/helper-code-frame-1.7.6.tgz#5a94d21b0057b69a7403fca0c253c3aaca95b1a5"
dependencies:
debug "^3.1.0"
"@webassemblyjs/wast-printer" "1.7.6"
"@webassemblyjs/helper-code-frame@1.5.13":
version "1.5.13"
resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-code-frame/-/helper-code-frame-1.5.13.tgz#1bd2181b6a0be14e004f0fe9f5a660d265362b58"
"@webassemblyjs/helper-fsm@1.7.6":
version "1.7.6"
resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-fsm/-/helper-fsm-1.7.6.tgz#ae1741c6f6121213c7a0b587fb964fac492d3e49"
"@webassemblyjs/helper-module-context@1.7.6":
version "1.7.6"
resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-module-context/-/helper-module-context-1.7.6.tgz#116d19a51a6cebc8900ad53ca34ff8269c668c23"
dependencies:
"@webassemblyjs/wast-printer" "1.5.13"
"@webassemblyjs/helper-fsm@1.5.13":
version "1.5.13"
resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-fsm/-/helper-fsm-1.5.13.tgz#cdf3d9d33005d543a5c5e5adaabf679ffa8db924"
"@webassemblyjs/helper-module-context@1.5.13":
version "1.5.13"
resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-module-context/-/helper-module-context-1.5.13.tgz#dc29ddfb51ed657655286f94a5d72d8a489147c5"
dependencies:
debug "^3.1.0"
mamacro "^0.0.3"
"@webassemblyjs/helper-wasm-bytecode@1.5.13":
version "1.5.13"
resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.5.13.tgz#03245817f0a762382e61733146f5773def15a747"
"@webassemblyjs/helper-wasm-bytecode@1.7.6":
version "1.7.6"
resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.7.6.tgz#98e515eaee611aa6834eb5f6a7f8f5b29fefb6f1"
"@webassemblyjs/helper-wasm-section@1.5.13":
version "1.5.13"
resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.5.13.tgz#efc76f44a10d3073b584b43c38a179df173d5c7d"
"@webassemblyjs/helper-wasm-section@1.7.6":
version "1.7.6"
resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.7.6.tgz#783835867bdd686df7a95377ab64f51a275e8333"
dependencies:
"@webassemblyjs/ast" "1.5.13"
"@webassemblyjs/helper-buffer" "1.5.13"
"@webassemblyjs/helper-wasm-bytecode" "1.5.13"
"@webassemblyjs/wasm-gen" "1.5.13"
debug "^3.1.0"
"@webassemblyjs/ast" "1.7.6"
"@webassemblyjs/helper-buffer" "1.7.6"
"@webassemblyjs/helper-wasm-bytecode" "1.7.6"
"@webassemblyjs/wasm-gen" "1.7.6"
"@webassemblyjs/ieee754@1.5.13":
version "1.5.13"
resolved "https://registry.yarnpkg.com/@webassemblyjs/ieee754/-/ieee754-1.5.13.tgz#573e97c8c12e4eebb316ca5fde0203ddd90b0364"
"@webassemblyjs/ieee754@1.7.6":
version "1.7.6"
resolved "https://registry.yarnpkg.com/@webassemblyjs/ieee754/-/ieee754-1.7.6.tgz#c34fc058f2f831fae0632a8bb9803cf2d3462eb1"
dependencies:
ieee754 "^1.1.11"
"@xtuc/ieee754" "^1.2.0"
"@webassemblyjs/leb128@1.5.13":
version "1.5.13"
resolved "https://registry.yarnpkg.com/@webassemblyjs/leb128/-/leb128-1.5.13.tgz#ab52ebab9cec283c1c1897ac1da833a04a3f4cee"
"@webassemblyjs/leb128@1.7.6":
version "1.7.6"
resolved "https://registry.yarnpkg.com/@webassemblyjs/leb128/-/leb128-1.7.6.tgz#197f75376a29f6ed6ace15898a310d871d92f03b"
dependencies:
long "4.0.0"
"@xtuc/long" "4.2.1"
"@webassemblyjs/utf8@1.5.13":
version "1.5.13"
resolved "https://registry.yarnpkg.com/@webassemblyjs/utf8/-/utf8-1.5.13.tgz#6b53d2cd861cf94fa99c1f12779dde692fbc2469"
"@webassemblyjs/utf8@1.7.6":
version "1.7.6"
resolved "https://registry.yarnpkg.com/@webassemblyjs/utf8/-/utf8-1.7.6.tgz#eb62c66f906af2be70de0302e29055d25188797d"
"@webassemblyjs/wasm-edit@1.5.13":
version "1.5.13"
resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-edit/-/wasm-edit-1.5.13.tgz#c9cef5664c245cf11b3b3a73110c9155831724a8"
"@webassemblyjs/wasm-edit@1.7.6":
version "1.7.6"
resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-edit/-/wasm-edit-1.7.6.tgz#fa41929160cd7d676d4c28ecef420eed5b3733c5"
dependencies:
"@webassemblyjs/ast" "1.5.13"
"@webassemblyjs/helper-buffer" "1.5.13"
"@webassemblyjs/helper-wasm-bytecode" "1.5.13"
"@webassemblyjs/helper-wasm-section" "1.5.13"
"@webassemblyjs/wasm-gen" "1.5.13"
"@webassemblyjs/wasm-opt" "1.5.13"
"@webassemblyjs/wasm-parser" "1.5.13"
"@webassemblyjs/wast-printer" "1.5.13"
debug "^3.1.0"
"@webassemblyjs/ast" "1.7.6"
"@webassemblyjs/helper-buffer" "1.7.6"
"@webassemblyjs/helper-wasm-bytecode" "1.7.6"
"@webassemblyjs/helper-wasm-section" "1.7.6"
"@webassemblyjs/wasm-gen" "1.7.6"
"@webassemblyjs/wasm-opt" "1.7.6"
"@webassemblyjs/wasm-parser" "1.7.6"
"@webassemblyjs/wast-printer" "1.7.6"
"@webassemblyjs/wasm-gen@1.5.13":
version "1.5.13"
resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-gen/-/wasm-gen-1.5.13.tgz#8e6ea113c4b432fa66540189e79b16d7a140700e"
"@webassemblyjs/wasm-gen@1.7.6":
version "1.7.6"
resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-gen/-/wasm-gen-1.7.6.tgz#695ac38861ab3d72bf763c8c75e5f087ffabc322"
dependencies:
"@webassemblyjs/ast" "1.5.13"
"@webassemblyjs/helper-wasm-bytecode" "1.5.13"
"@webassemblyjs/ieee754" "1.5.13"
"@webassemblyjs/leb128" "1.5.13"
"@webassemblyjs/utf8" "1.5.13"
"@webassemblyjs/ast" "1.7.6"
"@webassemblyjs/helper-wasm-bytecode" "1.7.6"
"@webassemblyjs/ieee754" "1.7.6"
"@webassemblyjs/leb128" "1.7.6"
"@webassemblyjs/utf8" "1.7.6"
"@webassemblyjs/wasm-opt@1.5.13":
version "1.5.13"
resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-opt/-/wasm-opt-1.5.13.tgz#147aad7717a7ee4211c36b21a5f4c30dddf33138"
"@webassemblyjs/wasm-opt@1.7.6":
version "1.7.6"
resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-opt/-/wasm-opt-1.7.6.tgz#fbafa78e27e1a75ab759a4b658ff3d50b4636c21"
dependencies:
"@webassemblyjs/ast" "1.5.13"
"@webassemblyjs/helper-buffer" "1.5.13"
"@webassemblyjs/wasm-gen" "1.5.13"
"@webassemblyjs/wasm-parser" "1.5.13"
debug "^3.1.0"
"@webassemblyjs/ast" "1.7.6"
"@webassemblyjs/helper-buffer" "1.7.6"
"@webassemblyjs/wasm-gen" "1.7.6"
"@webassemblyjs/wasm-parser" "1.7.6"
"@webassemblyjs/wasm-parser@1.5.13":
version "1.5.13"
resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-parser/-/wasm-parser-1.5.13.tgz#6f46516c5bb23904fbdf58009233c2dd8a54c72f"
"@webassemblyjs/wasm-parser@1.7.6":
version "1.7.6"
resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-parser/-/wasm-parser-1.7.6.tgz#84eafeeff405ad6f4c4b5777d6a28ae54eed51fe"
dependencies:
"@webassemblyjs/ast" "1.5.13"
"@webassemblyjs/helper-api-error" "1.5.13"
"@webassemblyjs/helper-wasm-bytecode" "1.5.13"
"@webassemblyjs/ieee754" "1.5.13"
"@webassemblyjs/leb128" "1.5.13"
"@webassemblyjs/utf8" "1.5.13"
"@webassemblyjs/ast" "1.7.6"
"@webassemblyjs/helper-api-error" "1.7.6"
"@webassemblyjs/helper-wasm-bytecode" "1.7.6"
"@webassemblyjs/ieee754" "1.7.6"
"@webassemblyjs/leb128" "1.7.6"
"@webassemblyjs/utf8" "1.7.6"
"@webassemblyjs/wast-parser@1.5.13":
version "1.5.13"
resolved "https://registry.yarnpkg.com/@webassemblyjs/wast-parser/-/wast-parser-1.5.13.tgz#5727a705d397ae6a3ae99d7f5460acf2ec646eea"
"@webassemblyjs/wast-parser@1.7.6":
version "1.7.6"
resolved "https://registry.yarnpkg.com/@webassemblyjs/wast-parser/-/wast-parser-1.7.6.tgz#ca4d20b1516e017c91981773bd7e819d6bd9c6a7"
dependencies:
"@webassemblyjs/ast" "1.5.13"
"@webassemblyjs/floating-point-hex-parser" "1.5.13"
"@webassemblyjs/helper-api-error" "1.5.13"
"@webassemblyjs/helper-code-frame" "1.5.13"
"@webassemblyjs/helper-fsm" "1.5.13"
long "^3.2.0"
"@webassemblyjs/ast" "1.7.6"
"@webassemblyjs/floating-point-hex-parser" "1.7.6"
"@webassemblyjs/helper-api-error" "1.7.6"
"@webassemblyjs/helper-code-frame" "1.7.6"
"@webassemblyjs/helper-fsm" "1.7.6"
"@xtuc/long" "4.2.1"
mamacro "^0.0.3"
"@webassemblyjs/wast-printer@1.5.13":
version "1.5.13"
resolved "https://registry.yarnpkg.com/@webassemblyjs/wast-printer/-/wast-printer-1.5.13.tgz#bb34d528c14b4f579e7ec11e793ec50ad7cd7c95"
"@webassemblyjs/wast-printer@1.7.6":
version "1.7.6"
resolved "https://registry.yarnpkg.com/@webassemblyjs/wast-printer/-/wast-printer-1.7.6.tgz#a6002c526ac5fa230fe2c6d2f1bdbf4aead43a5e"
dependencies:
"@webassemblyjs/ast" "1.5.13"
"@webassemblyjs/wast-parser" "1.5.13"
long "^3.2.0"
"@webassemblyjs/ast" "1.7.6"
"@webassemblyjs/wast-parser" "1.7.6"
"@xtuc/long" "4.2.1"
"@xtuc/ieee754@^1.2.0":
version "1.2.0"
resolved "https://registry.yarnpkg.com/@xtuc/ieee754/-/ieee754-1.2.0.tgz#eef014a3145ae477a1cbc00cd1e552336dceb790"
"@xtuc/long@4.2.1":
version "4.2.1"
resolved "https://registry.yarnpkg.com/@xtuc/long/-/long-4.2.1.tgz#5c85d662f76fa1d34575766c5dcd6615abcd30d8"
abbrev@1:
version "1.1.1"
@@ -264,7 +279,7 @@ ajv@^5.1.0, ajv@^5.3.0:
fast-json-stable-stringify "^2.0.0"
json-schema-traverse "^0.3.0"
ajv@^6.0.1, ajv@^6.1.0, ajv@^6.5.0:
ajv@^6.0.1, ajv@^6.1.0:
version "6.5.2"
resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.5.2.tgz#678495f9b82f7cca6be248dd92f59bff5e1f4360"
dependencies:
@@ -273,6 +288,15 @@ ajv@^6.0.1, ajv@^6.1.0, ajv@^6.5.0:
json-schema-traverse "^0.4.1"
uri-js "^4.2.1"
ajv@^6.5.3:
version "6.5.3"
resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.5.3.tgz#71a569d189ecf4f4f321224fecb166f071dd90f9"
dependencies:
fast-deep-equal "^2.0.1"
fast-json-stable-stringify "^2.0.0"
json-schema-traverse "^0.4.1"
uri-js "^4.2.2"
amdefine@>=0.0.4:
version "1.0.1"
resolved "https://registry.yarnpkg.com/amdefine/-/amdefine-1.0.1.tgz#4a5282ac164729e93619bcfd3ad151f817ce91f5"
@@ -1217,10 +1241,6 @@ chalk@^2.0.0, chalk@^2.1.0, chalk@^2.3.0, chalk@^2.4.1:
escape-string-regexp "^1.0.5"
supports-color "^5.3.0"
chardet@^0.4.0:
version "0.4.2"
resolved "https://registry.yarnpkg.com/chardet/-/chardet-0.4.2.tgz#b5473b33dc97c424e5d98dc87d55d4d8a29c8bf2"
chardet@^0.5.0:
version "0.5.0"
resolved "https://registry.yarnpkg.com/chardet/-/chardet-0.5.0.tgz#fe3ac73c00c3d865ffcc02a0682e2c20b6a06029"
@@ -1626,12 +1646,6 @@ defaults@^1.0.0:
dependencies:
clone "^1.0.2"
define-properties@^1.1.2:
version "1.1.3"
resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.1.3.tgz#cf88da6cbee26fe6db7094f61d870cbd84cee9f1"
dependencies:
object-keys "^1.0.12"
define-property@^0.2.5:
version "0.2.5"
resolved "https://registry.yarnpkg.com/define-property/-/define-property-0.2.5.tgz#c35b1ef918ec3c990f9a5bc57be04aacec5c8116"
@@ -1718,7 +1732,7 @@ domain-browser@^1.1.1:
version "1.2.0"
resolved "https://registry.yarnpkg.com/domain-browser/-/domain-browser-1.2.0.tgz#3d31f50191a6749dd1375a7f522e823d42e54eda"
dropzone@^5.3.0:
dropzone@^5.5.1:
version "5.5.1"
resolved "https://registry.yarnpkg.com/dropzone/-/dropzone-5.5.1.tgz#06e2f513e61d6aa363d4b556f18574f47cf7ba26"
@@ -1796,24 +1810,6 @@ error-ex@^1.2.0:
dependencies:
is-arrayish "^0.2.1"
es-abstract@^1.10.0:
version "1.12.0"
resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.12.0.tgz#9dbbdd27c6856f0001421ca18782d786bf8a6165"
dependencies:
es-to-primitive "^1.1.1"
function-bind "^1.1.1"
has "^1.0.1"
is-callable "^1.1.3"
is-regex "^1.0.4"
es-to-primitive@^1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/es-to-primitive/-/es-to-primitive-1.1.1.tgz#45355248a88979034b6792e19bb81f2b7975dd0d"
dependencies:
is-callable "^1.1.1"
is-date-object "^1.0.1"
is-symbol "^1.0.1"
es5-ext@^0.10.14, es5-ext@^0.10.35, es5-ext@^0.10.45, es5-ext@^0.10.9, es5-ext@~0.10.14, es5-ext@~0.10.2:
version "0.10.46"
resolved "https://registry.yarnpkg.com/es5-ext/-/es5-ext-0.10.46.tgz#efd99f67c5a7ec789baa3daa7f79870388f7f572"
@@ -1850,9 +1846,9 @@ escape-string-regexp@^1.0.2, escape-string-regexp@^1.0.5:
version "1.0.5"
resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4"
eslint-loader@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/eslint-loader/-/eslint-loader-2.1.0.tgz#61334c548aeb0b8e20ec3a552fb7a88c47261c6a"
eslint-loader@^2.1.1:
version "2.1.1"
resolved "https://registry.yarnpkg.com/eslint-loader/-/eslint-loader-2.1.1.tgz#2a9251523652430bfdd643efdb0afc1a2a89546a"
dependencies:
loader-fs-cache "^1.0.0"
loader-utils "^1.0.2"
@@ -1882,12 +1878,12 @@ eslint-visitor-keys@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz#3f3180fb2e291017716acb4c9d6d5b5c34a6a81d"
eslint@^5.2.0:
version "5.3.0"
resolved "https://registry.yarnpkg.com/eslint/-/eslint-5.3.0.tgz#53695aca5213968aacdf970ccb231e42a2b285f8"
eslint@^5.6.0:
version "5.6.0"
resolved "https://registry.yarnpkg.com/eslint/-/eslint-5.6.0.tgz#b6f7806041af01f71b3f1895cbb20971ea4b6223"
dependencies:
ajv "^6.5.0"
babel-code-frame "^6.26.0"
"@babel/code-frame" "^7.0.0"
ajv "^6.5.3"
chalk "^2.1.0"
cross-spawn "^6.0.5"
debug "^3.1.0"
@@ -1902,11 +1898,11 @@ eslint@^5.2.0:
functional-red-black-tree "^1.0.1"
glob "^7.1.2"
globals "^11.7.0"
ignore "^4.0.2"
ignore "^4.0.6"
imurmurhash "^0.1.4"
inquirer "^5.2.0"
inquirer "^6.1.0"
is-resolvable "^1.1.0"
js-yaml "^3.11.0"
js-yaml "^3.12.0"
json-stable-stringify-without-jsonify "^1.0.1"
levn "^0.3.0"
lodash "^4.17.5"
@@ -1919,8 +1915,7 @@ eslint@^5.2.0:
progress "^2.0.0"
regexpp "^2.0.0"
require-uncached "^1.0.3"
semver "^5.5.0"
string.prototype.matchall "^2.0.0"
semver "^5.5.1"
strip-ansi "^4.0.0"
strip-json-comments "^2.0.1"
table "^4.0.3"
@@ -2033,14 +2028,6 @@ extend@^3.0.0, extend@~3.0.1, extend@~3.0.2:
version "3.0.2"
resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa"
external-editor@^2.1.0:
version "2.2.0"
resolved "https://registry.yarnpkg.com/external-editor/-/external-editor-2.2.0.tgz#045511cfd8d133f3846673d1047c154e214ad3d5"
dependencies:
chardet "^0.4.0"
iconv-lite "^0.4.17"
tmp "^0.0.33"
external-editor@^3.0.0:
version "3.0.1"
resolved "https://registry.yarnpkg.com/external-editor/-/external-editor-3.0.1.tgz#fc9638c4d7cde4f0bb82b12307a1a23912c492e3"
@@ -2272,10 +2259,6 @@ fstream@^1.0.0, fstream@^1.0.2:
mkdirp ">=0.5 0"
rimraf "2"
function-bind@^1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d"
functional-red-black-tree@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz#1b0ab3bd553b2a0d6399d29c0e3ea0b252078327"
@@ -2494,7 +2477,7 @@ gulp-autoprefixer@latest:
through2 "^2.0.0"
vinyl-sourcemaps-apply "^0.2.0"
gulp-clean-css@^3.9.4:
gulp-clean-css@^3.10.0:
version "3.10.0"
resolved "https://registry.yarnpkg.com/gulp-clean-css/-/gulp-clean-css-3.10.0.tgz#bccd4605eff104bfa4980014cc4b3c24c571736d"
dependencies:
@@ -2511,7 +2494,7 @@ gulp-csscomb@^3.0.8:
gulp-util "^3.0.7"
through2 "^2.0.1"
gulp-rename@^1.3.0:
gulp-rename@^1.4.0:
version "1.4.0"
resolved "https://registry.yarnpkg.com/gulp-rename/-/gulp-rename-1.4.0.tgz#de1c718e7c4095ae861f7296ef4f3248648240bd"
@@ -2625,10 +2608,6 @@ has-gulplog@^0.1.0:
dependencies:
sparkles "^1.0.0"
has-symbols@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.0.tgz#ba1a8f1af2a0fc39650f5c850367704122063b44"
has-unicode@^2.0.0:
version "2.0.1"
resolved "https://registry.yarnpkg.com/has-unicode/-/has-unicode-2.0.1.tgz#e0e6fe6a28cf51138855e086d1691e771de2a8b9"
@@ -2660,12 +2639,6 @@ has-values@^1.0.0:
is-number "^3.0.0"
kind-of "^4.0.0"
has@^1.0.1:
version "1.0.3"
resolved "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796"
dependencies:
function-bind "^1.1.1"
hash-base@^3.0.0:
version "3.0.4"
resolved "https://registry.yarnpkg.com/hash-base/-/hash-base-3.0.4.tgz#5fc8686847ecd73499403319a6b0a3f3f6ae4918"
@@ -2717,7 +2690,7 @@ https-browserify@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/https-browserify/-/https-browserify-1.0.0.tgz#ec06c10e0a34c0f2faf199f7fd7fc78fffd03c73"
iconv-lite@^0.4.17, iconv-lite@^0.4.22, iconv-lite@^0.4.4:
iconv-lite@^0.4.22, iconv-lite@^0.4.4:
version "0.4.23"
resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.23.tgz#297871f63be507adcfbfca715d0cd0eed84e9a63"
dependencies:
@@ -2733,7 +2706,7 @@ icss-utils@^2.1.0:
dependencies:
postcss "^6.0.1"
ieee754@^1.1.11, ieee754@^1.1.4:
ieee754@^1.1.4:
version "1.1.12"
resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.1.12.tgz#50bf24e5b9c8bb98af4964c941cdb0918da7b60b"
@@ -2747,7 +2720,7 @@ ignore-walk@^3.0.1:
dependencies:
minimatch "^3.0.4"
ignore@^4.0.2:
ignore@^4.0.6:
version "4.0.6"
resolved "https://registry.yarnpkg.com/ignore/-/ignore-4.0.6.tgz#750e3db5862087b4737ebac8207ffd1ef27b25fc"
@@ -2806,24 +2779,6 @@ ini@^1.3.4, ini@~1.3.0:
version "1.3.5"
resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.5.tgz#eee25f56db1c9ec6085e0c22778083f596abf927"
inquirer@^5.2.0:
version "5.2.0"
resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-5.2.0.tgz#db350c2b73daca77ff1243962e9f22f099685726"
dependencies:
ansi-escapes "^3.0.0"
chalk "^2.0.0"
cli-cursor "^2.1.0"
cli-width "^2.0.0"
external-editor "^2.1.0"
figures "^2.0.0"
lodash "^4.3.0"
mute-stream "0.0.7"
run-async "^2.2.0"
rxjs "^5.5.2"
string-width "^2.1.0"
strip-ansi "^4.0.0"
through "^2.3.6"
inquirer@^6.0.0:
version "6.1.0"
resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-6.1.0.tgz#8f65c7b31c498285f4ddf3b742ad8c487892040b"
@@ -2842,6 +2797,24 @@ inquirer@^6.0.0:
strip-ansi "^4.0.0"
through "^2.3.6"
inquirer@^6.1.0:
version "6.2.0"
resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-6.2.0.tgz#51adcd776f661369dc1e894859c2560a224abdd8"
dependencies:
ansi-escapes "^3.0.0"
chalk "^2.0.0"
cli-cursor "^2.1.0"
cli-width "^2.0.0"
external-editor "^3.0.0"
figures "^2.0.0"
lodash "^4.17.10"
mute-stream "0.0.7"
run-async "^2.2.0"
rxjs "^6.1.0"
string-width "^2.1.0"
strip-ansi "^4.0.0"
through "^2.3.6"
interpret@^1.0.0, interpret@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/interpret/-/interpret-1.1.0.tgz#7ed1b1410c6a0e0f78cf95d3b8440c63f78b8614"
@@ -2895,10 +2868,6 @@ is-builtin-module@^1.0.0:
dependencies:
builtin-modules "^1.0.0"
is-callable@^1.1.1, is-callable@^1.1.3:
version "1.1.4"
resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.1.4.tgz#1e1adf219e1eeb684d691f9d6a05ff0d30a24d75"
is-data-descriptor@^0.1.4:
version "0.1.4"
resolved "https://registry.yarnpkg.com/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz#0b5ee648388e2c860282e793f1856fec3f301b56"
@@ -2911,10 +2880,6 @@ is-data-descriptor@^1.0.0:
dependencies:
kind-of "^6.0.0"
is-date-object@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/is-date-object/-/is-date-object-1.0.1.tgz#9aa20eb6aeebbff77fbd33e74ca01b33581d3a16"
is-descriptor@^0.1.0:
version "0.1.6"
resolved "https://registry.yarnpkg.com/is-descriptor/-/is-descriptor-0.1.6.tgz#366d8240dde487ca51823b1ab9f07a10a78251ca"
@@ -3005,12 +2970,6 @@ is-promise@^2.1, is-promise@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/is-promise/-/is-promise-2.1.0.tgz#79a2a9ece7f096e80f36d2b2f3bc16c1ff4bf3fa"
is-regex@^1.0.4:
version "1.0.4"
resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.0.4.tgz#5517489b547091b0930e095654ced25ee97e9491"
dependencies:
has "^1.0.1"
is-relative@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/is-relative/-/is-relative-1.0.0.tgz#a1bb6935ce8c5dba1e8b9754b9b2dcc020e2260d"
@@ -3025,10 +2984,6 @@ is-stream@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44"
is-symbol@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/is-symbol/-/is-symbol-1.0.1.tgz#3cc59f00025194b6ab2e38dbae6689256b660572"
is-typedarray@~1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a"
@@ -3081,11 +3036,11 @@ js-tokens@^3.0.0, js-tokens@^3.0.2:
version "3.0.2"
resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-3.0.2.tgz#9866df395102130e38f7f996bceb65443209c25b"
"js-tokens@^3.0.0 || ^4.0.0":
"js-tokens@^3.0.0 || ^4.0.0", js-tokens@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499"
js-yaml@^3.11.0:
js-yaml@^3.12.0:
version "3.12.0"
resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.12.0.tgz#eaed656ec8344f10f527c6bfa1b6e2244de167d1"
dependencies:
@@ -3345,18 +3300,14 @@ lodash@^4.0.0, lodash@^4.17.4, lodash@^4.17.5, lodash@^4.2.0, lodash@^4.3.0, lod
version "4.17.10"
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.10.tgz#1b7793cf7259ea38fb3661d4d38b3260af8ae4e7"
lodash@^4.17.10:
version "4.17.11"
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.11.tgz#b39ea6229ef607ecd89e2c8df12536891cac9b8d"
lodash@~1.0.1:
version "1.0.2"
resolved "https://registry.yarnpkg.com/lodash/-/lodash-1.0.2.tgz#8f57560c83b59fc270bd3d561b690043430e2551"
long@4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/long/-/long-4.0.0.tgz#9a7b71cfb7d361a194ea555241c92f7468d5bf28"
long@^3.2.0:
version "3.2.0"
resolved "https://registry.yarnpkg.com/long/-/long-3.2.0.tgz#d821b7138ca1cb581c172990ef14db200b5c474b"
loose-envify@^1.0.0:
version "1.4.0"
resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf"
@@ -3842,10 +3793,6 @@ object-hash@^1.1.4:
version "1.3.0"
resolved "https://registry.yarnpkg.com/object-hash/-/object-hash-1.3.0.tgz#76d9ba6ff113cf8efc0d996102851fe6723963e2"
object-keys@^1.0.12:
version "1.0.12"
resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.0.12.tgz#09c53855377575310cca62f55bb334abff7b3ed2"
object-visit@^1.0.0:
version "1.0.1"
resolved "https://registry.yarnpkg.com/object-visit/-/object-visit-1.0.1.tgz#f79c4493af0c5377b59fe39d395e41042dd045bb"
@@ -4385,12 +4332,6 @@ regex-not@^1.0.0, regex-not@^1.0.2:
extend-shallow "^3.0.2"
safe-regex "^1.1.0"
regexp.prototype.flags@^1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/regexp.prototype.flags/-/regexp.prototype.flags-1.2.0.tgz#6b30724e306a27833eeb171b66ac8890ba37e41c"
dependencies:
define-properties "^1.1.2"
regexpp@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/regexpp/-/regexpp-2.0.0.tgz#b2a7534a85ca1b033bcf5ce9ff8e56d4e0755365"
@@ -4579,12 +4520,6 @@ run-queue@^1.0.0, run-queue@^1.0.3:
dependencies:
aproba "^1.1.1"
rxjs@^5.5.2:
version "5.5.11"
resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-5.5.11.tgz#f733027ca43e3bec6b994473be4ab98ad43ced87"
dependencies:
symbol-observable "1.0.1"
rxjs@^6.1.0:
version "6.2.2"
resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.2.2.tgz#eb75fa3c186ff5289907d06483a77884586e1cf9"
@@ -4640,6 +4575,10 @@ semver@^4.1.0:
version "4.3.6"
resolved "https://registry.yarnpkg.com/semver/-/semver-4.3.6.tgz#300bc6e0e86374f7ba61068b5b1ecd57fc6532da"
semver@^5.5.1:
version "5.5.1"
resolved "https://registry.yarnpkg.com/semver/-/semver-5.5.1.tgz#7dfdd8814bdb7cabc7be0fb1d734cfb66c940477"
semver@~5.3.0:
version "5.3.0"
resolved "https://registry.yarnpkg.com/semver/-/semver-5.3.0.tgz#9b2ce5d3de02d17c6012ad326aa6b4d0cf54f94f"
@@ -4913,16 +4852,6 @@ string-width@^1.0.1, string-width@^1.0.2:
is-fullwidth-code-point "^2.0.0"
strip-ansi "^4.0.0"
string.prototype.matchall@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/string.prototype.matchall/-/string.prototype.matchall-2.0.0.tgz#2af8fe3d2d6dc53ca2a59bd376b089c3c152b3c8"
dependencies:
define-properties "^1.1.2"
es-abstract "^1.10.0"
function-bind "^1.1.1"
has-symbols "^1.0.0"
regexp.prototype.flags "^1.2.0"
string_decoder@^1.0.0, string_decoder@~1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8"
@@ -4976,9 +4905,9 @@ strip-json-comments@^2.0.1, strip-json-comments@~2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a"
style-loader@^0.21.0:
version "0.21.0"
resolved "https://registry.yarnpkg.com/style-loader/-/style-loader-0.21.0.tgz#68c52e5eb2afc9ca92b6274be277ee59aea3a852"
style-loader@^0.23.0:
version "0.23.0"
resolved "https://registry.yarnpkg.com/style-loader/-/style-loader-0.23.0.tgz#8377fefab68416a2e05f1cabd8c3a3acfcce74f1"
dependencies:
loader-utils "^1.1.0"
schema-utils "^0.4.5"
@@ -4993,10 +4922,6 @@ supports-color@^5.3.0, supports-color@^5.4.0:
dependencies:
has-flag "^3.0.0"
symbol-observable@1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/symbol-observable/-/symbol-observable-1.0.1.tgz#8340fc4702c3122df5d22288f88283f513d3fdd4"
table@^4.0.3:
version "4.0.3"
resolved "https://registry.yarnpkg.com/table/-/table-4.0.3.tgz#00b5e2b602f1794b9acaf9ca908a76386a7813bc"
@@ -5012,6 +4937,10 @@ tapable@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/tapable/-/tapable-1.0.0.tgz#cbb639d9002eed9c6b5975eb20598d7936f1f9f2"
tapable@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/tapable/-/tapable-1.1.0.tgz#0d076a172e3d9ba088fd2272b2668fb8d194b78c"
tar@^2.0.0:
version "2.2.1"
resolved "https://registry.yarnpkg.com/tar/-/tar-2.2.1.tgz#8e4d2a256c0e2185c6b18ad694aec968b83cb1d1"
@@ -5232,7 +5161,7 @@ upath@^1.0.5:
version "1.1.0"
resolved "https://registry.yarnpkg.com/upath/-/upath-1.1.0.tgz#35256597e46a581db4793d0ce47fa9aebfc9fabd"
uri-js@^4.2.1:
uri-js@^4.2.1, uri-js@^4.2.2:
version "4.2.2"
resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.2.2.tgz#94c540e1ff772956e2299507c010aea6c8838eb0"
dependencies:
@@ -5389,22 +5318,28 @@ webpack-cli@^3.1.0:
v8-compile-cache "^2.0.0"
yargs "^12.0.1"
webpack-sources@^1.0.1, webpack-sources@^1.1.0:
webpack-sources@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-1.1.0.tgz#a101ebae59d6507354d71d8013950a3a8b7a5a54"
dependencies:
source-list-map "^2.0.0"
source-map "~0.6.1"
webpack@^4.16.2:
version "4.16.5"
resolved "https://registry.yarnpkg.com/webpack/-/webpack-4.16.5.tgz#29fb39462823d7eb8aefcab8b45f7f241db0d092"
webpack-sources@^1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-1.2.0.tgz#18181e0d013fce096faf6f8e6d41eeffffdceac2"
dependencies:
"@webassemblyjs/ast" "1.5.13"
"@webassemblyjs/helper-module-context" "1.5.13"
"@webassemblyjs/wasm-edit" "1.5.13"
"@webassemblyjs/wasm-opt" "1.5.13"
"@webassemblyjs/wasm-parser" "1.5.13"
source-list-map "^2.0.0"
source-map "~0.6.1"
webpack@^4.19.1:
version "4.19.1"
resolved "https://registry.yarnpkg.com/webpack/-/webpack-4.19.1.tgz#096674bc3b573f8756c762754366e5b333d6576f"
dependencies:
"@webassemblyjs/ast" "1.7.6"
"@webassemblyjs/helper-module-context" "1.7.6"
"@webassemblyjs/wasm-edit" "1.7.6"
"@webassemblyjs/wasm-parser" "1.7.6"
acorn "^5.6.2"
acorn-dynamic-import "^3.0.0"
ajv "^6.1.0"
@@ -5421,10 +5356,10 @@ webpack@^4.16.2:
neo-async "^2.5.0"
node-libs-browser "^2.0.0"
schema-utils "^0.4.4"
tapable "^1.0.0"
tapable "^1.1.0"
uglifyjs-webpack-plugin "^1.2.4"
watchpack "^1.5.0"
webpack-sources "^1.0.1"
webpack-sources "^1.2.0"
which-module@^1.0.0:
version "1.0.0"
+2
View File
@@ -0,0 +1,2 @@
.idea
.DS_Store
+29
View File
@@ -1,3 +1,32 @@
# v2.8.2
## 12/14/2018
1. [](#new)
* Fire `onUserLoginRegisteredUser()` event to allow manipulation of User object after registration
# v2.8.1
## 12/13/2018
1. [](#bugfix)
* Fix various redirects to use `lang-safe` variety for better multi-language support [#186]((https://github.com/getgrav/grav-plugin-login/issues/186))
* Ensure only defined `user_registration.fields` are allowed in registration and profile forms
# v2.8.0
## 11/12/2018
1. [](#new)
* Store remember me triplets into `user://data/rememberme` instead of storing them into the cache
* Ability to register + authorize but require accounts to be manually enabled [#180](https://github.com/getgrav/grav-plugin-login/issues/180)
1. [](#improved)
* If login on registration or activation has been turned on, use login redirect if override is not set
* Dont set default templates for `register` and `unauthorized`, use overridable templates [#179](https://github.com/getgrav/grav-plugin-login/issues/179)
* Updated `de.yaml` [#175](https://github.com/getgrav/grav-plugin-login/pull/175)
* Updated `ru.yaml` [#176](https://github.com/getgrav/grav-plugin-login/pull/176)
1. [](#bugfix)
* Fixed broken remember me functionality
* Fixed client side validation in login forms
* Fix uppercase and Unicode username handling [#177](https://github.com/getgrav/grav-plugin-login/pull/177)
# v2.7.3
## 06/20/2018
+66 -21
View File
@@ -139,31 +139,61 @@ access:
# Default Configuration
```yaml
enabled: true # Enable the plugin
built_in_css: true # Use built-in CSS
route: # Specific route for Login page (default is '/login')
redirect_to_login: true # If you try to access a page you don't have access to, should you redirect to login route
redirect_after_login: # Path to redirect to after a successful login (eg '/user_profile')
route_activate: '/activate_user' # Route for the user activation process
route_forgot: '/forgot_password' # Route for the forgot password process
route_reset: '/reset_password' # Route for the reset password process
route_profile: '/user_profile' # Route for the user profile page
route_register: '/user_register' # Route for the user registration page
route_unauthorized: '/user_unauthorized' # Route for a page to display if user is unauthorized
enabled: true # Enable the plugin
built_in_css: true # Use built-in CSS
route: # Specific route for Login page (default is '/login')
redirect_to_login: true # If you try to access a page you don't have access to, should you redirect to login route
redirect_after_login: # Path to redirect to after a successful login (eg '/user_profile')
redirect_after_logout: '/' # Path to redirect to after a successful logout (eg '/')
route_activate: '/activate_user' # Route for the user activation process
route_forgot: '/forgot_password' # Route for the forgot password process
route_reset: '/reset_password' # Route for the reset password process
route_profile: '/user_profile' # Route for the user profile page
route_register: '/user_register' # Route for the user registration page
route_unauthorized: '/user_unauthorized' # Route for a page to display if user is unauthorized
dynamic_page_visibility: false # Integrate access into page visibility so things can be shown or hidden in the menu
parent_acl: false # Look to parent `access` rules for access requirements
protect_protected_page_media: false # Take `access` rules into account when directly accessing a page's media
dynamic_page_visibility: false # Integrate access into page visibility so things can be shown or hidden in the menu
parent_acl: false # Look to parent `access` rules for access requirements
protect_protected_page_media: false # Take `access` rules into account when directly accessing a page's media
rememberme:
enabled: true # Enable 'remember me' functionality
timeout: 604800 # Timeout in seconds. Defaults to 1 week
name: grav-rememberme # Name prefix of the session cookie
enabled: true # Enable 'remember me' functionality
timeout: 604800 # Timeout in seconds. Defaults to 1 week
name: grav-rememberme # Name prefix of the session cookie
max_pw_resets_count: 0 # Number of password resets in a specific time frame (0 = unlimited)
max_pw_resets_interval: 60 # Time in minutes to track password resets
max_login_count: 0 # Number of failed login attempts in a specific time frame (0 = unlimited)
max_login_interval: 2 # Time in minutes to track login attempts
max_pw_resets_count: 0 # Number of password resets in a specific time frame (0 = unlimited)
max_pw_resets_interval: 60 # Time in minutes to track password resets
max_login_count: 0 # Number of failed login attempts in a specific time frame (0 = unlimited)
max_login_interval: 2 # Time in minutes to track login attempts
user_registration:
enabled: false # Enable User Registration Process
fields: # List of fields to validate and store during user registration
- 'username' # This should match up with your registration form definition
- 'password'
- 'email'
- 'fullname'
- 'title'
- 'level'
default_values: # Any default values for fields you would like to set
level: Newbie # Here the 'level' field will be pre-populated with 'Newbie' text
access: # Default access to set for users created during registration
site:
login: 'true'
redirect_after_registration: '' # Route to redirect to after registration
options:
validate_password1_and_password2: true # Ensure that password1 and password2 match during registration (allows you to have just 1 pw field or 2)
set_user_disabled: false # Set this `true` if you want a user to activate their account via email
login_after_registration: false # Automatically login after registration
send_activation_email: false # Send an email that requires a special link to be clicked in order to activate the account
manually_enable: false # When using activation email, don't enable until an admin does it manually
send_notification_email: false # Send an email to the site administrator to indicate a user has registered
send_welcome_email: false # Send a welcome email to the user (probably should not be used with `send_activation_email`
```
# Usage
@@ -483,7 +513,22 @@ You can set the "Redirect after registration" option in the Login plugin, or as
display: /welcome
```
## Dynamic Page Visibility
You can control whether or not a page is visible to a user by first enabling the option in the `login` configuration:
```
dyanamic_page_visilbity: true
```
With this activated you can put the following option into the header of each page:
```
login:
visibility_requires_access: true
```
This will ensure the `access:` options on the page are satisfied in order for this page to be `visible` and therefore displayed in the menu structure.
# Known issues
+18 -5
View File
@@ -1,5 +1,5 @@
name: Login
version: 2.7.3
version: 2.8.2
description: Enables user authentication and login screen.
icon: sign-in
author:
@@ -290,7 +290,7 @@ form:
type: toggle
label: PLUGIN_LOGIN.SET_USER_DISABLED
help: PLUGIN_LOGIN.SET_USER_DISABLED_HELP
highlight: 1
highlight: 0
options:
1: PLUGIN_ADMIN.YES
0: PLUGIN_ADMIN.NO
@@ -312,18 +312,29 @@ form:
type: toggle
label: PLUGIN_LOGIN.SEND_ACTIVATION_EMAIL
help: PLUGIN_LOGIN.SEND_ACTIVATION_EMAIL_HELP
highlight: 1
highlight: 0
options:
1: PLUGIN_ADMIN.YES
0: PLUGIN_ADMIN.NO
validate:
type: bool
user_registration.options.manually_enable:
type: toggle
label: PLUGIN_LOGIN.MANUALLY_ENABLE
help: PLUGIN_LOGIN.MANUALLY_ENABLE_HELP
highlight: 0
options:
1: PLUGIN_ADMIN.YES
0: PLUGIN_ADMIN.NO
validate:
type: bool
user_registration.options.send_notification_email:
type: toggle
label: PLUGIN_LOGIN.SEND_NOTIFICATION_EMAIL
help: PLUGIN_LOGIN.SEND_NOTIFICATION_EMAIL_HELP
highlight: 1
highlight: 0
options:
1: PLUGIN_ADMIN.YES
0: PLUGIN_ADMIN.NO
@@ -334,10 +345,12 @@ form:
type: toggle
label: PLUGIN_LOGIN.SEND_WELCOME_EMAIL
help: PLUGIN_LOGIN.SEND_WELCOME_EMAIL_HELP
highlight: 1
highlight: 0
options:
1: PLUGIN_ADMIN.YES
0: PLUGIN_ADMIN.NO
validate:
type: bool
Security:
type: tab
+10 -4
View File
@@ -335,7 +335,13 @@ class Controller
$author = $this->grav['config']->get('site.author.name', '');
$fullname = $user->fullname ?: $user->username;
$reset_link = $this->grav['base_url_absolute'] . $this->grav['config']->get('plugins.login.route_reset') . '/task:login.reset/token' . $param_sep . $token . '/user' . $param_sep . $user->username . '/nonce' . $param_sep . Utils::getNonce('reset-form');
if ($this->grav['language']->getDefault() != $this->grav['language']->getLanguage()) {
$lang = '/'.$this->grav['language']->getLanguage();
} else {
$lang = '';
}
$reset_link = $this->grav['base_url_absolute'] . $lang . $this->grav['config']->get('plugins.login.route_reset') . '/task:login.reset/token' . $param_sep . $token . '/user' . $param_sep . $user->username . '/nonce' . $param_sep . Utils::getNonce('reset-form');
$sitename = $this->grav['config']->get('site.title', 'Website');
@@ -381,7 +387,7 @@ class Controller
if ($good_token === $token) {
if (time() > $expire) {
$messages->add($language->translate('PLUGIN_LOGIN.RESET_LINK_EXPIRED'), 'error');
$this->grav->redirect($this->grav['config']->get('plugins.login.route_forgot', '/'));
$this->grav->redirectLangSafe($this->grav['config']->get('plugins.login.route_forgot', '/'));
return true;
}
@@ -401,7 +407,7 @@ class Controller
}
$messages->add($language->translate('PLUGIN_LOGIN.RESET_INVALID_LINK'), 'error');
$this->grav->redirect($this->grav['config']->get('plugins.login.route_forgot'));
$this->grav->redirectLangSafe($this->grav['config']->get('plugins.login.route_forgot'));
return true;
@@ -412,7 +418,7 @@ class Controller
if (!$user || !$token) {
$messages->add($language->translate('PLUGIN_LOGIN.RESET_INVALID_LINK'), 'error');
$this->grav->redirect($this->grav['config']->get('plugins.login.route_forgot'));
$this->grav->redirectLangSafe($this->grav['config']->get('plugins.login.route_forgot'));
return true;
}
+7 -8
View File
@@ -196,7 +196,7 @@ class Login
}
if ($redirect) {
$this->grav->redirect($redirect, $event->getRedirectCode());
$this->grav->redirectLangSafe($redirect, $event->getRedirectCode());
}
return $user->authenticated && $user->authorized;
@@ -230,7 +230,7 @@ class Login
$username = $this->validateField('username', $data['username']);
$file = CompiledYamlFile::instance($this->grav['locator']->findResource('account://' . $username . YAML_EXT,
$file = CompiledYamlFile::instance($this->grav['locator']->findResource('account://' . mb_strtolower($username) . YAML_EXT,
true, true));
// Create user object and save it
@@ -423,9 +423,6 @@ class Login
$author
]);
$to = $user->email;
$sent = EmailUtils::sendEmail($subject, $content, $to);
if ($sent < 1) {
@@ -452,12 +449,14 @@ class Login
if (!$this->rememberMe) {
/** @var Config $config */
$config = $this->grav['config'];
$cookieName = $config->get('plugins.login.rememberme.name');
$timeout = $config->get('plugins.login.rememberme.timeout');
// Setup storage for RememberMe cookies
$storage = new TokenStorage;
$storage = new TokenStorage('user://data/rememberme', $timeout);
$this->rememberMe = new RememberMe($storage);
$this->rememberMe->setCookieName($config->get('plugins.login.rememberme.name'));
$this->rememberMe->setExpireTime($config->get('plugins.login.rememberme.timeout'));
$this->rememberMe->setCookieName($cookieName);
$this->rememberMe->setExpireTime($timeout);
// Hardening cookies with user-agent and random salt or
// fallback to use system based cache key
@@ -2,35 +2,28 @@
/**
* @package Grav\Plugin\Login
*
* @copyright Copyright (C) 2014 - 2017 RocketTheme, LLC. All rights reserved.
* @copyright Copyright (C) 2014 - 2018 RocketTheme, LLC. All rights reserved.
* @license MIT License; see LICENSE file for details.
*/
namespace Grav\Plugin\Login\RememberMe;
use Grav\Common\Cache;
use Grav\Common\File\CompiledYamlFile;
use Grav\Common\Filesystem\Folder;
use Grav\Common\Grav;
use Doctrine\Common\Cache\CacheProvider;
use Doctrine\Common\Cache\FilesystemCache;
use Birke\Rememberme\Storage\StorageInterface;
/**
* Storage wrapper for Doctrine cache
* Token Storage wrapper
*
* Used for storing the credential/token/persistentToken triplets.
*
* @author Sommerregen <sommerregen@benjamin-regler.de>
*/
class TokenStorage implements StorageInterface
{
/**
* @var CacheProvider
*/
protected $driver;
/** @var string */
protected $path;
/**
* @var string
*/
protected $cache_dir;
/** @var int */
protected $timeout;
/**
* Constructor
@@ -38,28 +31,16 @@ class TokenStorage implements StorageInterface
* @param string $path Path to storage directory
* @throws \InvalidArgumentException
*/
public function __construct($path = 'cache://rememberme')
public function __construct($path = 'user://data/rememberme', $timeout = 604800)
{
/** @var Cache $cache */
$cache = Grav::instance()['cache'];
$this->cache_dir = Grav::instance()['locator']->findResource($path, true, true);
// Setup cache
$this->driver = $cache->getCacheDriver();
if ($this->driver instanceof FilesystemCache) {
$this->driver = new FilesystemCache($this->cache_dir);
}
// Set the cache namespace to our unique key
$this->driver->setNamespace($cache->getKey());
$this->path = Grav::instance()['locator']->findResource($path, true, true);
$this->timeout = $timeout;
}
/**
* Return Tri-state value constant
*
* @param mixed $credential Unique credential (user id,
* email address, user name)
* @param mixed $credential Unique credential (user id, email address, user name)
* @param string $token One-Time Token
* @param string $persistentToken Persistent Token
*
@@ -67,64 +48,47 @@ class TokenStorage implements StorageInterface
*/
public function findTriplet($credential, $token, $persistentToken)
{
// Hash the tokens, because they can contain a salt and can be accessed in the file system
$persistentToken = sha1($persistentToken);
$token = sha1($token);
// Hash the tokens, because they can contain a salt and can be
// accessed in the file system
$persistentToken = sha1(trim($persistentToken));
$token = sha1(trim($token));
$file = $this->getFile($credential);
$tokens = (array)$file->content();
$id = $this->getId($credential);
if (!$this->driver->contains($id)) {
if (!isset($tokens[$persistentToken]) || $tokens[$persistentToken] < time() + $this->timeout) {
return self::TRIPLET_NOT_FOUND;
}
list($expire, $tokens) = $this->driver->fetch($id);
if (isset($tokens[$persistentToken]) && $tokens[$persistentToken] === $token) {
return self::TRIPLET_FOUND;
$stored = key($tokens[$persistentToken]);
if ($stored !== $token) {
return self::TRIPLET_INVALID;
}
return self::TRIPLET_INVALID;
return self::TRIPLET_FOUND;
}
/**
* Store the new token for the credential and the persistent token.
* Create a new storage entry, if the combination of credential and
* persistent token does not exist.
* Store the new token for the credential and the persistent token. Create a new storage entry, if the combination
* of credential and persistent token does not exist.
*
* @param mixed $credential
* @param string $token
* @param string $persistentToken
* @param int $expire Timestamp when this triplet
* will expire (0 = no expiry)
* @param int $expire Timestamp when this triplet will expire (0 = no expiry)
*/
public function storeTriplet($credential, $token, $persistentToken, $expire = null)
{
// Hash the tokens, because they can contain a salt and can be
// accessed in the file system
$persistentToken = sha1(trim($persistentToken));
$token = sha1(trim($token));
// Hash the tokens, because they can contain a salt and can be accessed in the file system
$persistentToken = sha1($persistentToken);
$token = sha1($token);
$e = null;
$tokens = [];
$id = $this->getId($credential);
if ($this->driver->contains($id)) {
list($e, $tokens) = $this->driver->fetch($id);
}
$file = $this->getFile($credential);
$tokens = (array)$file->content();
// Get cache lifetime for tokens
if ($expire === null && $e === null) {
/** @var Cache $cache */
$cache = Grav::instance()['cache'];
$expire = $cache->getLifetime();
} elseif ($expire === null) {
$expire = $e;
}
// Update token
$tokens[$persistentToken] = [$token => time()];
// Update tokens
$tokens[$persistentToken] = $token;
$this->driver->save($id, [$expire, $tokens], $expire);
return $this;
$file->save($tokens);
}
/**
@@ -137,7 +101,6 @@ class TokenStorage implements StorageInterface
*/
public function replaceTriplet($credential, $token, $persistentToken, $expire = null)
{
$this->cleanTriplet($credential, $persistentToken);
$this->storeTriplet($credential, $token, $persistentToken, $expire);
}
@@ -149,16 +112,25 @@ class TokenStorage implements StorageInterface
*/
public function cleanTriplet($credential, $persistentToken)
{
// Hash the tokens, because they can contain a salt and can be
// accessed in the file system
$persistentToken = sha1(trim($persistentToken));
// Hash the tokens, because they can contain a salt and can be accessed in the file system
$persistentToken = sha1($persistentToken);
// Delete token from storage
$id = $this->getId($credential);
if ($this->driver->contains($id)) {
list($expire, $tokens) = $this->driver->fetch($id);
$file = $this->getFile($credential);
if (!$file->exists()) {
return;
}
$tokens = (array)$file->content();
if (isset($tokens[$persistentToken])) {
// Delete token from storage
unset($tokens[$persistentToken]);
$this->driver->save($id, [$expire, $tokens], $expire);
if ($tokens) {
$file->save($tokens);
} else {
$file->delete();
}
}
}
@@ -170,8 +142,10 @@ class TokenStorage implements StorageInterface
*/
public function cleanAllTriplets($credential)
{
$id = $this->getId($credential);
$this->driver->delete($id);
$file = $this->getFile($credential);
if ($file->exists()) {
$file->delete();
}
}
/**
@@ -179,20 +153,17 @@ class TokenStorage implements StorageInterface
*/
public function clearCache()
{
$this->driver->flushAll();
if (is_dir($this->path)) {
Folder::delete($this->path, false);
}
}
/**
* Get the cache id
*
* @param string $key A key to compute the cache id for
* @return string The cache id
* @param string $credential
* @return CompiledYamlFile
*/
protected function getId($key)
protected function getFile($credential)
{
/** @var Cache $cache */
$cache = Grav::instance()['cache'];
return 'login' . md5(trim($key) . $cache->getKey());
return CompiledYamlFile::instance($this->path . '/' . sha1($credential) . '.yaml');
}
}
@@ -118,7 +118,7 @@ class ChangePasswordCommand extends ConsoleCommand
}
// Lowercase the username for the filename
$username = strtolower($username);
$username = mb_strtolower($username);
/** @var UniformResourceLocator $locator */
$locator = Grav::instance()['locator'];
@@ -116,7 +116,7 @@ class ChangeUserStateCommand extends ConsoleCommand
}
// Lowercase the username for the filename
$username = strtolower($username);
$username = mb_strtolower($username);
/** @var UniformResourceLocator $locator */
$locator = Grav::instance()['locator'];
+4 -4
View File
@@ -131,9 +131,9 @@ class NewUserCommand extends ConsoleCommand
return $value;
});
$username = $helper->ask($this->input, $this->output, $question);
$data['username'] = $helper->ask($this->input, $this->output, $question);
} else {
$username = $this->options['user'];
$data['username'] = $this->options['user'];
}
@@ -227,7 +227,7 @@ class NewUserCommand extends ConsoleCommand
}
// Lowercase the username for the filename
$username = strtolower($username);
$username = mb_strtolower($data['username']);
/** @var UniformResourceLocator $locator */
$locator = Grav::instance()['locator'];
@@ -281,4 +281,4 @@ class NewUserCommand extends ConsoleCommand
$question->setHiddenFallback(true);
return $helper->ask($this->input, $this->output, $question);
}
}
}
+15
View File
@@ -0,0 +1,15 @@
{
"project":"grav-plugin-login",
"platforms":{
"grav":{
"nodes":{
"plugin":[
{
"source":"/",
"destination":"/user/plugins/login"
}
]
}
}
}
}
+110 -29
View File
@@ -1,47 +1,128 @@
PLUGIN_LOGIN:
USERNAME: "Benutzername"
EMAIL: "Email"
USERNAME_EMAIL: "Benutzername/Email"
EMAIL: "E-Mail"
USERNAME_EMAIL: "Benutzername/E-Mail"
PASSWORD: "Passwort"
ACCESS_DENIED: "Zugang verweigert"
LOGIN_FAILED: "Login fehlgeschlagen..."
LOGIN_SUCCESSFUL: "Du wurdest erfolgreich eingeloggt."
ACCESS_DENIED: "Zugang verweigert"
LOGIN_FAILED: "Login fehlgeschlagen"
LOGIN_SUCCESSFUL: "Sie wurden erfolgreich angemeldet."
BTN_LOGIN: "Anmelden"
BTN_LOGOUT: "Abmelden"
BTN_FORGOT: "Vergessen"
BTN_REGISTER: "Registrieren"
BTN_RESET: "Passwort zurücksetzen"
BTN_SEND_INSTRUCTIONS: "Sende Anweisungen zum Zurücksetzen"
RESET_LINK_EXPIRED: "Der Link zum Zurücksetzen ist abgelaufen. Bitte erneut versuchen."
RESET_PASSWORD_RESET: "Das Passwort wurde zurückgesetzt"
RESET_INVALID_LINK: "Es wurde ein ungültiger Link zum Zurücksetzen verwendet. Bitte erneut versuchen."
FORGOT_INSTRUCTIONS_SENT_VIA_EMAIL: "Die Anweisungen zum Zurücksetzen Ihres Passworts wurden per E-Mail gesendet."
FORGOT_FAILED_TO_EMAIL: "Das Versenden der Anweisung per E-Mail ist fehlgeschlagen. Bitte später erneut versuchen."
FORGOT_CANNOT_RESET_EMAIL_NO_EMAIL: "Das Passwort für %s kann nicht zurückgesetzt werden. Es ist keine E-Mail-Adresse hinterlegt."
FORGOT_CANNOT_RESET_EMAIL_NO_PASSWORD: "Das Passwort für %s kann nicht zurückgesetzt werden. Diese E-Mail ist mit einem Remote-Account verknüpft."
FORGOT_USERNAME_DOES_NOT_EXIST: "Der Benutzer mit dem Benutzername <b>%s</ b> existiert nicht."
FORGOT_EMAIL_NOT_CONFIGURED: "Das Passwort kann nicht zurückgesetzt werden, da die Website ist nicht zum Versenden von E-Mails konfiguriert."
FORGOT_EMAIL_SUBJECT: "Passwort zurückzusetzen für %s"
FORGOT_EMAIL_BODY: "<h1>Passwort Zurücksetzen</h1><p>Hallo %1$s,</p><p>Es wurde ein Anfrage auf <b>%4$s</b> gestellt, um Ihr Passwort zu ändern.</p><p><br /><a href=\"%2$s\" class=\"btn-primary\">Klicken Sie hier, um Ihr Passwort zurückzusetzen</a><br /><br /></p><p>Alternativ kopieren Sie die folgende URL in die Adresszeile Ihres Browsers:</p> <p class=\"word-break\"><a href=\"%2$s\">%2$s</a></p> <p><br />Mit freundlichen Grüßen,<br /><br />%3$s</p>"
REMEMBER_ME: "Angemeldet bleiben"
REMEMBER_ME_HELP: "Speichert einen Cookie im Browser, welcher eine fortwährende Anmeldung sicherstellt."
BUILTIN_CSS: "Nutze das integrierte CSS"
REMEMBER_ME_STOLEN_COOKIE: "Jemand anderes hat Ihre Zugangsdaten verwendet, um auf diese Seite zuzugreifen! Alle Sitzungen wurden ausgeloggt. Bitte melden Sie sich mit Ihren Zugangsdaten an und überprüfen Sie Ihre Daten."
BUILTIN_CSS: "Nutze das integrierte Plugin CSS"
BUILTIN_CSS_HELP: "Nutze das CSS, welches vom Admin Plugin bereitgestellt werden"
ROUTE: "Anmeldepfad"
ROUTE_HELP: "Route zu einer benutzerdefinierten Anmeldeseite, die von Ihrem Theme bereitgestellt wird."
ROUTE_REGISTER: "Registrierungspfad"
USERNAME_NOT_AVAILABLE: "Der Nutzername %s existiert bereits, bitte wähle einen Anderen"
USER_NEEDS_EMAIL_FIELD: "Der Nutzer benötigt ein E-Mail Feld"
EMAIL_SENDING_FAILURE: "Ein Fehler ist beim senden der E-Mail aufgetreten"
ACTIVATION_EMAIL_SUBJECT: "Aktiviere dein Account auf %s"
ACTIVATION_EMAIL_BODY: "Hi %s, click %s to activate your account on %s"
WELCOME_EMAIL_SUBJECT: "Willkommen zu %s"
WELCOME_EMAIL_BODY: "Hi %s, willkommen zu %s!"
NOTIFICATION_EMAIL_SUBJECT: "Neuer Nutzer auf %s"
NOTIFICATION_EMAIL_BODY: "Hi, ein neuer Nutzer hat sich auf %s registriert. Nutzername: %s, E-Mail: %s"
ROUTE_REGISTER_HELP: "Pfad zur Registrierungsseite. Stellen Sie dies ein, wenn Sie die eingebaute Registrierungsseite verwenden möchten. Lassen Sie das Feld leer, wenn Sie ein eigenes Anmeldeformular haben."
USERNAME_NOT_VALID: "Der Benutzername sollte zwischen 3 und 16 Zeichen enthalten, einschließlich Kleinbuchstaben, Zahlen, Unterstrichen und Bindestrichen. Großbuchstaben, Leerzeichen und Sonderzeichen sind nicht zulässig"
USERNAME_NOT_AVAILABLE: "Der Benutzername %s existiert bereits. Bitte wählen Sie einen anderen Benutzernamen."
EMAIL_NOT_AVAILABLE: "Die E-Mail-Adresse %s ist bereits vorhanden. Bitte wählen Sie eine andere E-Mail-Adresse aus."
PASSWORD_NOT_VALID: "Das Passwort muss mindestens eine Zahl und einen Groß- und Kleinbuchstaben sowie mindestens 8 oder mehr Zeichen enthalten."
PASSWORDS_DO_NOT_MATCH: "Die Passwörter stimmen nicht überein. Überprüfen Sie, ob Sie das gleiche Passwort zweimal eingegeben haben."
USER_NEEDS_EMAIL_FIELD: "Der Benutzer benötigt ein E-Mail Feld."
EMAIL_SENDING_FAILURE: "Beim Senden der E-Mail ist ein Fehler aufgetreten."
ACTIVATION_EMAIL_SUBJECT: "Aktivieren Sie Ihren Account bei %s"
ACTIVATION_EMAIL_BODY: "<h1>Account Aktivierung</h1><p>Hallo %1$s, </p><p>Ihr Konto bei <b>%3$s</b> wurde erfolgreich erstellt, aber Sie können sich erst anmelden, wenn es aktiviert ist.</p><p><br/><a href=\"%2$s\" class=\"btn-primary\">Account jetzt aktivieren</a><br/><br/></p><p>Alternativ kopieren Sie die folgende URL in die Adresszeile Ihres Browsers:</p><p class=\"word-break\"><a href=\"%2$s\">%2$s</a></p><p><br/>Mit freundlichen Grüßen,<br/><br/>%4$s</p>"
ACTIVATION_NOTICE_MSG: "Hallo %s, Ihr Konto wurde erstellt. Bitte überprüfen Sie Ihre E-Mail, um es vollständig zu aktivieren"
WELCOME_EMAIL_SUBJECT: "Willkommen bei %s"
WELCOME_EMAIL_BODY: "<h1>Account erstellt</h1><p>Hallo %1$s, </p><p>Ihr Konto bei <b>%3$s</b> wurde erfolgreich erstellt.</p><p><br/><a href=\"%2$s\" class=\"btn-primary\">Jetzt anmelden</a><br/><br/></p><p>Alternativ kopieren Sie die folgende URL in die Adresszeile Ihres Browsers:</p><p class=\"word-break\"><a href=\"%2$s\">%2$s</a></p><p><br/>Mit freundlichen Grüßen,<br/><br/>%4$s</p>"
WELCOME_NOTICE_MSG: "Hallo %s, Ihr Account wurde erfolgreich erstellt"
NOTIFICATION_EMAIL_SUBJECT: "Neuer Benutzer bei %s"
NOTIFICATION_EMAIL_BODY: "<h1>Neuer Benutzer</h1><p>Hallo, ein neuer Benutzer hat sich bei %1$s registriert.</p><p><ul><li>Benutzername: <b>%2$s</b></li><li>E-Mail: <b>%3$s</b></ul><p><p><br/><a href=\"%4$s\" class=\"btn-primary\">%1$s aufrufen</a><br/><br/></p>"
EMAIL_FOOTER: "GetGrav.org"
ACTIVATION_LINK_EXPIRED: "Aktivierungslink ist abgelaufen"
USER_ACTIVATED_SUCCESSFULLY: "Nutzer erfolgreich aktiviert"
INVALID_REQUEST: "Ungültige Anfrage"
USER_REGISTRATION: "Nutzer Registrierung"
USER_REGISTRATION_ENABLED_HELP: "Aktiviere die Nutzer Registrierung"
VALIDATE_PASSWORD1_AND_PASSWORD2: "Überprüfe das doppelt eingegebene Passwort"
SEND_ACTIVATION_EMAIL: "Aktivierungs E-Mail senden"
SEND_NOTIFICATION_EMAIL: "Benachtichtigungs E-Mail senden"
SEND_WELCOME_EMAIL: "Sende eine Willkommens E-Mail"
DEFAULT_VALUES: "Standard Werte"
ACTIVATION_LINK_EXPIRED: "Der Aktivierungslink ist abgelaufen."
USER_ACTIVATED_SUCCESSFULLY: "Benutzer wurde erfolgreich aktiviert."
INVALID_REQUEST: "Ungültige Anfrage."
USER_REGISTRATION: "Benutzerregistrierung."
USER_REGISTRATION_ENABLED_HELP: "Benutzerregistrierung aktivieren."
VALIDATE_PASSWORD1_AND_PASSWORD2: "Doppelt eingegebenes Passwort bestätigen."
VALIDATE_PASSWORD1_AND_PASSWORD2_HELP: "Validieren und vergleichen Sie zwei verschiedene Felder für die Passwörter mit den Namen `password1` und` password2`. Aktivieren Sie diese Option, wenn Sie im Anmeldeformular zwei Passwortfelder haben."
SET_USER_DISABLED: "Benutzer als deaktiviert festlegen"
SET_USER_DISABLED_HELP: "Am besten zusammen mit der E-Mail 'Aktivierungs-E-Mail senden' verwenden. Fügt den Benutzer zu Grav hinzu, setzt ihn jedoch als deaktiviert"
LOGIN_AFTER_REGISTRATION: "Benutzer nach Registrierung anmelden."
LOGIN_AFTER_REGISTRATION_HELP: "Meldet den Benutzer direkt nach der Registrierung an. Wenn eine E-Mail-Aktivierung erforderlich ist, wird der Benutzer sofort nach der Aktivierung des Kontos angemeldet."
SEND_ACTIVATION_EMAIL: "Aktivierungs-E-Mail senden"
SEND_ACTIVATION_EMAIL_HELP: "Sendet eine E-Mail an den Benutzer, um seinen Account zu aktivieren. Aktivieren Sie die Option 'Benutzer als deaktiviert festlegen', wenn Sie diese Funktion verwenden, sodass der Benutzer als deaktiviert festgelegt wird und eine E-Mail gesendet wird, um das Konto zu aktivieren"
SEND_NOTIFICATION_EMAIL: "Benachrichtigungs-E-Mail senden"
SEND_NOTIFICATION_EMAIL_HELP: "Benachrichtigt den Seiten-Administrator, dass sich ein neuer Benutzer registriert hat. Die E-Mail wird in der E-Mail-Plugin-Konfiguration an das Feld 'to' gesendet"
SEND_WELCOME_EMAIL: "Sende Willkommens-E-Mail"
SEND_WELCOME_EMAIL_HELP: "Sendet eine E-Mail an den neu registrierten Benutzer"
DEFAULT_VALUES: "Standardwerte"
DEFAULT_VALUES_HELP: "Liste der Feldnamen und der zugehörigen Werte, die standardmäßig dem Benutzerprofil (Yaml-Datei) hinzugefügt werden, ohne vom Benutzer konfiguriert zu werden. Trennen Sie mehrere Werte durch ein Komma ohne Leerzeichen zwischen den Werten."
ADDITIONAL_PARAM_KEY: "Parameter"
ADDITIONAL_PARAM_VALUE: "Wert"
REGISTRATION_FIELDS: "Registrierungsfelder"
REGISTRATION_FIELDS_HELP: "Fügen Sie die Felder hinzu, die der Yaml-Datei des Benutzers hinzugefügt werden. Felder, die hier nicht aufgeführt sind, werden nicht hinzugefügt, auch wenn sie im Anmeldeformular vorhanden sind."
REGISTRATION_FIELD_KEY: "Feldname"
REDIRECT_AFTER_LOGIN: "Umleitung nach Login"
REDIRECT_AFTER_REGISTRATION: "Umleitung nach Registrierung"
REDIRECT_AFTER_LOGIN: "Weiterleitung nach Login."
REDIRECT_AFTER_LOGIN_HELP: "Benutzerdefinierte Seite zu der, nach erfolgreichem Login, weitergeleitet wird."
REDIRECT_AFTER_LOGOUT: "Weiterleitung nach Abmelden."
REDIRECT_AFTER_LOGOUT_HELP: "Benutzerdefinierte Seite zu der, nach dem Abmelden des Benutzers, weitergeleitet wird."
REDIRECT_AFTER_REGISTRATION: "Weiterleitung nach Registrierung."
REDIRECT_AFTER_REGISTRATION_HELP: "Benutzerdefinierte Seite zu der, nach Abschluss der Registrierung, weitergeleitet wird."
OPTIONS: "Optionen"
EMAIL_VALIDATION_MESSAGE: "Muss eine gültige E-Mail Adresse sein"
WELCOME: "Willkommen"
EMAIL_VALIDATION_MESSAGE: "Muss eine gültige E-Mail-Adresse sein."
PASSWORD_VALIDATION_MESSAGE: "Das Passwort muss mindestens eine Zahl und einen Groß- und Kleinbuchstaben sowie mindestens 8 oder mehr Zeichen enthalten."
TIMEOUT_HELP: "Legt das Sitzungszeitlimit in Sekunden fest, wenn 'Angemeldet bleiben' vom Benutzer aktiviert und überprüft wird. Das Minimum ist 604800, was einer Woche entspricht."
GROUPS_HELP: "Liste der Gruppen, denen neu registrierte Benutzer zugewiesen werden, falls vorhanden."
SITE_ACCESS_HELP: "Liste der Seiten, auf die neu registrierte Benutzer Zugriff haben. Beispiel: `login` -> `true`"
WELCOME: "Willkommen"
REDIRECT_AFTER_ACTIVATION: "Weiterleitung nach Benutzeraktivierung"
REDIRECT_AFTER_ACTIVATION_HELP: "Wird verwendet, wenn der Benutzer das Konto per E-Mail aktivieren muss. Einmal aktiviert, wird diese Seite angezeigt"
REGISTRATION_DISABLED: "Registrierung deaktiviert"
USE_PARENT_ACL_LABEL: "Übergeordnete Zugriffsregeln verwenden"
USE_PARENT_ACL_HELP: "Verwende übergeordneten Zugriffsregeln, wenn keine Regeln definiert sind."
PROTECT_PROTECTED_PAGE_MEDIA_LABEL: Schütze auch die Medien, von Login-geschützten Seiten."
PROTECT_PROTECTED_PAGE_MEDIA_HELP: "Wenn diese Option aktiviert ist, ist das Medium einer, durch einen Login geschützten, Seite ebenfalls Login-geschützt und kann nur angezeigt werden, wenn Sie angemeldet sind."
SECURITY_TAB: "Sicherheit"
MAX_RESETS_COUNT: "Max. Anzahl an Versuchen, um das Passwort zurücksetzen."
MAX_RESETS_COUNT_HELP: "'Flood protection' Einstellung, um das Passwort zurücksetzen (0 - nicht begrenzt)"
MAX_RESETS_INTERVAL: "Max. Intervall an Versuchen, um das Passwort zurücksetzen."
MAX_RESETS_INTERVAL_HELP: "Zeitintervall für die max. Anzahl an Versuchen das Passwort zurückzusetzen."
FORGOT_CANNOT_RESET_IT_IS_BLOCKED: "Kann das Passwort für %s nicht zurücksetzen, die Funktion zum Zurücksetzen des Passworts wurde vorübergehend blockiert. Bitte versuchen Sie es später erneut (maximal %s Minuten)"
MAX_LOGINS_COUNT: "Max. Anzahl an Anmeldeversuche"
MAX_LOGINS_COUNT_HELP: "'Flood protection' Einstellung (0 - nicht begrenzt)"
MAX_LOGINS_INTERVAL: "Max. Login Intervall"
MAX_LOGINS_INTERVAL_HELP: "Das max. Zeitintervall für Anmeldeversuche."
TOO_MANY_LOGIN_ATTEMPTS: "Zu viele fehlgeschlagene Anmeldeversuche in der konfigurierten Zeit (%s Minuten)"
SECONDS: "Sekunden"
RESETS: "Resets"
ATTEMPTS: "Versuche"
ROUTES: "Pfade"
ROUTE_FORGOT: "Pfad für 'Passwort vergessen'"
ROUTE_RESET: "Pfad für 'Passwort zurücksetzen'"
ROUTE_PROFILE: "Benutzerprofil Pfad"
ROUTE_ACTIVATE: "Benutzeraktivierung Pfad"
LOGGED_OUT: "Sie wurden erfolgreich abgemeldet…"
PAGE_RESTRICTED: "Der Zugang ist eingeschränkt, bitte melden Sie sich an…"
DYNAMIC_VISIBILITY: "Dynamische Seitensichtbarkeit"
DYNAMIC_VISIBILITY_HELP: "Ermöglicht die dynamische Verarbeitung der Seitensichtbarkeit basierend auf Zugriffsregeln, wenn 'login.visibility_requires_access' auf einer Seite auf 'true' gesetzt ist."
USER_IS_REMOTE_ONLY: "Dieser Benutzer wurde mit einem Remotedienst authentifiziert, sodass sein Profil nicht gespeichert werden kann."
2FA_TITLE: "2-Faktor-Authentifizierung"
2FA_INSTRUCTIONS: "##### 2-Faktor-Authentifizierung\n Sie haben **2FA** für dieses Konto aktiviert. Bitte verwenden Sie Ihre **2FA** App, um den aktuellen **6-stelligen Code ** einzugeben, damit der Anmeldevorgang abgeschlossen werden kann."
2FA_REGEN_HINT: "Wenn Sie das Secret neu generieren, müssen Sie Ihre Authentifikator-App aktualisieren."
2FA_FAILED: "Ungültiger 2-Faktor-Authentifizierungscode, bitte versuchen Sie es erneut…"
2FA_ENABLED: "2FA aktiviert"
2FA_CODE_INPUT: "000000"
2FA_SECRET: "2FA Secret"
2FA_SECRET_HELP: "Scannen Sie diesen QR-Code in Ihre [Authenticator App](https://learn.getgrav.org/admin-panel/2fa#apps). Es ist sinnvoll, das Secret an einem sicheren Ort zu sichern, falls Sie Ihre App neu installieren müssen. In der [Grav Doku](https://learn.getgrav.org/admin-panel/2fa) finden Sie weitere Informationen."
2FA_REGENERATE: "Neu generieren"
BTN_CANCEL: "Abbrechen"
+5 -2
View File
@@ -50,7 +50,8 @@ PLUGIN_LOGIN:
NOTIFICATION_EMAIL_BODY: "<h1>New User</h1><p>Hi, a new user registered on %1$s.</p><p><ul><li>Username: <b>%2$s</b></li><li>Email: <b>%3$s</b></ul><p><p><br/><a href=\"%4$s\" class=\"btn-primary\">Visit %1$s</a><br/><br/></p>"
EMAIL_FOOTER: "GetGrav.org"
ACTIVATION_LINK_EXPIRED: "Activation link expired"
USER_ACTIVATED_SUCCESSFULLY: "User activated successfully"
USER_ACTIVATED_SUCCESSFULLY: "User account activated successfully"
USER_ACTIVATED_SUCCESSFULLY_NOT_ENABLED: "User account activated but account is being reviewed"
INVALID_REQUEST: "Invalid request"
USER_REGISTRATION: "User Registration"
USER_REGISTRATION_ENABLED_HELP: "Enable the user registration"
@@ -126,4 +127,6 @@ PLUGIN_LOGIN:
2FA_SECRET: "2FA Secret"
2FA_SECRET_HELP: "Scan this QR code into your [Authenticator App](https://learn.getgrav.org/admin-panel/2fa#apps). Also it's a good idea to backup the secret in a safe location, in case you need to reinstall your app. Check the [Grav docs](https://learn.getgrav.org/admin-panel/2fa) for more information "
2FA_REGENERATE: "Regenerate"
BTN_CANCEL: "Cancel"
BTN_CANCEL: "Cancel"
MANUALLY_ENABLE: "Manually Enable"
MANUALLY_ENABLE_HELP: "When using 'activation email' and 'notification email', you can ensure the user can self activate, but requires manually enabling the user to login"
+24 -3
View File
@@ -18,6 +18,7 @@ PLUGIN_LOGIN:
FORGOT_INSTRUCTIONS_SENT_VIA_EMAIL: "Инструкции по сбросу пароля были отправлены по электронной почте"
FORGOT_FAILED_TO_EMAIL: "Не удалось отправить инструкции по электронной почте, повторите попытку позже"
FORGOT_CANNOT_RESET_EMAIL_NO_EMAIL: "Не удается сбросить пароль для %s, адресс электронной почты не установлен"
FORGOT_CANNOT_RESET_EMAIL_NO_PASSWORD: "Невозможно сбросить пароль для %s, этот email связан с удаленной учетной записью"
FORGOT_USERNAME_DOES_NOT_EXIST: "Пользователь с именем <b>%s</b> не существует"
FORGOT_EMAIL_NOT_CONFIGURED: "Невозможно сбросить пароль. Этот сайт не настроен для отправки писем"
FORGOT_EMAIL_SUBJECT: "%s Запрос на сброс пароля"
@@ -34,14 +35,17 @@ PLUGIN_LOGIN:
ROUTE_REGISTER_HELP: "Путь к пользовательской странице регистрации. Заполните, если вы хотите использовать встроенную страницу регистрации. Оставьте его пустым, если у вас есть собственная регистрационная форма"
USERNAME_NOT_VALID: "Имя пользователя должно быть от 3 до 16 символов, включая строчные буквы, цифры, символы подчеркивания и дефисы. Прописные буквы, пробелы и специальные символы не допускаются"
USERNAME_NOT_AVAILABLE: "Имя пользователя %s уже существует, выберите другое имя пользователя"
EMAIL_NOT_AVAILABLE: "Адрес электронной почты %s уже существует, выберите другой адрес электронной почты"
PASSWORD_NOT_VALID: "Пароль должен содержать как минимум одно число, одну прописную и строчную букву, и быть не менее 8 символов"
PASSWORDS_DO_NOT_MATCH: "Пароли не совпадают. Дважды проверьте, что вы дважды ввели тот же пароль"
USER_NEEDS_EMAIL_FIELD: "Пользователю требуется поле электронной почты"
EMAIL_SENDING_FAILURE: "Произошла ошибка при отправке письма"
ACTIVATION_EMAIL_SUBJECT: "Активируйте свою учетную запись %s"
ACTIVATION_EMAIL_BODY: "Привет %s, перейдите <a href=\"%s\">сюда</a> для активации вашей учетной записи %s"
ACTIVATION_NOTICE_MSG: "Привет %s, ваша учетная запись создана, пожалуйста проверьте электронную почту, чтобы полностью ее активировать"
WELCOME_EMAIL_SUBJECT: "Добро пожаловать в %s"
WELCOME_EMAIL_BODY: "Привет %s, добро пожаловать в %s!"
WELCOME_NOTICE_MSG: "Привет %s, ваша учетная запись была успешно создана"
NOTIFICATION_EMAIL_SUBJECT: "Новый пользователь %s"
NOTIFICATION_EMAIL_BODY: "Привет, новый пользователь, зарегистрированный на %s. Имя пользователя: %s, email: %s"
EMAIL_FOOTER: "GetGrav.org"
@@ -54,8 +58,8 @@ PLUGIN_LOGIN:
VALIDATE_PASSWORD1_AND_PASSWORD2_HELP: "Подтвердить и сравнить два разных поля для паролей с именами `password1` и` password2`. Включите это, если у вас есть два поля пароля в регистрационной форме"
SET_USER_DISABLED: "Установить пользователя как отключенный"
SET_USER_DISABLED_HELP: "Лучше всего использовать электронную почту «Отправить электронную почту активации». Добавляет пользователя в Grav, но устанавливает его как отключенный"
LOGIN_AFTER_REGISTRATION: "Воход в систему после регистрации"
LOGIN_AFTER_REGISTRATION_HELP: "Автоматический воход в систему после регистрации. Если требуется активация электронной почты, пользователь будет входить в систему сразу после активации учетной записи"
LOGIN_AFTER_REGISTRATION: "Вход в систему после регистрации"
LOGIN_AFTER_REGISTRATION_HELP: "Автоматический вход в систему после регистрации. Если требуется активация электронной почты, пользователь будет входить в систему сразу после активации учетной записи"
SEND_ACTIVATION_EMAIL: "Отправить письмо активации"
SEND_ACTIVATION_EMAIL_HELP: "Отправляет электронное письмо пользователю для активации своей учетной записи. Включите параметр «Установить пользователя как отключенный» при использовании этой функции, чтобы пользователь был отключен, и для активации учетной записи будет отправлено электронное письмо"
SEND_NOTIFICATION_EMAIL: "Отправить уведомление по электронной почте"
@@ -71,6 +75,8 @@ PLUGIN_LOGIN:
REGISTRATION_FIELD_KEY: "Имя поля"
REDIRECT_AFTER_LOGIN: "Перенаправление после входа в систему"
REDIRECT_AFTER_LOGIN_HELP: "Пользовательский маршрут для перенаправления после входа в систему"
REDIRECT_AFTER_LOGOUT: "Перенаправление после выхода из системы"
REDIRECT_AFTER_LOGOUT_HELP: "Пользовательский маршрут для перенаправления после выхода из системы"
REDIRECT_AFTER_REGISTRATION: "Перенаправление после регистрации"
REDIRECT_AFTER_REGISTRATION_HELP: "Пользовательский маршрут для перенаправления после регистрации"
OPTIONS: "Опции"
@@ -105,4 +111,19 @@ PLUGIN_LOGIN:
ROUTE_FORGOT: "Забыли пароль"
ROUTE_RESET: "Сброса пароля"
ROUTE_PROFILE: "Профиля пользователя"
ROUTE_ACTIVATE: "Активации пользователя"
ROUTE_ACTIVATE: "Активации пользователя"
LOGGED_OUT: "Вы успешно вышли из системы..."
PAGE_RESTRICTED: "Доступ ограничен, войдите в систему..."
DYNAMIC_VISIBILITY: "Динамическая видимость страницы"
DYNAMIC_VISIBILITY_HELP: "Позволяет динамически обрабатывать видимость страницы на основе правил доступа, если для параметра login.visibility_requires_access установлено значение true на странице"
USER_IS_REMOTE_ONLY: "Этот пользователь аутентифицирован с помощью удаленного сервиса, поэтому профиль не может быть сохранен"
2FA_TITLE: "2-факторная аутентификация"
2FA_INSTRUCTIONS: "##### 2-факторная аутентификация\nВ вашем аккаунте включена **2FA**. Пожалуйста, используйте свое **2FA** приложение для ввода текущего **6-значного кода** для завершения процесса входа в систему."
2FA_REGEN_HINT: "Чтобы восстановить секрет, вам потребуется обновить приложение для аутентификации"
2FA_FAILED: "Недопустимый код проверки подлинности 2-факторной аутентификации, повторите попытку...."
2FA_ENABLED: "2FA Включена"
2FA_CODE_INPUT: "000000"
2FA_SECRET: "2FA Секрет"
2FA_SECRET_HELP: "Сканируйте этот QR-код в свое [Приложение аутентификации](https://learn.getgrav.org/admin-panel/2fa#apps). Также рекомендуется сохранить секрет в безопасном месте, если вам прийдется переустановить приложение. Проверьте [Grav docs](https://learn.getgrav.org/admin-panel/2fa) для дополнительной информации "
2FA_REGENERATE: "Сгенерировать повторно"
BTN_CANCEL: "Отмена"
Executable → Regular
+98 -26
View File
@@ -7,6 +7,8 @@
*/
namespace Grav\Plugin;
use Grav\Common\Data\Data;
use Grav\Common\Debugger;
use Grav\Common\Grav;
use Grav\Common\Language\Language;
use Grav\Common\Page\Page;
@@ -330,7 +332,6 @@ class LoginPlugin extends Plugin
if (!$page) {
$page = new Page;
$page->init(new \SplFileInfo(__DIR__ . '/pages/register.md'));
$page->template('form');
$page->slug(basename($route));
$pages->addPage($page, $route);
@@ -354,6 +355,9 @@ class LoginPlugin extends Plugin
$token = $uri->param('token');
$user = User::load($username);
$redirect_route = $this->config->get('plugins.login.user_registration.redirect_after_activation');
$redirect_code = null;
if (empty($user->activation_token)) {
$message = $this->grav['language']->translate('PLUGIN_LOGIN.INVALID_REQUEST');
$messages->add($message, 'error');
@@ -365,13 +369,17 @@ class LoginPlugin extends Plugin
$message = $this->grav['language']->translate('PLUGIN_LOGIN.ACTIVATION_LINK_EXPIRED');
$messages->add($message, 'error');
} else {
$user['state'] = 'enabled';
if ($this->config->get('plugins.login.user_registration.options.manually_enable', false)) {
$message = $this->grav['language']->translate('PLUGIN_LOGIN.USER_ACTIVATED_SUCCESSFULLY_NOT_ENABLED');
} else {
$user['state'] = 'enabled';
$message = $this->grav['language']->translate('PLUGIN_LOGIN.USER_ACTIVATED_SUCCESSFULLY');
}
$messages->add($message, 'info');
unset($user['activation_token']);
$user->save();
$message = $this->grav['language']->translate('PLUGIN_LOGIN.USER_ACTIVATED_SUCCESSFULLY');
$messages->add($message, 'info');
if ($this->config->get('plugins.login.user_registration.options.send_welcome_email', false)) {
$this->login->sendWelcomeEmail($user);
}
@@ -380,7 +388,18 @@ class LoginPlugin extends Plugin
}
if ($this->config->get('plugins.login.user_registration.options.login_after_registration', false)) {
$this->login->login(['username' => $username], ['after_registration' => true]);
$loginEvent = $this->login->login(['username' => $username], ['after_registration' => true, 'return_event' => true]);
// If there's no activation redirect, get one from login.
if (!$redirect_route) {
$message = $loginEvent->getMessage();
if ($message) {
$messages->add($message, $loginEvent->getMessageType());
}
$redirect_route = $loginEvent->getRedirect();
$redirect_code = $loginEvent->getRedirectCode();
}
}
}
} else {
@@ -389,8 +408,7 @@ class LoginPlugin extends Plugin
}
}
$redirect_route = $this->config->get('plugins.login.user_registration.redirect_after_activation', '/');
$this->grav->redirect($redirect_route);
$this->grav->redirectLangSafe($redirect_route ?: '/', $redirect_code);
}
/**
@@ -430,7 +448,6 @@ class LoginPlugin extends Plugin
if (!$page) {
$page = new Page;
$page->init(new \SplFileInfo(__DIR__ . '/pages/unauthorized.md'));
$page->template('default');
$page->slug(basename($route));
$pages->addPage($page, $route);
@@ -521,7 +538,7 @@ class LoginPlugin extends Plugin
// User is not logged in; redirect to login page.
if ($this->redirect_to_login && $this->route && !$authorized) {
$this->grav->redirect($this->route, 302);
$this->grav->redirectLangSafe($this->route, 302);
}
/** @var Twig $twig */
@@ -637,8 +654,14 @@ class LoginPlugin extends Plugin
throw new \RuntimeException($language->translate('PLUGIN_LOGIN.USER_REGISTRATION_DISABLED'));
}
$form->validate();
$form->filter();
/** @var Data $form_data */
$form_data = $form->getData();
// Check for existing username
$username = $form->value('username');
$username = $form_data->get('username');
$existing_username = User::find($username,['username']);
if ($existing_username->exists()) {
$this->grav->fireEvent('onFormValidationError', new Event([
@@ -653,7 +676,7 @@ class LoginPlugin extends Plugin
}
// Check for existing email
$email = $form->value('email');
$email = $form_data->get('email');
$existing_email = User::find($email,['email']);
if ($existing_email->exists()) {
$this->grav->fireEvent('onFormValidationError', new Event([
@@ -675,7 +698,7 @@ class LoginPlugin extends Plugin
if ($this->config->get('plugins.login.user_registration.options.validate_password1_and_password2',
false)
) {
if ($form->value('password1') !== $form->value('password2')) {
if ($form_data->get('password1') !== $form_data->get('password2')) {
$this->grav->fireEvent('onFormValidationError', new Event([
'form' => $form,
'message' => $language->translate('PLUGIN_LOGIN.PASSWORDS_DO_NOT_MATCH')
@@ -684,7 +707,7 @@ class LoginPlugin extends Plugin
return;
}
$data['password'] = $form->value('password1');
$data['password'] = $form_data->get('password1');
}
$fields = (array)$this->config->get('plugins.login.user_registration.fields', []);
@@ -706,8 +729,8 @@ class LoginPlugin extends Plugin
}
}
if (!isset($data[$field]) && $form->value($field)) {
$data[$field] = $form->value($field);
if (!isset($data[$field]) && $form_data->get($field)) {
$data[$field] = $form_data->get($field);
}
}
@@ -719,6 +742,7 @@ class LoginPlugin extends Plugin
$this->grav->fireEvent('onUserLoginRegisterData', new Event(['data' => &$data]));
$user = $this->login->register($data);
$this->grav->fireEvent('onUserLoginRegisteredUser', new Event(['user' => &$user]));
$fullname = $user->fullname ?: $user->username;
@@ -739,13 +763,26 @@ class LoginPlugin extends Plugin
$this->grav->fireEvent('onUserLoginRegistered', new Event(['user' => $user]));
$redirect = $this->config->get('plugins.login.user_registration.redirect_after_registration');
$redirect_code = null;
if (isset($data['state']) && $data['state'] === 'enabled' && $this->config->get('plugins.login.user_registration.options.login_after_registration', false)) {
$this->login->login(['username' => $username], ['after_registration' => true], ['user' => $user]);
$loginEvent = $this->login->login(['username' => $username], ['after_registration' => true], ['user' => $user, 'return_event' => true]);
// If there's no registration redirect, get one from login.
if (!$redirect) {
$message = $loginEvent->getMessage();
if ($message) {
$messages->add($message, $loginEvent->getMessageType());
}
$redirect = $loginEvent->getRedirect();
$redirect_code = $loginEvent->getRedirectCode();
}
}
$redirect = $this->config->get('plugins.login.user_registration.redirect_after_registration', false);
if ($redirect) {
$this->grav->redirect($redirect);
$this->grav->redirectLangSafe($redirect, $redirect_code);
}
}
@@ -762,6 +799,12 @@ class LoginPlugin extends Plugin
$user = $this->grav['user'];
$language = $this->grav['language'];
$form->validate();
$form->filter();
/** @var Data $form_data */
$form_data = $form->getData();
// Don't save if user doesn't exist
if (!$user->exists()) {
$this->grav->fireEvent('onFormValidationError', new Event([
@@ -773,7 +816,7 @@ class LoginPlugin extends Plugin
}
// Stop overloading of username
$username = $form->value('username');
$username = $form->data('username');
if (isset($username)) {
$this->grav->fireEvent('onFormValidationError', new Event([
'form' => $form,
@@ -787,7 +830,7 @@ class LoginPlugin extends Plugin
}
// Check for existing email
$email = $form->value('email');
$email = $form->data('email');
$existing_email = User::find($email,['email']);
if ($user->username != $existing_email->username && $existing_email->exists()) {
$this->grav->fireEvent('onFormValidationError', new Event([
@@ -801,7 +844,15 @@ class LoginPlugin extends Plugin
return;
}
$user->merge($form->getData()->toArray());
$fields = (array)$this->config->get('plugins.login.user_registration.fields', []);
$data = [];
foreach ($fields as $field) {
if (!isset($data[$field]) && $form_data->get($field)) {
$data[$field] = $form_data->get($field);
}
}
$user->merge($data);
try {
$user->save();
@@ -865,17 +916,24 @@ class LoginPlugin extends Plugin
// Only use remember me if user isn't set and feature is enabled.
if ($this->grav['config']->get('plugins.login.rememberme.enabled') && !$event->getUser()->exists()) {
/** @var Debugger $debugger */
$debugger = $this->grav['debugger'];
/** @var RememberMe $rememberMe */
$rememberMe = $this->grav['login']->rememberMe();
$username = $rememberMe->login();
if ($rememberMe->loginTokenWasInvalid()) {
// Token was invalid. We will display error page as this was likely an attack.
$debugger->addMessage('Remember Me: Stolen token!');
throw new \RuntimeException($this->grav['language']->translate('PLUGIN_LOGIN.REMEMBER_ME_STOLEN_COOKIE'), 403);
}
if ($username === false) {
// User has not been remembered, there is no point of continuing.
$debugger->addMessage('Remember Me: No token matched.');
$event->setStatus($event::AUTHENTICATION_FAILURE);
$event->stopPropagation();
@@ -883,8 +941,25 @@ class LoginPlugin extends Plugin
}
// Allow remember me to work with different login methods.
$user = User::load($username, false);
$event->setCredential('username', $username);
$event->setUser(User::load($username, false));
$event->setUser($user);
if (!$user->exists()) {
$debugger->addMessage('Remember Me: User does not exist');
$event->setStatus($event::AUTHENTICATION_FAILURE);
$event->stopPropagation();
return;
}
$debugger->addMessage('Remember Me: Authenticated!');
$event->setStatus($event::AUTHENTICATION_SUCCESS);
$event->stopPropagation();
return;
}
}
@@ -967,9 +1042,6 @@ class LoginPlugin extends Plugin
$username = $event->getUser()->get('username');
if ($event->getCredential('rememberme')) {
$login->rememberMe()->createCookie($username);
} else {
$login->rememberMe()->getStorage()->cleanAllTriplets($username);
$login->rememberMe()->clearCookie();
}
}
}
+1
View File
@@ -50,5 +50,6 @@ user_registration:
set_user_disabled: false # Set this `true` if you want a user to activate their account via email
login_after_registration: false # Automatically login after registration
send_activation_email: false # Send an email that requires a special link to be clicked in order to activate the account
manually_enable: false # When using activation email, don't enable until an admin does it manually
send_notification_email: false # Send an email to the site administrator to indicate a user has registered
send_welcome_email: false # Send a welcome email to the user (probably should not be used with `send_activation_email`
@@ -1,3 +1,6 @@
{% set client_side_validation = form.client_side_validation is not null ? form.client_side_validation : config.plugins.form.client_side_validation|default(true) %}
{% set inline_errors = form.inline_errors is not null ? form.inline_errors : config.plugins.form.inline_errors(false) %}
<section id="grav-login">
{{ content|raw }}
+5 -2
View File
@@ -1,3 +1,6 @@
{% set client_side_validation = form.client_side_validation is not null ? form.client_side_validation : config.plugins.form.client_side_validation|default(true) %}
{% set inline_errors = form.inline_errors is not null ? form.inline_errors : config.plugins.form.inline_errors(false) %}
<section id="grav-login">
{% if page.template == 'login' or show_login_form %}
@@ -18,7 +21,7 @@
{% include template %}
{% endfor %}
<form method="post" action="{{ (base_url_relative ~ uri.path)|e }}" class="{{ form_outer_classes }}">
<form method="post" action="{{ (base_url_relative ~ uri.path)|e }}" class="{{ form_outer_classes }}">
{# OLD WAY OF INCLUDING 3RD PARTY LOGIN OPTIONS #}
{% if grav.twig.plugins_hooked_loginPage %}
@@ -58,4 +61,4 @@
{% endif %}
</section>
</section>
View File
@@ -1,4 +1,6 @@
{% set form = forms('login-twofa') %}
{% set client_side_validation = form.client_side_validation is not null ? form.client_side_validation : config.plugins.form.client_side_validation|default(true) %}
{% set inline_errors = form.inline_errors is not null ? form.inline_errors : config.plugins.form.inline_errors(false) %}
<form method="post" action="{{ (base_url_relative ~ uri.path)|e }}" class="{{ form_outer_classes }}">
{% for field in form.fields %}
@@ -1,4 +1,7 @@
{% if uri.param('token') and uri.param('task') %}
{% set client_side_validation = form.client_side_validation is not null ? form.client_side_validation : config.plugins.form.client_side_validation|default(true) %}
{% set inline_errors = form.inline_errors is not null ? form.inline_errors : config.plugins.form.inline_errors(false) %}
<section id="grav-login">
{{ content|raw }}
@@ -0,0 +1 @@
{% extends 'form.html.twig' %}
@@ -0,0 +1 @@
{% extends 'default.html.twig' %}
@@ -0,0 +1,9 @@
composer.lock
vendor
nbproject
.idea
.buildpath
.project
.DS_Store
.*.sw*
.*.un~
@@ -0,0 +1,14 @@
language: php
php:
- 5.4
- 5.5
- 5.6
- 7.0
- 7.1
- hhvm
install:
- travis_retry composer install --no-interaction
- composer info -i
script: vendor/bin/phpunit --bootstrap tests/bootstrap.php --configuration tests/phpunit.xml tests
@@ -0,0 +1,4 @@
.idea
nbproject
example/tokens
/vendor/
View File
View File
View File
View File
View File
View File
View File
View File
@@ -0,0 +1,3 @@
/composer.lock
/phpunit.xml
/vendor/
+41
View File
@@ -0,0 +1,41 @@
sudo: false
language: php
cache:
directories:
- $HOME/.composer/cache
- $HOME/.local
- vendor
matrix:
fast_finish: true
include:
- php: 7.1
env:
- EXECUTE_CS_CHECK=true
- EXECUTE_TEST_COVERALLS=true
- PATH="$HOME/.local/bin:$PATH"
- php: nightly
allow_failures:
- php: nightly
before_install:
- if [[ $EXECUTE_TEST_COVERALLS != 'true' ]]; then phpenv config-rm xdebug.ini || return 0 ; fi
- composer self-update
- if [[ $EXECUTE_TEST_COVERALLS == 'true' ]]; then composer require --dev --no-update satooshi/php-coveralls:dev-master ; fi
install:
- travis_retry composer install --no-interaction
- composer info -i
script:
- if [[ $EXECUTE_TEST_COVERALLS == 'true' ]]; then vendor/bin/phpunit --coverage-clover clover.xml ; fi
- if [[ $EXECUTE_TEST_COVERALLS != 'true' ]]; then vendor/bin/phpunit ; fi
- if [[ $EXECUTE_CS_CHECK == 'true' ]]; then vendor/bin/phpcs ; fi
after_script:
- if [[ $EXECUTE_TEST_COVERALLS == 'true' ]]; then vendor/bin/coveralls ; fi
notifications:
email: true
@@ -0,0 +1,189 @@
## Ignore Visual Studio temporary files, build results, and
## files generated by popular Visual Studio add-ons.
# User-specific files
*.suo
*.user
*.sln.docstates
# Build results
[Dd]ebug/
[Dd]ebugPublic/
[Rr]elease/
[Rr]eleases/
x64/
x86/
build/
bld/
[Bb]in/
[Oo]bj/
# Roslyn cache directories
*.ide/
# MSTest test Results
[Tt]est[Rr]esult*/
[Bb]uild[Ll]og.*
#NUNIT
*.VisualState.xml
TestResult.xml
# Build Results of an ATL Project
[Dd]ebugPS/
[Rr]eleasePS/
dlldata.c
*_i.c
*_p.c
*_i.h
*.ilk
*.meta
*.obj
*.pch
*.pdb
*.pgc
*.pgd
*.rsp
*.sbr
*.tlb
*.tli
*.tlh
*.tmp
*.tmp_proj
*.log
*.vspscc
*.vssscc
.builds
*.pidb
*.svclog
*.scc
# Chutzpah Test files
_Chutzpah*
# Visual C++ cache files
ipch/
*.aps
*.ncb
*.opensdf
*.sdf
*.cachefile
# Visual Studio profiler
*.psess
*.vsp
*.vspx
# TFS 2012 Local Workspace
$tf/
# Guidance Automation Toolkit
*.gpState
# ReSharper is a .NET coding add-in
_ReSharper*/
*.[Rr]e[Ss]harper
*.DotSettings.user
# JustCode is a .NET coding addin-in
.JustCode
# TeamCity is a build add-in
_TeamCity*
# DotCover is a Code Coverage Tool
*.dotCover
# NCrunch
_NCrunch_*
.*crunch*.local.xml
# MightyMoose
*.mm.*
AutoTest.Net/
# Web workbench (sass)
.sass-cache/
# Installshield output folder
[Ee]xpress/
# DocProject is a documentation generator add-in
DocProject/buildhelp/
DocProject/Help/*.HxT
DocProject/Help/*.HxC
DocProject/Help/*.hhc
DocProject/Help/*.hhk
DocProject/Help/*.hhp
DocProject/Help/Html2
DocProject/Help/html
# Click-Once directory
publish/
# Publish Web Output
*.[Pp]ublish.xml
*.azurePubxml
# TODO: Comment the next line if you want to checkin your web deploy settings
# but database connection strings (with potential passwords) will be unencrypted
*.pubxml
*.publishproj
# NuGet Packages
*.nupkg
# The packages folder can be ignored because of Package Restore
**/packages/*
# except build/, which is used as an MSBuild target.
!**/packages/build/
# If using the old MSBuild-Integrated Package Restore, uncomment this:
#!**/packages/repositories.config
# Windows Azure Build Output
csx/
*.build.csdef
# Windows Store app package directory
AppPackages/
# Others
sql/
*.Cache
ClientBin/
[Ss]tyle[Cc]op.*
~$*
*~
*.dbmdl
*.dbproj.schemaview
*.pfx
*.publishsettings
node_modules/
# RIA/Silverlight projects
Generated_Code/
# Backup & report files from converting an old project file
# to a newer Visual Studio version. Backup files are not needed,
# because we have git ;-)
_UpgradeReport_Files/
Backup*/
UpgradeLog*.XML
UpgradeLog*.htm
# SQL Server files
*.mdf
*.ldf
# Business Intelligence projects
*.rdl.data
*.bim.layout
*.bim_*.settings
# Microsoft Fakes
FakesAssemblies/
# Composer
/vendor
# .vs
.vs/
@@ -0,0 +1,18 @@
language: php
dist: trusty
matrix:
include:
- php: 5.3
dist: precise
php:
- 5.4
- 5.5
- 5.6
- 7.0
- 7.1
- hhvm
script:
- if [[ "$TRAVIS_PHP_VERSION" == '5.6' ]]; then phpunit --coverage-text tests ; fi
+2
View File
@@ -0,0 +1,2 @@
node_modules
package-lock.json
+30
View File
@@ -1,3 +1,33 @@
# v2.0.2
## 12/16/2018
1. [](#bugfix)
* Fixed an issue with checker not being initialized on Fatal Error
# v2.0.1
## 12/07/2018
1. [](#new)
* Added support for admin reporting available in Grav 1.6
1. [](#bugfix)
* Fixed issue with twig auto-escaping
* Fixed problems plugin potentially breaking CLI command if plugins get initialized
# v2.0.0
## 09/30/2018
1. [](#new)
* Completely rewritten to be much more flexible
* New _class_ based problems architecture for unified problem definition and reporting
* New `onProblemsInitialized()` plugin event for 3rd party plugins to add their own problem checks
* New more intuitive theme based on Spectre.css to display problems
* Storage of problem state to allow for displaying in admin plugin
* Now with 3 states `critical`, `warning`, and `notice`. Only critical will stop the site working.
* Added some new PHP module checks
* Added a new `umask` permission check
1. [](#improved)
* Implemented extra image checks [#17](https://github.com/getgrav/grav-plugin-problems/pull/17)
# v1.4.7
## 05/16/2017
+40 -39
View File
@@ -1,6 +1,6 @@
# Grav Problems Plugin
![Problems](assets/readme_1.png)
![Problems](assets/readme_1.jpg)
`Problems` is a [Grav](http://github.com/getgrav/grav) Plugin and allows to detect issues.
@@ -26,7 +26,7 @@ You should now have all the plugin files under
/your/site/grav/user/plugins/problems
>> NOTE: This plugin is a modular component for Grav which requires [Grav](http://github.com/getgrav/grav), the [Error](https://github.com/getgrav/grav-plugin-error) and [Problems](https://github.com/getgrav/grav-plugin-problems) plugins, and a theme to be installed in order to operate.
> NOTE: This plugin is a modular component for Grav which requires [Grav](http://github.com/getgrav/grav), the [Error](https://github.com/getgrav/grav-plugin-error) and [Problems](https://github.com/getgrav/grav-plugin-problems) plugins, and a theme to be installed in order to operate.
# Usage
@@ -34,28 +34,35 @@ You should now have all the plugin files under
`Problems` checks for the following common issues:
| Check | Description |
| :---------------------------------- | :-------------------------------------------------------------------------------------------------------- |
| Apache `mod_rewrite` | Checks to ensure `mod_rewrite` is enabled if you are running an Apache server. |
| PHP Version | Checks to make sure the PHP version being run by the server meets or exceeds Grav's minimum requirements. |
| PHP GD (Image Manipulation Library) | Checks to make sure that PHP GD is installed. |
| PHP Curl (Data Transfer Library) | Checks to make sure that PHP Curl is installed. |
| PHP OpenSSL (Secure Sockets Library) | Checks to make sure that PHP OpenSSL is installed. |
| PHP Mbstring (Multibyte String Library) | Checks to make sure that PHP Mbstring is installed. |
| .htaccess | Checks to make sure that there is an `.htaccess` file in Grav's root directory. |
| `bin/*` executable | Checks that all the files in the `bin/` folder are exectuable. |
| Cache | Checks the `/cache` folder's existence and verifies that it is writeable. |
| Logs | Checks the `/logs` folder's existence and verifies that it is writeable. |
| Images | Checks the `/images` folder's existence and verifies that it is writeable. |
| Assets | Checks the `/assets` folder's existence and verifies that it is writeable. |
| System | Checks the `/system` folder's existence. |
| Data | Checks the `/user/data` folder's existence and verifies that it is writeable. |
| Pages | Checks the `/user/images` folder's existence. |
| Config | Checks the `/user/config` folder's existence. |
| Error | Checks to make sure the **Error** plugin is installed in `/user/plugins/error`. |
| Plugins | Checks the `/user/plugins` folder's existence. |
| Themes | Checks the `/user/themes` folder's existence. |
| Vendor | Checks the `/vendor` folder's existence. |
| Check | Description |
| :-------------- | :-------------------------------------------------------------------------------- |
| Apache | `mod_rewrite` is enabled if you are running an Apache server. |
| PHP Version | PHP version being run by the server meets or exceeds Grav's minimum requirements. |
| PHP Modules | PHP GD library is installed. |
| | PHP Curl library is installed. |
| | PHP Ctype library is installed |
| | PHP Dom is library installed |
| | PHP OpenSSL library is installed |
| | PHP XML library is installed |
| | PHP Zip library is installed |
| | PHP Exif library is installed if Exif support is enabled |
| | PHP OpenSSL library is installed. |
| | PHP Mbstring library is installed. |
| Essential Files | `.htaccess` file in Grav's root directory. |
| | Checks that all the files in the `bin/` folder are exectuable. |
| | `/cache` folder's existence and verifies that it is writeable. |
| | `/logs` folder's existence and verifies that it is writeable. |
| | `/images` folder's existence and verifies that it is writeable. |
| | `/assets` folder's existence and verifies that it is writeable. |
| | `/system` folder's existence. |
| | `/tmp` folder's existence. |
| | `/user/data` folder's existence and verifies that it is writeable. |
| | `/user/images` folder's existence. |
| | `/user/config` folder's existence. |
| | **Error** plugin is installed in `/user/plugins/error`. |
| | `/user/plugins` folder's existence. |
| | `/user/themes` folder's existence. |
| | `/vendor` folder's existence. |
If an issue is discovered, you will be greeted with a page that lists these checks and whether or not your install passed or failed them. Green checks mean it passed, and a red x indicates that the there is something amiss with the item.
@@ -65,25 +72,19 @@ If a change is caught and the cache is refreshed, the plugin will loop through i
`Problems` gets also triggered if any fatal exception is caught.
# Updating
# CLI Command
As development for the Problems plugin continues, new versions may become available that add additional features and functionality, improve compatibility with newer Grav releases, and generally provide a better user experience. Updating Problems is easy, and can be done through Grav's GPM system, as well as manually.
Problems 2.0 comes with a handy CLI command so you can run the checks at any time
## GPM Update (Preferred)
```bash
bin/plugin problems check
```
The simplest way to update this plugin is via the [Grav Package Manager (GPM)](http://learn.getgrav.org/advanced/grav-gpm). You can do this with this by navigating to the root directory of your Grav install using your system's Terminal (also called command line) and typing the following:
You should see some output like this:
bin/gpm update problems
This command will check your Grav install to see if your Problems plugin is due for an update. If a newer release is found, you will be asked whether or not you wish to update. To continue, type `y` and hit enter. The plugin will automatically update and clear Grav's cache.
![](assets/cli.png)
## Manual Update
# Extending Plugins
Manually updating Problems is pretty simple. Here is what you will need to do to get this done:
* Delete the `your/site/user/plugins/problems` directory.
* Download the new version of the Problems plugin from either [GitHub](https://github.com/getgrav/grav-plugin-problems) or [GetGrav.org](http://getgrav.org/downloads/plugins#extras).
* Unzip the zip file in `your/site/user/plugins` and rename the resulting folder to `problems`.
* Clear the Grav cache. The simplest way to do this is by going to the root Grav directory in terminal and typing `bin/grav clear-cache`.
> Note: Any changes you have made to any of the files listed under this directory will also be removed and replaced by the new set. Any files located elsewhere (for example a YAML settings file placed in `user/config/plugins`) will remain intact.
You can also extend the problems plugin via the `onProblemsInitialized()` event. The event includes an array of Problems. Simply create your own Problems class that extends the `Grav\Plugin\Problems\Base\Problem` class and add it to the array.
Binary file not shown.

After

Width:  |  Height:  |  Size: 392 KiB

+11
View File
@@ -0,0 +1,11 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" preserveAspectRatio="xMidYMid" width="95.7" height="83.6" viewBox="0 0 47.85 41.8">
<defs>
<style>
.cls-1 {
fill: #000;
fill-rule: evenodd;
}
</style>
</defs>
<path d="M47.557,21.555 L37.591,32.529 C37.394,32.746 37.124,32.856 36.853,32.856 C36.614,32.856 36.374,32.770 36.183,32.597 C35.776,32.225 35.746,31.596 36.116,31.188 L45.472,20.884 L36.116,10.585 C35.746,10.177 35.776,9.547 36.183,9.176 C36.591,8.806 37.221,8.836 37.591,9.243 L47.556,20.212 C47.901,20.593 47.902,21.173 47.557,21.555 ZM18.874,41.119 C18.744,41.552 18.349,41.830 17.920,41.830 C17.825,41.830 17.729,41.816 17.633,41.788 C17.106,41.629 16.807,41.073 16.965,40.546 L28.923,0.648 C29.082,0.120 29.639,-0.175 30.164,-0.022 C30.691,0.137 30.991,0.694 30.832,1.221 L18.874,41.119 ZM11.614,32.594 C11.423,32.768 11.183,32.853 10.944,32.853 C10.673,32.853 10.403,32.743 10.206,32.527 L0.241,21.558 C-0.104,21.176 -0.105,20.596 0.241,20.215 L10.206,9.240 C10.576,8.833 11.207,8.803 11.614,9.173 C12.021,9.545 12.051,10.174 11.681,10.582 L2.325,20.886 L11.681,31.184 C12.051,31.592 12.021,32.223 11.614,32.594 Z" class="cls-1"/>
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

Some files were not shown because too many files have changed in this diff Show More