modif comtabilite
This commit is contained in:
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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",
|
||||
|
||||
Generated
+372
-478
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"project":"grav-plugin-admin",
|
||||
"platforms":{
|
||||
"grav":{
|
||||
"nodes":{
|
||||
"plugin":[
|
||||
{
|
||||
"source":"/",
|
||||
"destination":"/user/plugins/admin"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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') || '';
|
||||
}
|
||||
|
||||
|
||||
Executable → Regular
@@ -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'));
|
||||
}
|
||||
|
||||
@@ -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
@@ -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"}
|
||||
+1
-1
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+2
-2
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
Executable → Regular
+18
-17
File diff suppressed because one or more lines are too long
@@ -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);
|
||||
+30
-29
File diff suppressed because one or more lines are too long
@@ -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",
|
||||
|
||||
Executable → Regular
@@ -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 %}
|
||||
|
||||
+18
-14
@@ -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 %}
|
||||
|
||||
+8
-6
@@ -1,5 +1,12 @@
|
||||
{% extends "forms/field.html.twig" %}
|
||||
|
||||
{% macro spanToggle(input, length) %}
|
||||
{% set space = repeat(' ', (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(' ', (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(' ', (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(' ', (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(' ', (length - input|length) / 2) ~ input ~ repeat(' ', (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 ~ ' • ' : "PLUGIN_ADMIN.PAGE"|tu ~ ' • ') ~
|
||||
(p.modular ? "PLUGIN_ADMIN.MODULAR"|tu ~ ' • ' : '') ~
|
||||
(p.routable ? "PLUGIN_ADMIN.ROUTABLE"|tu ~ ' • ' : "PLUGIN_ADMIN.NON_ROUTABLE"|tu ~ ' • ') ~
|
||||
(p.visible ? "PLUGIN_ADMIN.VISIBLE"|tu ~ ' • ' : "PLUGIN_ADMIN.NON_VISIBLE"|tu ~ ' • ') ~
|
||||
(p.published ? "PLUGIN_ADMIN.PUBLISHED"|tu ~ ' • ' : "PLUGIN_ADMIN.NON_PUBLISHED"|tu ~ ' • ') %}
|
||||
|
||||
{% 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(' • ')|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 ~ ' • ' : "PLUGIN_ADMIN.PAGE"|tu ~ ' • ') ~
|
||||
(p.modular ? "PLUGIN_ADMIN.MODULAR"|tu ~ ' • ' : '') ~
|
||||
(p.routable ? "PLUGIN_ADMIN.ROUTABLE"|tu ~ ' • ' : "PLUGIN_ADMIN.NON_ROUTABLE"|tu ~ ' • ') ~
|
||||
(p.visible ? "PLUGIN_ADMIN.VISIBLE"|tu ~ ' • ' : "PLUGIN_ADMIN.NON_VISIBLE"|tu ~ ' • ') ~
|
||||
(p.published ? "PLUGIN_ADMIN.PUBLISHED"|tu ~ ' • ' : "PLUGIN_ADMIN.NON_PUBLISHED"|tu ~ ' • ') %}
|
||||
|
||||
{% 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(' • ')|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>
|
||||
|
||||
Executable → Regular
@@ -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">
|
||||
|
||||
+17
-13
@@ -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 %}
|
||||
|
||||
Executable → Regular
File diff suppressed because it is too large
Load Diff
+1
-135
@@ -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
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user