modif comtabilite

This commit is contained in:
2019-01-16 16:23:31 +01:00
parent 6b741a9666
commit 9312edc3ae
1844 changed files with 158848 additions and 55874 deletions
+18
View File
@@ -0,0 +1,18 @@
# EditorConfig is awesome: http://EditorConfig.org
# top-most EditorConfig file
root = true
# Unix-style newlines with a newline ending every file
[*]
charset = utf-8
end_of_line = lf
trim_trailing_whitespace = true
insert_final_newline = true
indent_style = space
indent_size = 4
# 2 space indentation
[*.yaml, *.yml]
indent_style = space
indent_size = 2
+14
View File
@@ -0,0 +1,14 @@
themes/grav/.sass-cache
.DS_Store
# Node Modules
**/node_modules/**
themes/grav/js/admin.js
themes/grav/js/vendor.js
themes/grav/js/*.map
.idea
tests/_output/*
tests/_support/_generated/*
tests/cache/*
tests/error.log
+140 -7
View File
@@ -1,3 +1,136 @@
# 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
1. [](#improved)
* Make order field to use context, not data
* Switched to new Grav Yaml class to support Native + Fallback YAML libraries
* Minor fix for `file` thumbnails display
* Requires Grav 1.5.1
# v1.8.8
## 08/17/2018
1. [](#improved)
* Support URI Params and Query attributes in Login redirect
* Added support for textarea value type in `array` field
* Added some new lang strings for Grav 1.5.0
1. [](#bugfix)
* Support params and querystring in login redirect
* Added field name nesting with tab field
# v1.8.7
## 07/31/2018
1. [](#bugfix)
* Fix for deleting 'extra' media files [grav#2100](https://githubcom/getgrav/grav/issues/2100)
# v1.8.6
## 07/13/2018
1. [](#bugfix)
* Force `html` for markdown preview [grav#2066](https://github.com/getgrav/grav/issues/2066)
* Add missing `authorizeTask()` checks in controller [#1483](https://github.com/getgrav/grav/issues/1483)
* Add support for `force_ssl` to admin URLs [#1479](https://github.com/getgrav/grav-plugin-admin/issues/1479)
# v1.8.5
## 06/20/2018
1. [](#bugfix)
* Fixed broken folder attribute on filepicker [#1465](https://github.com/getgrav/grav-plugin-admin/issues/1465)
* Added translation for system.session.initialize
* Slight updates on new translation strings
# v1.8.4
## 06/11/2018
1. [](#improved)
* Including EXIF JS library in the modules dependencies to fix orientation when uploading images
1. [](#bugfix)
* Initialize session on setup [#1451](https://github.com/getgrav/grav-plugin-admin/issues/1451)
* Force a `null` order when empty in the post request
* Fixed some 2FA form styling issues
# v1.8.3
## 05/31/2018
1. [](#new)
* Added support for selectize plugins as options in the selectize field
1. [](#bugfix)
* Fixed deep linking in admin after login [#1456](https://github.com/getgrav/grav-plugin-admin/issues/1456)
* Fixed Undefined property: `stdClass::$image` in v1.8.2 [#1454](https://github.com/getgrav/grav-plugin-admin/issues/1454)
* Pass media order when calling `task:listmedia`
# v1.8.2
## 05/24/2018
1. [](#new)
* Added custom object support for filepicker field
* Don't allow saving of a user with no local account file
* Controls for `list` field were not in sync between top and bottom
1. [](#improved)
* More subtle `fieldset` styling
1. [](#bugfix)
* Check if `$object->blueprints()` exists in `onAdminAfterSave`
* When creating first user, check `admin.login` not `site.login`
* Fix admin login redirects for multisite setups
* Fixed issue with filepicker field where images wouldn't properly merge with the current value if in a page header
* Fixed media delete for streams
# v1.8.1
## 05/15/2018
@@ -18,7 +151,7 @@
1. [](#improved)
* Added support for JavaScript `bottom` block [#1425](https://github.com/getgrav/grav-plugin-admin/pull/1425)
* Added better typography styling for blockquote and markdown in `display` field
* Vendor updates
* Vendor updates
1. [](#bugfix)
* Added missing MarkdownExtra strings [#1385](https://github.com/getgrav/grav-plugin-admin/pull/1385)
* Updated `blueprints.yaml` with missing `step` attribute [#1415](https://github.com/getgrav/grav-plugin-admin/pull/1415)
@@ -46,7 +179,7 @@
* Implemented workaround for required text fields [#1390](https://github.com/getgrav/grav-plugin-admin/issues/1390)
* Fixed highlight color in Firefox [getgrav/grav#1949](https://github.com/getgrav/grav/issues/1949)
* Fix for bad redirect on saving simplesearch (possibly others)
# v1.7.2
## 03/21/2018
@@ -58,8 +191,8 @@
* Optimization to stop admin for looking for pages in disabled plugins
* Added configuration option to choose if you want to use new 'inline' preview or `new tab'
1. [](#bugfix)
* Fix redirect bug when changing admin route to `admin-*`
* Changed Twig `|count` to `|length` filter [#1391](https://github.com/getgrav/grav-plugin-admin/issues/1391)
* Fix redirect bug when changing admin route to `admin-*`
* Changed Twig `|count` to `|length` filter [#1391](https://github.com/getgrav/grav-plugin-admin/issues/1391)
* Fix for page preview when `HTTP_REFERRER` is not set [grav#1930](https://github.com/getgrav/grav/issues/1930)
# v1.7.1
@@ -94,7 +227,7 @@
* Fixed issue with default lang translation in admin [#1361](https://github.com/getgrav/grav-plugin-admin/issues/1361)
* Typos in `Tools` -> `Direct Install` page [#1345](https://github.com/getgrav/grav-plugin-admin/issues/1345)
* Fixed bug with frontmatter being killed when in `Expert Mode` [#1354](https://github.com/getgrav/grav-plugin-admin/issues/1354)
# v1.7.0-rc.3
## 02/15/2018
@@ -119,7 +252,7 @@
* Tidied Tools panel and added translations
* Tooltip and new icon for site preview
* Updated JS library dependencies
* Changed CodeMirror editor to use sans-serif font for readability
* Changed CodeMirror editor to use sans-serif font for readability
1. [](#bugfix)
* Fixed z-index issue in fullscreen mode [#1317](https://github.com/getgrav/grav-plugin-admin/issues/1317)
@@ -215,7 +348,7 @@
* Fixed fields not working in Microsoft Edge with Selectize.js [#1222](https://github.com/getgrav/grav-plugin-admin/pull/1222)
* Replaced a left-over "is empty" check [#1232](https://github.com/getgrav/grav-plugin-admin/pull/1232)
* Fixed headers drop-down in editor to align properly
# v1.6.0
## 09/07/2017
+31 -4
View File
@@ -144,6 +144,7 @@ class AdminPlugin extends Plugin
// Only activate admin if we're inside the admin path.
if ($this->isAdminPath()) {
$this->grav['session']->init();
$this->active = true;
// Set cache based on admin_cache option
@@ -249,7 +250,7 @@ class AdminPlugin extends Plugin
unset($this->grav['user']);
$this->grav['user'] = $user;
$user->authenticated = true;
$user->authorized = $user->authorize('site.login');
$user->authorized = $user->authorize('admin.login');
$messages = $this->grav['messages'];
$messages->add($this->grav['language']->translate('PLUGIN_ADMIN.LOGIN_LOGGED_IN'), 'info');
@@ -316,6 +317,16 @@ class AdminPlugin extends Plugin
*/
public function onPagesInitialized()
{
$config = $this->config;
// Force SSL with redirect if required
if ($config->get('system.force_ssl')) {
if (!isset($_SERVER['HTTPS']) || $_SERVER['HTTPS'] !== 'on') {
$url = 'https://' . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI'];
$this->grav->redirect($url);
}
}
$this->session = $this->grav['session'];
// Set original route for the home page.
@@ -349,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');
@@ -373,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));
@@ -456,7 +480,9 @@ class AdminPlugin extends Plugin
$twig->twig_vars['location'] = $this->template;
$twig->twig_vars['base_url_relative_frontend'] = $twig->twig_vars['base_url_relative'] ?: '/';
$twig->twig_vars['admin_route'] = trim($this->admin_route, '/');
$twig->twig_vars['current_route'] = '/' . $twig->twig_vars['admin_route'] . '/' . $this->template . '/' . $this->route;
$twig->twig_vars['base_url_relative'] = $twig->twig_vars['base_url_simple'] . '/' . $twig->twig_vars['admin_route'];
$twig->twig_vars['current_url'] = rtrim($twig->twig_vars['base_url_relative'] . '/' . $this->template . '/' . $this->route, '/');
$theme_url = '/' . ltrim($this->grav['locator']->findResource('plugin://admin/themes/' . $this->theme,
false), '/');
$twig->twig_vars['theme_url'] = $theme_url;
@@ -762,7 +788,8 @@ class AdminPlugin extends Plugin
{
// Special case to redirect after changing the admin route to avoid 'breaking'
$obj = $event['object'];
if (null !== $obj) {
if (null !== $obj && method_exists($obj, 'blueprints')) {
$blueprint = $obj->blueprints()->getFilename();
if ($blueprint === 'admin/blueprints' && isset($obj->route) && $this->admin_route !== $obj->route) {
+2 -2
View File
@@ -1,5 +1,5 @@
name: Admin Panel
version: 1.8.1
version: 1.8.14
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.4.5' }
- { 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
@@ -2,10 +2,9 @@
namespace Grav\Plugin\Admin\Twig;
use Grav\Common\Grav;
use Grav\Common\Yaml;
use Grav\Common\Language\Language;
use Grav\Common\Page\Page;
use Symfony\Component\Yaml\Yaml;
use Symfony\Component\Yaml\Parser;
class AdminTwigExtension extends \Twig_Extension
{
@@ -32,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']),
];
}
@@ -43,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;
@@ -81,7 +98,7 @@ class AdminTwigExtension extends \Twig_Extension
return $this->grav['admin']->translate($args, $lang);
}
public function toYamlFilter($value, $inline = true)
public function toYamlFilter($value, $inline = null)
{
return Yaml::dump($value, $inline);
@@ -89,8 +106,7 @@ class AdminTwigExtension extends \Twig_Extension
public function fromYamlFilter($value)
{
$yaml = new Parser();
return $yaml->parse($value);
return Yaml::parse($value);
}
public function adminNicetimeFilter($date, $long_strings = true)
+16 -14
View File
@@ -27,7 +27,7 @@ use RocketTheme\Toolbox\ResourceLocator\UniformResourceIterator;
use RocketTheme\Toolbox\ResourceLocator\UniformResourceLocator;
use RocketTheme\Toolbox\Session\Message;
use RocketTheme\Toolbox\Session\Session;
use Symfony\Component\Yaml\Yaml;
use Grav\Common\Yaml;
use Composer\Semver\Semver;
use PicoFeed\Reader\Reader;
@@ -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 = isset($post['redirect']) ? $post['redirect'] : $this->uri->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'));
@@ -392,11 +392,9 @@ class Admin
if ($user->authorized) {
$event->defMessage('PLUGIN_ADMIN.LOGIN_LOGGED_IN', 'info');
$event->defRedirect($redirect);
$event->defRedirect(isset($post['redirect']) ? $post['redirect'] : $redirect);
} else {
$this->session->redirect = $redirect;
$event->defRedirect($this->uri->route());
}
} else {
if ($user->authorized) {
@@ -406,7 +404,7 @@ class Admin
}
}
$event->defRedirect($this->uri->route());
$event->defRedirect($redirect);
$message = $event->getMessage();
if ($message) {
@@ -597,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
@@ -671,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])) {
@@ -1420,6 +1419,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) {
@@ -2,9 +2,12 @@
namespace Grav\Plugin\Admin;
use Grav\Common\Config\Config;
use Grav\Common\Data\Data;
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;
@@ -199,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.
@@ -225,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),
@@ -259,7 +285,7 @@ class AdminBaseController
}
// Handle errors and breaks without proceeding further
if ($upload->file->error != UPLOAD_ERR_OK) {
if ($upload->file->error !== UPLOAD_ERR_OK) {
$this->admin->json_response = [
'status' => 'error',
'message' => sprintf($this->admin->translate('PLUGIN_ADMIN.FILEUPLOAD_UNABLE_TO_UPLOAD', null),
@@ -285,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 === '*') {
@@ -293,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;
}
}
}
@@ -366,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;
}
@@ -717,9 +755,9 @@ class AdminBaseController
} else {
$new_data = $files;
}
if (isset($data['header'][$init_key])) {
if (isset($obj->header()->{$init_key})) {
$obj->modifyHeader($init_key,
array_replace_recursive([], $data['header'][$init_key], $new_data));
array_replace_recursive([], $obj->header()->{$init_key}, $new_data));
} else {
$obj->modifyHeader($init_key, $new_data);
}
@@ -743,30 +781,44 @@ class AdminBaseController
return false;
}
$data = $this->view === 'pages' ? $this->admin->page(true) : $this->prepareData([]);
$settings = $data->blueprints()->schema()->getProperty($this->post['name']);
$data = $this->view === 'pages' ? $this->admin->page(true) : $this->prepareData([]);
if (null === $data) {
return false;
}
if (method_exists($data, 'blueprints')) {
$settings = $data->blueprints()->schema()->getProperty($this->post['name']);
} elseif (method_exists($data, 'getBlueprint')) {
$settings = $data->getBlueprint()->schema()->getProperty($this->post['name']);
}
if (isset($settings['folder'])) {
$folder = $settings['folder'];
} else {
$folder = '@self';
$folder = 'self@';
}
// Do not use self@ outside of pages
if ($this->view !== 'pages' && in_array($folder, ['@self', 'self@', '@self@'])) {
$this->admin->json_response = [
'status' => 'error',
'message' => sprintf($this->admin->translate('PLUGIN_ADMIN.FILEUPLOAD_PREVENT_SELF', null), $folder)
];
if (!$data instanceof MediaInterface) {
$this->admin->json_response = [
'status' => 'error',
'message' => sprintf($this->admin->translate('PLUGIN_ADMIN.FILEUPLOAD_PREVENT_SELF', null), $folder)
];
return false;
return false;
}
$media = $data->getMedia();
} else {
// Set destination
$folder = Folder::getRelativePath(rtrim($folder, '/'));
$folder = $this->admin->getPagePathFromToken($folder);
$media = new Media($folder);
}
// Set destination
$folder = Folder::getRelativePath(rtrim($folder, '/'));
$folder = $this->admin->getPagePathFromToken($folder);
$media = new Media($folder);
$available_files = [];
$metadata = [];
$thumbs = [];
@@ -859,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);
@@ -876,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) {
@@ -919,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);
+199 -97
View File
@@ -14,17 +14,18 @@ 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;
use Grav\Plugin\Admin\Twig\AdminTwigExtension;
use Grav\Plugin\Login\TwoFactorAuth\TwoFactorAuth;
use Grav\Common\Yaml;
use RocketTheme\Toolbox\Event\Event;
use RocketTheme\Toolbox\File\File;
use RocketTheme\Toolbox\File\JsonFile;
use RocketTheme\Toolbox\ResourceLocator\UniformResourceLocator;
use Symfony\Component\Yaml\Exception\ParseException;
use Symfony\Component\Yaml\Yaml;
/**
* Class AdminController
@@ -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,14 +612,18 @@ 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()) {
$this->admin->setMessage($this->admin->translate('PLUGIN_ADMIN.NO_USER_EXISTS'),'error');
return false;
}
if (!$this->admin->authorize(['admin.super', 'admin.users'])) {
//not admin.super or admin.users
// no user file or not admin.super or admin.users
if ($this->prepareData($data)->username !== $this->grav['user']->username) {
$this->admin->setMessage($this->admin->translate('PLUGIN_ADMIN.INSUFFICIENT_PERMISSIONS_FOR_TASK') . ' save.',
'error');
$this->admin->setMessage($this->admin->translate('PLUGIN_ADMIN.INSUFFICIENT_PERMISSIONS_FOR_TASK') . ' save.','error');
return false;
}
}
@@ -643,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(), '.');
@@ -758,16 +775,8 @@ class AdminController extends AdminBaseController
public function checkValidFrontmatter($frontmatter)
{
try {
// Try native PECL YAML PHP extension first if available.
if (function_exists('yaml_parse')) {
$saved = @ini_get('yaml.decode_php');
@ini_set('yaml.decode_php', 0);
@yaml_parse("---\n" . $frontmatter . "\n...");
@ini_set('yaml.decode_php', $saved);
} else {
Yaml::parse($frontmatter);
}
} catch (ParseException $e) {
Yaml::parse($frontmatter);
} catch (\RuntimeException $e) {
return false;
}
@@ -874,6 +883,10 @@ class AdminController extends AdminBaseController
protected function taskGetNewsFeed()
{
if (!$this->authorizeTask('dashboard', ['admin.login', 'admin.super'])) {
return false;
}
$cache = $this->grav['cache'];
if ($this->post['refresh'] === 'true') {
@@ -910,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;
}
/**
@@ -922,6 +937,10 @@ class AdminController extends AdminBaseController
*/
protected function taskGetUpdates()
{
if (!$this->authorizeTask('dashboard', ['admin.login', 'admin.super'])) {
return false;
}
$data = $this->post;
$flush = (isset($data['flush']) && $data['flush'] == true) ? true : false;
@@ -954,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;
}
/**
@@ -968,12 +992,16 @@ class AdminController extends AdminBaseController
*/
protected function taskGetNotifications()
{
if (!$this->authorizeTask('dashboard', ['admin.login', 'admin.super'])) {
return false;
}
$cache = $this->grav['cache'];
if (!(bool)$this->grav['config']->get('system.cache.enabled') || !$notifications = $cache->fetch('notifications')) {
//No notifications cache (first time)
$this->admin->json_response = ['status' => 'success', 'notifications' => [], 'need_update' => true];
return;
return true;
}
$need_update = false;
@@ -990,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 = [
@@ -998,6 +1026,8 @@ class AdminController extends AdminBaseController
'notifications' => $notifications,
'need_update' => $need_update
];
return true;
}
/**
@@ -1007,6 +1037,10 @@ class AdminController extends AdminBaseController
*/
protected function taskProcessNotifications()
{
if (!$this->authorizeTask('notifications', ['admin.login', 'admin.super'])) {
return false;
}
$cache = $this->grav['cache'];
$data = $this->post;
@@ -1156,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
@@ -1172,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 {
@@ -1181,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) {
@@ -1191,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);
}
/**
@@ -1215,6 +1249,15 @@ class AdminController extends AdminBaseController
$package_name = isset($data['package_name']) ? $data['package_name'] : '';
$current_version = isset($data['current_version']) ? $data['current_version'] : '';
if (!$this->authorizeTask('install ' . $type, ['admin.' . $type, 'admin.super'])) {
$json_response = [
'status' => 'error',
'message' => $this->admin->translate('PLUGIN_ADMIN.INSUFFICIENT_PERMISSIONS_FOR_TASK')
];
$this->sendJsonResponse($json_response, 403);
}
$url = "https://getgrav.org/download/{$type}s/$slug/$current_version";
$result = Gpm::directInstall($url);
@@ -1344,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([
@@ -1574,6 +1617,8 @@ class AdminController extends AdminBaseController
{
$this->uri = $this->uri ?: $this->grav['uri'];
$uri = $this->uri->post('uri');
$order = $this->uri->post('order') ?: null;
if ($uri) {
/** @var UniformResourceLocator $locator */
$locator = $this->grav['locator'];
@@ -1584,8 +1629,11 @@ class AdminController extends AdminBaseController
$media_path = $page ? $page->path() : null;
}
if ($order) {
$order = array_map('trim', explode(',', $order));
}
return $media_path ? new Media($media_path) : null;
return $media_path ? new Media($media_path, $order) : null;
}
/**
@@ -1646,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) {
@@ -1659,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;
@@ -1687,9 +1743,16 @@ class AdminController extends AdminBaseController
return false;
}
/** @var UniformResourceLocator $locator */
$locator = $this->grav['locator'];
$path = $media->path();
if ($locator->isStream($path)) {
$path = $locator->findResource($path, true, true);
}
// Upload it
if (!move_uploaded_file($_FILES['file']['tmp_name'],
sprintf('%s/%s', $media->path(), $_FILES['file']['name']))
sprintf('%s/%s', $path, $filename))
) {
$this->admin->json_response = [
'status' => 'error',
@@ -1701,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;
}
@@ -1750,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',
@@ -1759,7 +1826,13 @@ class AdminController extends AdminBaseController
return false;
}
/** @var UniformResourceLocator $locator */
$locator = $this->grav['locator'];
$targetPath = $media->path() . '/' . $filename;
if ($locator->isStream($targetPath)) {
$targetPath = $locator->findResource($targetPath, true, true);
}
$fileParts = pathinfo($filename);
$found = false;
@@ -1781,7 +1854,13 @@ class AdminController extends AdminBaseController
// Remove Extra Files
foreach (scandir($media->path(), SCANDIR_SORT_NONE) as $file) {
if (preg_match("/{$fileParts['filename']}@\d+x\.{$fileParts['extension']}(?:\.meta\.yaml)?$|{$filename}\.meta\.yaml$/", $file)) {
$result = unlink($media->path() . '/' . $file);
$targetPath = $media->path() . '/' . $file;
if ($locator->isStream($targetPath)) {
$targetPath = $locator->findResource($targetPath, true, true);
}
$result = unlink($targetPath);
if (!$result) {
$this->admin->json_response = [
@@ -1825,9 +1904,9 @@ class AdminController extends AdminBaseController
*/
protected function taskProcessMarkdown()
{
/*if (!$this->authorizeTask('process markdown', ['admin.pages', 'admin.super'])) {
return;
}*/
if (!$this->authorizeTask('process markdown', ['admin.pages', 'admin.super'])) {
return false;
}
try {
$page = $this->admin->page(true);
@@ -1843,6 +1922,7 @@ class AdminController extends AdminBaseController
$this->preparePage($page, true);
$page->header();
$page->templateFormat('html');
// Add theme template paths to Twig loader
$template_paths = $this->grav['locator']->findResources('theme://templates');
@@ -2153,6 +2233,10 @@ class AdminController extends AdminBaseController
*/
protected function taskSwitchlanguage()
{
if (!$this->authorizeTask('switch language', ['admin.pages', 'admin.super'])) {
return false;
}
$data = (array)$this->data;
if (isset($data['lang'])) {
@@ -2176,6 +2260,68 @@ class AdminController extends AdminBaseController
$admin_route = $this->admin->base;
$this->setRedirect('/' . $language . $admin_route . '/' . $redirect);
return true;
}
/**
* Handle direct install.
*/
protected function taskDirectInstall()
{
if (!$this->authorizeTask('install', ['admin.super'])) {
return false;
}
$file_path = isset($this->data['file_path']) ? $this->data['file_path'] : null ;
if (isset($_FILES['uploaded_file'])) {
// Check $_FILES['file']['error'] value.
switch ($_FILES['uploaded_file']['error']) {
case UPLOAD_ERR_OK:
break;
case UPLOAD_ERR_NO_FILE:
$this->admin->setMessage($this->admin->translate('PLUGIN_ADMIN.NO_FILES_SENT'), 'error');
return false;
case UPLOAD_ERR_INI_SIZE:
case UPLOAD_ERR_FORM_SIZE:
$this->admin->setMessage($this->admin->translate('PLUGIN_ADMIN.EXCEEDED_FILESIZE_LIMIT'), 'error');
return false;
case UPLOAD_ERR_NO_TMP_DIR:
$this->admin->setMessage($this->admin->translate('PLUGIN_ADMIN.UPLOAD_ERR_NO_TMP_DIR'), 'error');
return false;
default:
$this->admin->setMessage($this->admin->translate('PLUGIN_ADMIN.UNKNOWN_ERRORS'), 'error');
return false;
}
$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;
}
}
$result = Gpm::directInstall($file_path);
if ($result === true) {
$this->admin->setMessage($this->admin->translate('PLUGIN_ADMIN.INSTALLATION_SUCCESSFUL'), 'info');
} else {
$this->admin->setMessage($this->admin->translate('PLUGIN_ADMIN.INSTALLATION_FAILED') . ': ' . $result,
'error');
}
$this->setRedirect('/tools');
return true;
}
/**
@@ -2249,49 +2395,5 @@ class AdminController extends AdminBaseController
return $filename . '.md';
}
/**
* Handle direct install.
*/
protected function taskDirectInstall()
{
$file_path = isset($this->data['file_path']) ? $this->data['file_path'] : null ;
if (isset($_FILES['uploaded_file'])) {
// Check $_FILES['file']['error'] value.
switch ($_FILES['uploaded_file']['error']) {
case UPLOAD_ERR_OK:
break;
case UPLOAD_ERR_NO_FILE:
$this->admin->setMessage($this->admin->translate('PLUGIN_ADMIN.NO_FILES_SENT'), 'error');
return false;
case UPLOAD_ERR_INI_SIZE:
case UPLOAD_ERR_FORM_SIZE:
$this->admin->setMessage($this->admin->translate('PLUGIN_ADMIN.EXCEEDED_FILESIZE_LIMIT'), 'error');
return false;
case UPLOAD_ERR_NO_TMP_DIR:
$this->admin->setMessage($this->admin->translate('PLUGIN_ADMIN.UPLOAD_ERR_NO_TMP_DIR'), 'error');
return false;
default:
$this->admin->setMessage($this->admin->translate('PLUGIN_ADMIN.UNKNOWN_ERRORS'), 'error');
return false;
}
$file_path = $_FILES['uploaded_file']['tmp_name'];
}
$result = Gpm::directInstall($file_path);
if ($result === true) {
$this->admin->setMessage($this->admin->translate('PLUGIN_ADMIN.INSTALLATION_SUCCESSFUL'), 'info');
} else {
$this->admin->setMessage($this->admin->translate('PLUGIN_ADMIN.INSTALLATION_FAILED') . ': ' . $result,
'error');
}
$this->setRedirect('/tools');
return true;
}
}
+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
+15
View File
@@ -0,0 +1,15 @@
{
"project":"grav-plugin-admin",
"platforms":{
"grav":{
"nodes":{
"plugin":[
{
"source":"/",
"destination":"/user/plugins/admin"
}
]
}
}
}
}
+28 -2
View File
@@ -392,6 +392,10 @@ PLUGIN_ADMIN:
DISPLAY_ERRORS_HELP: "Display full backtrace-style error page"
LOG_ERRORS: "Log errors"
LOG_ERRORS_HELP: "Log errors to /logs folder"
LOG_HANDLER: "Log handler"
LOG_HANDLER_HELP: "Where to output the logs"
SYSLOG_FACILITY: "Syslog facility"
SYSLOG_FACILITY_HELP: "Syslog facility for output"
DEBUGGER: "Debugger"
DEBUGGER_HELP: "Enable Grav debugger and following settings"
DEBUG_TWIG: "Debug Twig"
@@ -441,6 +445,7 @@ PLUGIN_ADMIN:
PAGE_FILE: "Page Template"
PAGE_FILE_HELP: "Page template file name, and by default the display template for this page"
NO_USER_ACCOUNTS: "No user accounts found, please create one first..."
NO_USER_EXISTS: "No local user exists for this account, cannot save..."
REDIRECT_TRAILING_SLASH: "Redirect trailing slash"
REDIRECT_TRAILING_SLASH_HELP: "Perform a 301 redirect rather than transparently handling trailing slash URIs."
DEFAULT_DATE_FORMAT: "Page date format"
@@ -502,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"
@@ -554,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"
@@ -713,3 +716,26 @@ PLUGIN_ADMIN:
ROUTE_ALIASES: "Route Aliases"
OPEN_NEW_TAB: "Open in new tab"
PAGEMEDIA_RESIZER: "> The following settings apply to images uploaded through the page media. Resize Width / Height will automatically resize down and proportionally an image if exceeds the limits set in the inputs. Resolution min and max values define the size ranges for uploaded images. Set the fields to 0 to prevent any manipulation."
SESSION_INITIALIZE: "Initialize Session"
SESSION_INITIALIZE_HELP: "Makes Grav to start a session. This feature is needed to make any user interaction to work, such as login, forms etc. Admin plugin isn't affected by this setting."
STRICT_YAML_COMPAT: "YAML Compatibility"
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"
@@ -28,6 +28,10 @@ class Template {
return this.container.find('[data-grav-array-mode="value_only"]:first').length || false;
}
isTextArea() {
return this.container.data('grav-array-textarea') || false;
}
shouldBeDisabled() {
// check for toggleables, if field is toggleable and it's not enabled, render disabled
let toggle = this.container.closest('.form-field').find('[data-grav-field="toggleable"] input[type="checkbox"]');
@@ -35,20 +39,22 @@ class Template {
}
getNewRow() {
let tpl = '';
let tpl = '';const value = this.isTextArea()
? `<textarea ${this.shouldBeDisabled() ? 'disabled="disabled"' : ''} data-grav-array-type="value" name="" placeholder="${this.getValuePlaceholder()}"></textarea>`
: `<input ${this.shouldBeDisabled() ? 'disabled="disabled"' : ''} data-grav-array-type="value" type="text" name="" value="" placeholder="${this.getValuePlaceholder()}" />`;
if (this.isValueOnly()) {
tpl += `
<div class="form-row array-field-value_only" data-grav-array-type="row">
<span data-grav-array-action="sort" class="fa fa-bars"></span>
<input ${this.shouldBeDisabled() ? 'disabled="disabled"' : ''} data-grav-array-type="value" type="text" value="" placeholder="${this.getValuePlaceholder()}" />
${value}
`;
} else {
tpl += `
<div class="form-row" data-grav-array-type="row">
<span data-grav-array-action="sort" class="fa fa-bars"></span>
<input ${this.shouldBeDisabled() ? 'disabled="disabled"' : ''} data-grav-array-type="key" type="text" value="" placeholder="${this.getKeyPlaceholder()}" />
<input ${this.shouldBeDisabled() ? 'disabled="disabled"' : ''} data-grav-array-type="value" type="text" name="" value="" placeholder="${this.getValuePlaceholder()}" />
${value}
`;
}
@@ -1,8 +1,8 @@
import $ from 'jquery';
import { config } from 'grav-config';
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 {
@@ -45,10 +45,11 @@ export default class FilePickerField {
let parent = field.closest('[data-grav-filepicker]');
let name = parent.data('name');
let value = parent.data('value');
let params = JSON.stringify(uri_params || '{}');
request(url, {
method: 'post',
body: { name }
body: { name, params }
}, (response) => {
if (typeof response.files === 'undefined') {
return;
@@ -78,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>
@@ -1,5 +1,6 @@
import $ from 'jquery';
import Dropzone from 'dropzone';
import EXIF from 'exif-js';
import request from '../../utils/request';
import { config, translations } from 'grav-config';
@@ -70,6 +71,8 @@ const DropzoneMediaConfig = {
</div>`.trim()
};
global.EXIF = EXIF;
const ACCEPT_FUNC = function(file, done, settings) {
const resolution = settings.resolution;
if (!resolution) return done();
@@ -162,7 +165,6 @@ export default class FilesField {
}
getURI() {
console.log(this.container.data('mediaUri'));
return this.container.data('mediaUri') || '';
}
View File
@@ -20,9 +20,8 @@ export default class SelectizeField {
let field = (isInput ? element : element.find('input, select'));
if (!field.length || field.get(0).selectize) { return; }
field.selectize($.extend({}, data, {
plugins: ['required-fix']
}));
const plugins = $.merge(data.plugins ? data.plugins : [], ['required-fix']);
field.selectize($.extend({}, data, { plugins }));
this.elements.push(field.data('selectize'));
}
+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';
@@ -74,7 +74,8 @@ export default class PageMedia extends FilesField {
}
fetchMedia() {
const body = { uri: this.getURI() };
const order = this.container.closest('.form-field').find('[name="data[header][media_order]"]').val();
const body = { uri: this.getURI(), order };
let url = this.urls.fetch;
request(url, { method: 'post', body }, (response) => {
@@ -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));
+1 -1
View File
@@ -1,3 +1,3 @@
@import url("https://fonts.googleapis.com/css?family=Roboto:300,400,500|Inconsolata:400,700|&subset=latin-ext");body,h5,h6,.badge,.note,.grav-mdeditor-preview,input,select,textarea,button,.selectize-input{font-family:"Roboto","Helvetica","Tahoma","Geneva","Arial",sans-serif}h1,h2,h3,h4,.form-tabs>label,.label{font-family:"Josefin Slab","Helvetica","Tahoma","Geneva","Arial",sans-serif}code,kbd,pre,samp,body .CodeMirror{font-family:"Inconsolata","Monaco","Consolas","Lucida Console",monospace !important}
/*# sourceMappingURL=../css-compiled/fonts.css.map */
/*# sourceMappingURL=fonts.css.map */
@@ -1,11 +1 @@
{
"version": 3,
"file": "../scss/fonts.css",
"sources": [
"../scss/fonts.scss",
"../hdr0",
"../scss/configuration/fonts/_support.scss"
],
"mappings": "AE+DA,OAAO,CAAC,uGAAI,CAtBR,AAAA,IAAI,CAAE,AAAA,EAAE,CAAE,AAAA,EAAE,CACZ,AAAA,MAAM,CAAE,AAAA,KAAK,CAAE,AAAA,sBAAsB,CACrC,AAAA,KAAK,CAAE,AAAA,MAAM,CAAE,AAAA,QAAQ,CAAE,AAAA,MAAM,CAAE,AAAA,gBAAgB,AAAC,CAC9C,WAAW,CAAE,QAAU,CAAE,WAAW,CAAE,QAAQ,CAAE,QAAQ,CAAE,OAAO,CAAE,UAAU,CAChF,AAID,AAAA,EAAE,CAAE,AAAA,EAAE,CAAE,AAAA,EAAE,CAAE,AAAA,EAAE,CACd,AAAa,UAAH,CAAG,KAAK,CAAE,AAAA,MAAM,AAAC,CACvB,WAAW,CAAE,cAAU,CAAE,WAAW,CAAE,QAAQ,CAAE,QAAQ,CAAE,OAAO,CAAE,UAAU,CAChF,AAID,AAAA,IAAI,CAAE,AAAA,GAAG,CAAE,AAAA,GAAG,CAAE,AAAA,IAAI,CACpB,AAAK,IAAD,CAAC,WAAW,AAAC,CACb,WAAW,CAAE,aAAU,CAAE,QAAQ,CAAE,UAAU,CAAE,gBAAgB,CAAE,SAAS,CAAC,UAAU,CACxF",
"names": []
}
{"version":3,"file":"fonts.css","sources":["fonts.scss","configuration/fonts/_support.scss"],"sourcesContent":["$fonts-header: 'Josefin Slab' !default;\n$fonts-default: 'Roboto' !default;\n$fonts-mono: 'Inconsolata' !default;\n\n$font-definitions: (\n Josefin+Slab: 400,\n Roboto: '300,400,500',\n Inconsolata: '400,700'\n);\n\n@import \"configuration/fonts/support\";\n\n\n\n\n","@function str-replace($string, $search, $replace: '') {\n $index: str-index($string, $search);\n\n @if $index {\n @return str-slice($string, 1, $index - 1) + $replace + str-replace(str-slice($string, $index + str-length($search)), $search, $replace);\n }\n\n @return $string;\n}\n\n@function admin-font-faces($fonts) {\n $url: \"https://fonts.googleapis.com/css?family=\";\n $nb: 0;\n\n @each $fontname, $weights in $fonts {\n\n @if $fontname == $fonts-default or\n $fontname == $fonts-header or\n $fontname == $fonts-mono {\n\n $nb: $nb + 1;\n $nb-word: 0;\n\n $fontname: str-replace(\"#{$fontname}\", \" \", \"+\");\n\n $url: $url + $fontname;\n\n @if $weights != null {\n $url: $url + \":\" + $weights;\n }\n\n @if $nb < 3 {\n $url: $url + \"|\";\n }\n }\n }\n\n @return $url + \"&subset=latin-ext\";\n}\n\n@mixin body-fonts($font) {\n body, h5, h6,\n .badge, .note, .grav-mdeditor-preview,\n input, select, textarea, button, .selectize-input {\n font-family: \"#{$font}\", \"Helvetica\", \"Tahoma\", \"Geneva\", \"Arial\", sans-serif;\n }\n}\n\n@mixin header-fonts($font) {\n h1, h2, h3, h4,\n .form-tabs > label, .label {\n font-family: \"#{$font}\", \"Helvetica\", \"Tahoma\", \"Geneva\", \"Arial\", sans-serif;\n }\n}\n\n@mixin mono-fonts($font) {\n code, kbd, pre, samp,\n body .CodeMirror {\n font-family: \"#{$font}\", \"Monaco\", \"Consolas\", \"Lucida Console\", monospace !important;\n }\n}\n$font-url: admin-font-faces($font-definitions);\n\n@import url(\"#{$font-url}\");\n\n@include body-fonts($fonts-default);\n\n@include header-fonts($fonts-header);\n\n@include mono-fonts($fonts-mono);\n\n\n\n\n\n"],"names":[],"mappings":"AC+DA,OAAO,CAAC,uGAAI,CAtBR,AAAA,IAAI,CAAE,EAAE,CAAE,EAAE,CACZ,MAAM,CAAE,KAAK,CAAE,sBAAsB,CACrC,KAAK,CAAE,MAAM,CAAE,QAAQ,CAAE,MAAM,CAAE,gBAAgB,AAAC,CAC9C,WAAW,CAAE,QAAU,CAAE,WAAW,CAAE,QAAQ,CAAE,QAAQ,CAAE,OAAO,CAAE,UAAU,CAChF,AAID,AAAA,EAAE,CAAE,EAAE,CAAE,EAAE,CAAE,EAAE,CACd,UAAU,CAAG,KAAK,CAAE,MAAM,AAAC,CACvB,WAAW,CAAE,cAAU,CAAE,WAAW,CAAE,QAAQ,CAAE,QAAQ,CAAE,OAAO,CAAE,UAAU,CAChF,AAID,AAAA,IAAI,CAAE,GAAG,CAAE,GAAG,CAAE,IAAI,CACpB,IAAI,CAAC,WAAW,AAAC,CACb,WAAW,CAAE,aAAU,CAAE,QAAQ,CAAE,UAAU,CAAE,gBAAgB,CAAE,SAAS,CAAC,UAAU,CACxF"}
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
File diff suppressed because one or more lines are too long
@@ -1,3 +1,3 @@
body,h5,h6,.badge,.note,.grav-mdeditor-preview,input,select,textarea,button,.selectize-input,h1,h2,h3,h4,#admin-menu li,.form-tabs>label,.label,body .CodeMirror pre{font-family:"Helvetica Neue", "Helvetica", "Tahoma", "Geneva", "Arial", sans-serif}code,kbd,pre,samp{font-family:"Monaco", "Consolas", "Lucida Console", monospace}
/*# sourceMappingURL=../css-compiled/simple-fonts.css.map */
/*# sourceMappingURL=simple-fonts.css.map */
@@ -1,10 +1 @@
{
"version": 3,
"file": "../scss/simple-fonts.css",
"sources": [
"../scss/simple-fonts.scss",
"../hdr0"
],
"mappings": "AAAA,AAAA,IAAI,CAAE,AAAA,EAAE,CAAE,AAAA,EAAE,CACZ,AAAA,MAAM,CAAE,AAAA,KAAK,CAAE,AAAA,sBAAsB,CACrC,AAAA,KAAK,CAAE,AAAA,MAAM,CAAE,AAAA,QAAQ,CAAE,AAAA,MAAM,CAAE,AAAA,gBAAgB,CACjD,AAAA,EAAE,CAAE,AAAA,EAAE,CAAE,AAAA,EAAE,CAAE,AAAA,EAAE,CACd,AAAY,WAAD,CAAC,EAAE,CAAE,AAAa,UAAH,CAAG,KAAK,CAAE,AAAA,MAAM,CAC1C,AAAiB,IAAb,CAAC,WAAW,CAAC,GAAG,AAAC,CACjB,WAAW,CAAE,sEAAuE,CACvF,AACD,AAAA,IAAI,CAAE,AAAA,GAAG,CAAE,AAAA,GAAG,CAAE,AAAA,IAAI,AAAE,CAClB,WAAW,CAAE,iDAAkD,CAClE",
"names": []
}
{"version":3,"file":"simple-fonts.css","sources":["simple-fonts.scss"],"sourcesContent":["body, h5, h6,\n.badge, .note, .grav-mdeditor-preview,\ninput, select, textarea, button, .selectize-input,\nh1, h2, h3, h4,\n#admin-menu li, .form-tabs > label, .label,\nbody .CodeMirror pre {\n font-family: \"Helvetica Neue\", \"Helvetica\", \"Tahoma\", \"Geneva\", \"Arial\", sans-serif;\n}\ncode, kbd, pre, samp {\n font-family: \"Monaco\", \"Consolas\", \"Lucida Console\", monospace;\n}\n\n"],"names":[],"mappings":"AAAA,AAAA,IAAI,CAAE,EAAE,CAAE,EAAE,CACZ,MAAM,CAAE,KAAK,CAAE,sBAAsB,CACrC,KAAK,CAAE,MAAM,CAAE,QAAQ,CAAE,MAAM,CAAE,gBAAgB,CACjD,EAAE,CAAE,EAAE,CAAE,EAAE,CAAE,EAAE,CACd,WAAW,CAAC,EAAE,CAAE,UAAU,CAAG,KAAK,CAAE,MAAM,CAC1C,IAAI,CAAC,WAAW,CAAC,GAAG,AAAC,CACjB,WAAW,CAAE,sEAAsE,CACtF,AACD,AAAA,IAAI,CAAE,GAAG,CAAE,GAAG,CAAE,IAAI,AAAE,CAClB,WAAW,CAAE,iDAAiD,CACjE"}
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
View File
View File
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
+2 -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",
@@ -22,11 +21,13 @@
"eonasdan-bootstrap-datetimepicker": "^4.17.47",
"es6-map": "^0.1.5",
"es6-promise": "^4.1.1",
"exif-js": "^2.3.0",
"gemini-scrollbar": "^1.5.2",
"immutable": "^3.8.1",
"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",
View File
@@ -251,7 +251,7 @@ tr {
&:focus, &:hover {
color: $button-text;
background-color: $button-bg;
background-color: darken($button-bg, 5%);
}
}
@@ -298,7 +298,7 @@ tr {
}
}
.instructions {
.instructions, p {
color: rgba($nav-text,0.8);
}
@@ -306,6 +306,10 @@ tr {
background: darken($nav-bg, 5%) url(../images/logo.svg) 50% 50% no-repeat;
}
h5 {
color: $nav-text;
}
form {
input {
background: lighten($nav-bg, 3%);
@@ -674,6 +678,10 @@ form {
input {
color: $form-field-text;
}
a.remove {
color: $page-bg !important;
}
}
}
}
@@ -909,16 +917,16 @@ form {
}
.form-fieldset {
background-color: #f7f7f7;
border: 2px solid #e1e1e1;
background-color: darken($content-bg, 1%);
border: 1px solid $form-border;
}
.form-fieldset--label {
background-color: #f3f3f3;
background-color: darken($content-bg, 4%);
&:hover,
.form-fieldset input:checked + & {
background-color: #eee;
background-color: darken($content-bg, 6%);
}
}
#admin-main {
@@ -252,14 +252,14 @@ $preview-height: 150px;
overflow: hidden;
img {
position: absolute;
left: 50%;
top: 50%;
height: auto;
width: 100%;
-webkit-transform: translate(-50%,-50%);
-ms-transform: translate(-50%,-50%);
transform: translate(-50%,-50%);
//position: absolute;
//left: 50%;
//top: 50%;
//height: auto;
//width: 100%;
//-webkit-transform: translate(-50%,-50%);
//-ms-transform: translate(-50%,-50%);
//transform: translate(-50%,-50%);
}
}
}
@@ -272,7 +272,7 @@ $preview-height: 150px;
img {
position: absolute;
object-fit: cover;
object-fit: contain;
top: 0;
left: 0;
width: $preview-width;
@@ -604,7 +604,7 @@ textarea.frontmatter {
letter-spacing: normal;
}
.form-fieldset {
margin: 1rem 2rem;
margin: 1rem 1.5rem;
}
.form-fieldset--label {
@@ -672,5 +672,3 @@ textarea.frontmatter {
}
}
@@ -127,6 +127,10 @@
right: 0;
padding: 1.5rem 3rem;
.button {
margin-bottom: 2px;
}
button:first-child {
margin-right: 1rem;
}
@@ -1,46 +1,56 @@
{% extends "forms/field.html.twig" %}
{% macro renderer(key, text, field, scope) %}
{% if text is not iterable %}
<div class="form-row{% if field.value_only %} array-field-value_only{% endif %}"
data-grav-array-type="row">
<span data-grav-array-action="sort" class="fa fa-bars"></span>
{% if field.value_only != true %}
{% if key == '0' and text == '' %}
{% set key = '' %}
{% 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 }}" />
{% 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>
{% 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 %} />
{% endif %}
<span data-grav-array-action="rem" class="fa fa-minus"></span>
<span data-grav-array-action="add" class="fa fa-plus"></span>
</div>
{% 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 %}
{% macro renderer(key, text, field, scope) %}
{% if text is not iterable %}
<div class="form-row{% if field.value_only %} array-field-value_only{% endif %}"
data-grav-array-type="row">
<span data-grav-array-action="sort" class="fa fa-bars"></span>
{% if field.value_only != true %}
{% if key == '0' and text == '' %}
{% set key = '' %}
{% 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 }}" />
{% endif %}
<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 %} />
<span data-grav-array-action="rem" class="fa fa-minus"></span>
<span data-grav-array-action="add" class="fa fa-plus"></span>
</div>
{% endif %}
{% endmacro %}
{% 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 -%}
@@ -58,18 +68,26 @@
<div class="form-row" data-grav-array-type="row">
<span data-grav-array-action="sort" class="fa fa-bars"></span>
{% if field.value_only != true %}
<input
data-grav-array-type="key"
type="text"
{% if field.disabled or isDisabledToggleable %}disabled="disabled"{% endif %}
placeholder="{{ field.placeholder_key|e|tu }}" />
<input
data-grav-array-type="key"
type="text"
{% 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 }}"
{% if field.disabled or isDisabledToggleable %}disabled="disabled"{% endif %}
placeholder="{{ field.placeholder_value|e|tu }}"></textarea>
{% else %}
<input
data-grav-array-type="value"
type="text"
name="{{ (scope ~ field.name)|fieldName }}"
{% if field.disabled or isDisabledToggleable %}disabled="disabled"{% endif %}
placeholder="{{ field.placeholder_value|e|tu }}" />
{% endif %}
<input
data-grav-array-type="value"
type="text"
name="{{ (scope ~ field.name)|fieldName }}"
{% if field.disabled or isDisabledToggleable %}disabled="disabled"{% endif %}
placeholder="{{ field.placeholder_value|e|tu }}" />
<span data-grav-array-action="rem" class="fa fa-minus"></span>
<span data-grav-array-action="add" class="fa fa-plus"></span>
</div>
@@ -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>
@@ -45,16 +45,18 @@
{% if field.max is defined and not field.selectunique %}data-max="{{ field.max }}"{% endif %}
>
{% if fieldControls in ['top', 'both'] %}
<div class="collection-actions{{ not value|length ? ' hidden' : '' }}">
<button class="button" type="button" data-action="expand_all"
{% if field.disabled or isDisabledToggleable %}disabled="disabled"{% endif %}><i class="fa fa-chevron-circle-down"></i> {{ "PLUGIN_ADMIN.EXPAND_ALL"|e|tu }}</button>
<button class="button" type="button" data-action="collapse_all"
{% if field.disabled or isDisabledToggleable %}disabled="disabled"{% endif %}><i class="fa fa-chevron-circle-right"></i> {{ "PLUGIN_ADMIN.COLLAPSE_ALL"|e|tu }}</button>
<div class="collection-actions">
{% if collapsible %}
<button class="button" type="button" data-action="expand_all"
{% if field.disabled or isDisabledToggleable %}disabled="disabled"{% endif %}><i class="fa fa-chevron-circle-down"></i> {{ "PLUGIN_ADMIN.EXPAND_ALL"|e|tu }}</button>
<button class="button" type="button" data-action="collapse_all"
{% if field.disabled or isDisabledToggleable %}disabled="disabled"{% endif %}><i class="fa fa-chevron-circle-right"></i> {{ "PLUGIN_ADMIN.COLLAPSE_ALL"|e|tu }}</button>
{% endif %}
{% if field.sortby %}
<button class="button{{ not value|length ? ' hidden' : '' }}" type="button" data-action="sort" data-action-sort="{{ field.sortby }}" data-action-sort-dir="{{ field.sortby_dir|default('asc') }}"
{% if field.disabled or isDisabledToggleable %}disabled="disabled"{% endif %}><i class="fa fa-sort-amount-{{ field.sortby_dir|default('asc') }}"></i> {{ btnSortLabel|e|tu }} '{{ field.sortby }}'</button>
{% endif %}
<button class="button" type="button" data-action="add" data-action-add="top"
<button class="button" type="button" data-action="add" data-action-add="bottom"
{% if field.disabled or isDisabledToggleable %}disabled="disabled"{% endif %}><i class="fa fa-plus"></i> {{ btnLabel|e|tu }}</button>
</div>
{% endif %}
@@ -75,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 -%}
@@ -1,6 +1,6 @@
{% set value = (value is null ? field.default : value) %}
{% set siblings = data.parent.children %}
{% set canOrder = data.order %}
{% set siblings = context.parent.children %}
{% set canOrder = context.order %}
{% set vertical = field.style == 'vertical' %}
<div class="form-field grid pure-g{% if vertical %} vertical{% endif %}">
@@ -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,7 +1,7 @@
{% extends "forms/field.html.twig" %}
{% block global_attributes %}
data-grav-selectize="{{ (field.selectize is defined ? field.selectize|merge({'create': true})|merge({'items':value}) : {'create': true})|json_encode()|e('html_attr') }}"
data-grav-selectize="{{ (field.selectize is defined ? field.selectize|merge({'create': true}) : {'create': true})|json_encode()|e('html_attr') }}"
{{ parent() }}
{% endblock %}
@@ -1,9 +1,13 @@
{% if field.fields %}
<div class="form-tab">
{% for field in field.fields %}
{% for childName, child in field.fields %}
{% if field.type %}
{% set value = field.name ? data.value(field.name) : data.toArray %}
{% include ["forms/fields/#{field.type}/#{field.type}.html.twig", 'forms/fields/text/text.html.twig'] %}
{%- if childName starts with '.' -%}
{% set child = child|merge({ name: field.name ~ childName }) %}
{% endif %}
{% set value = child.name ? data.value(child.name) : data.toArray %}
{% include ["forms/fields/#{child.type}/#{child.type}.html.twig", 'forms/fields/text/text.html.twig'] with { field: child, value: data.value(child.name) } %}
{% endif %}
{% endfor %}
</div>
@@ -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,7 +1,7 @@
{% if authorize(['admin.pages', 'admin.super']) %}
<div id="latest">
<div class="button-bar">
<a class="button" href="{{ uri.route(true) }}/pages"><i class="fa fa-fw fa-file-text-o"></i>{{ "PLUGIN_ADMIN.MANAGE_PAGES"|tu }}</a>
<a class="button" href="{{ base_url_relative }}/pages"><i class="fa fa-fw fa-file-text-o"></i>{{ "PLUGIN_ADMIN.MANAGE_PAGES"|tu }}</a>
</div>
<h1>{{ "PLUGIN_ADMIN.LATEST_PAGE_UPDATES"|tu }}</h1>
<table>
View File
@@ -16,7 +16,7 @@
{% endfor %}
<div class="{{ form_button_outer_classes ?: 'form-actions secondary-accent' }}">
<div class="form-actionst">
<div class="form-actions primary-accent">
<button type="submit" class="button primary" name="task" value="twofa">{{ 'PLUGIN_LOGIN.BTN_LOGIN'|t }}</button>
</div>
</div>
@@ -15,7 +15,7 @@
{% block integration %}{% endblock %}
{% set redirect = redirect ?: uri.route(false) %}
{% set redirect = redirect ?: uri.path ~ uri.params ~ (uri.query ? '?' ~ uri.query : '') %}
<form method="post" action="{{ base_url_relative }}">
<div class="padding">
@@ -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>
@@ -48,7 +48,7 @@
{% set gumroad_loaded = true %}
<script src="//gumroad.com/js/gumroad.js"></script>
{% endif %}
<a class="gumroad-button button" href="https://gum.co/{{ plugin.premium.product_permalink }}" target="_blank" data-gumroad-single-product="true"><i class="fa fa-shopping-cart"></i> {{ "Buy Now"|tu }}</a>
<a class="gumroad-button button" href="https://gum.co/{{ plugin.premium.permalink }}" target="_blank" data-gumroad-single-product="true"><i class="fa fa-shopping-cart"></i> {{ "Buy Now"|tu }}</a>
{% else %}
<a class="button" href="#" data-remodal-target="add-package" data-packages-slugs="{{ plugin.slug }}" data-plugin-action="start-package-installation"><i class="fa fa-plus"></i> {{ "PLUGIN_ADMIN.INSTALL_PLUGIN"|tu }}</a>
{% endif %}
@@ -52,7 +52,7 @@
{% set gumroad_loaded = true %}
<script src="//gumroad.com/js/gumroad.js"></script>
{% endif %}
<a class="gumroad-button button" href="https://gum.co/{{ plugin.premium.product_permalink }}" target="_blank" data-gumroad-single-product="true"><i class="fa fa-shopping-cart"></i> {{ "Buy Now"|tu }}</a>
<a class="gumroad-button button" href="https://gum.co/{{ plugin.premium.permalink }}" target="_blank" data-gumroad-single-product="true"><i class="fa fa-shopping-cart"></i> {{ "Buy Now"|tu }}</a>
{% else %}
<a class="button" href="#" data-remodal-target="add-package" data-packages-slugs="{{ plugin.slug }}" data-plugin-action="start-package-installation"><i class="fa fa-plus"></i> {{ "PLUGIN_ADMIN.INSTALL"|tu }}</a>
{% endif %}
View File
File diff suppressed because it is too large Load Diff
Vendored Executable → Regular
+1 -135
View File
@@ -1,135 +1 @@
#!/usr/bin/env php
<?php
require_once 'vendor/autoload.php';
use PicoFeed\Config\Config;
use PicoFeed\Reader\Favicon;
use PicoFeed\Scraper\Scraper;
use PicoFeed\Reader\Reader;
use PicoFeed\Logging\Logger;
use PicoFeed\PicoFeedException;
Logger::enable();
function get_feed($url, $disable_filtering = false)
{
try {
$reader = new Reader;
$resource = $reader->discover($url);
$parser = $reader->getParser(
$resource->getUrl(),
$resource->getContent(),
$resource->getEncoding()
);
if ($disable_filtering) {
$parser->disableContentFiltering();
}
return $parser->execute();
}
catch (PicoFeedException $e) {
echo 'Exception thrown ===> "'.$e->getMessage().'"'.PHP_EOL;
return false;
}
}
function get_item($feed, $item_id)
{
foreach ($feed->items as $item) {
if ($item->getId() === $item_id) {
echo $item;
echo "============= CONTENT ================\n";
echo $item->getContent();
echo "\n============= CONTENT ================\n";
break;
}
}
}
function dump_feed($url)
{
$feed = get_feed($url);
echo $feed;
}
function debug_feed($url)
{
get_feed($url);
print_r(Logger::getMessages());
}
function dump_item($url, $item_id)
{
$feed = get_feed($url);
if ($feed !== false) {
get_item($feed, $item_id);
}
}
function nofilter_item($url, $item_id)
{
$feed = get_feed($url, true);
if ($feed !== false) {
get_item($feed, $item_id);
}
}
function grabber($url)
{
$grabber = new Scraper(new Config);
$grabber->setUrl($url);
$grabber->execute();
print_r(Logger::getMessages());
echo "============= CONTENT ================\n";
echo $grabber->getRelevantContent().PHP_EOL;
echo "============= FILTERED ================\n";
echo $grabber->getFilteredContent().PHP_EOL;
}
function fetch_favicon($url)
{
$favicon = new Favicon();
echo $favicon->find($url) . PHP_EOL;
}
// Parse command line arguments
if ($argc === 4) {
switch ($argv[1]) {
case 'item':
dump_item($argv[2], $argv[3]);
die;
case 'nofilter':
nofilter_item($argv[2], $argv[3]);
die;
}
} else if ($argc === 3) {
switch ($argv[1]) {
case 'feed':
dump_feed($argv[2]);
die;
case 'debug':
debug_feed($argv[2]);
die;
case 'grabber':
grabber($argv[2]);
die;
case 'favicon':
fetch_favicon($argv[2]);
die;
}
}
printf("Usage:\n");
printf("%s feed <feed-url>\n", $argv[0]);
printf("%s debug <feed-url>\n", $argv[0]);
printf("%s item <feed-url> <item-id>\n", $argv[0]);
printf("%s nofilter <feed-url> <item-id>\n", $argv[0]);
printf("%s grabber <url>\n", $argv[0]);
printf("%s favicon <url>\n", $argv[0]);
../fguillot/picofeed/picofeed
View File
@@ -1,54 +0,0 @@
{
"name": "zendframework/zendxml",
"description": "Utility library for XML usage, best practices, and security in PHP",
"license": "BSD-3-Clause",
"keywords": [
"zf",
"zendframework",
"xml",
"security"
],
"support": {
"issues": "https://github.com/zendframework/ZendXml/issues",
"source": "https://github.com/zendframework/ZendXml",
"rss": "https://github.com/zendframework/ZendXml/releases.atom",
"chat": "https://zendframework-slack.herokuapp.com",
"forum": "https://discourse.zendframework.com/c/questions/components"
},
"require": {
"php": "^5.6 || ^7.0"
},
"require-dev": {
"zendframework/zend-coding-standard": "~1.0.0",
"phpunit/phpunit": "^5.7.27 || ^6.5.8 || ^7.1.4"
},
"autoload": {
"psr-4": {
"ZendXml\\": "src/"
}
},
"autoload-dev": {
"psr-4": {
"ZendXmlTest\\": "test/"
}
},
"config": {
"sort-packages": true
},
"extra": {
"branch-alias": {
"dev-master": "1.1.x-dev",
"dev-develop": "1.2.x-dev"
}
},
"scripts": {
"check": [
"@cs-check",
"@test"
],
"cs-check": "phpcs",
"cs-fix": "phpcbf",
"test": "phpunit --colors=always",
"test-coverage": "phpunit --colors=always --coverage-clover clover.xml"
}
}