diff --git a/pages/01.home/default.md b/pages/01.home/default.md new file mode 100644 index 0000000..13b2376 --- /dev/null +++ b/pages/01.home/default.md @@ -0,0 +1,42 @@ +--- +title: Home +body_classes: title-center title-h1h2 +--- + +# Say Hello to Grav! +## installation successful... + +Congratulations! You have installed the **Base Grav Package** that provides a **simple page** and the default **Quark** theme to get you started. + +!! If you see a **404 Error** when you click `Typography` in the menu, please refer to the [troubleshooting guide](http://learn.getgrav.org/troubleshooting/page-not-found). + +### Find out all about Grav + +* Learn about **Grav** by checking out our dedicated [Learn Grav](http://learn.getgrav.org) site. +* Download **plugins**, **themes**, as well as other Grav **skeleton** packages from the [Grav Downloads](http://getgrav.org/downloads) page. +* Check out our [Grav Development Blog](http://getgrav.org/blog) to find out the latest goings on in the Grav-verse. + +!!! If you want a more **full-featured** base install, you should check out [**Skeleton** packages available in the downloads](http://getgrav.org/downloads). + +### Edit this Page + +To edit this page, simply navigate to the folder you installed **Grav** into, and then browse to the `user/pages/01.home` folder and open the `default.md` file in your [editor of choice](http://learn.getgrav.org/basics/requirements). You will see the content of this page in [Markdown format](http://learn.getgrav.org/content/markdown). + +### Create a New Page + +Creating a new page is a simple affair in **Grav**. Simply follow these simple steps: + +1. Navigate to your pages folder: `user/pages/` and create a new folder. In this example, we will use [explicit default ordering](http://learn.getgrav.org/content/content-pages) and call the folder `03.mypage`. +2. Launch your text editor and paste in the following sample code: + + --- + title: My New Page + --- + # My New Page! + + This is the body of **my new page** and I can easily use _Markdown_ syntax here. + +3. Save this file in the `user/pages/03.mypage/` folder as `default.md`. This will tell **Grav** to render the page using the **default** template. +4. That is it! Reload your browser to see your new page in the menu. + +! NOTE: The page will automatically show up in the Menu after the "Typography" menu item. If you wish to change the name that shows up in the Menu, simple add: `menu: My Page` between the dashes in the page content. This is called the YAML front matter, and it is where you configure page-specific options. diff --git a/plugins/admin/.editorconfig b/plugins/admin/.editorconfig index 3b95e6d..6375a81 100644 --- a/plugins/admin/.editorconfig +++ b/plugins/admin/.editorconfig @@ -1,4 +1,4 @@ -# EditorConfig is awesome: http://EditorConfig.org +# EditorConfig is awesome: https://EditorConfig.org # top-most EditorConfig file root = true @@ -10,8 +10,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_size = 2 + +# 4 space indentation +[*.php] +indent_size = 4 diff --git a/plugins/admin/CHANGELOG.md b/plugins/admin/CHANGELOG.md index 6c9e75b..2a66eae 100644 --- a/plugins/admin/CHANGELOG.md +++ b/plugins/admin/CHANGELOG.md @@ -1,3 +1,145 @@ +# v1.10.30.2 +## 02/09/2022 + +1. [](#bugfix) + * Fixed regression preventing new `elements` field from saving its state + +# v1.10.30.1 +## 02/09/2022 + +1. [](#improved) + * List field items will now require confirmation before getting deleted + +# v1.10.30 +## 02/07/2022 + +1. [](#new) + * Require **Grav 1.7.30** + * Updated SCSS compiler to v1.10 + * PageMedia can now be collapsed and thumbnails previewed smaller, in order to save room on the page. Selection will be remembered. + * DEPRECATED: Admin field `pages_list_display_field` is no longer available as an option [#2191](https://github.com/getgrav/grav-plugin-admin/issues/2191) + * When listing installable themes/plugins, it is now possible to sort them by [Premium](https://getgrav.org/premium) +2. [](#improved) + * Updated JavaScript dependencies + * Cleaned up JavaScript unused dependencies and warnings + * Removed unused style assets + * Plugins list rows now properly highlight on hover, no more guessing when wanting to disable a plugin! +3. [](#bugfix) + * Fixed `elements` field when it's used inside `list` field + * Fixed issue uploading non-images media when Resolution setting enabled in Admin [#2172](https://github.com/getgrav/grav-plugin-admin/issues/2172) + * Prevent fields from being toggled incorrectly by adding originalValue to childs of fieldset. [#2218](https://github.com/getgrav/grav-plugin-admin/pull/2218) + * Fixed persistent focus on Folder field when Adding page (Safari) [#2209](https://github.com/getgrav/grav-plugin-admin/issues/2209) + * Fixed performance of Plugins / Themes sort in the installation table + * Fixed list field with key/value pairs throwing an exception due to bad value [#2199](https://github.com/getgrav/grav-plugin-admin/issues/2199) + * Fixed disabling/enabling plugin from the list breaking the plugin configuration + +# v1.10.29 +## 01/28/2022 + +1. [](#new) + * Require **Grav 1.7.29** +3. [](#improved) + * Made path handling unicode-safe, use new `Utils::basename()` and `Utils::pathinfo()` everywhere + +# v1.10.28 +## 01/24/2022 + +1. [](#bugfix) + * Clean file names before displaying errors/metadata modals + * Recompiled JS for production [#2225](https://github.com/getgrav/grav-plugin-admin/issues/2225) + +# v1.10.27 +## 01/12/2022 + +1. [](#new) + * Support for `YubiKey OTP` 2-Factor authenticator + * New `elements` container field that shows/hides children fields based on boolean trigger value + * Requires Grav `v1.7.27` and Login `v3.6.2` +2. [](#improved) + * Added new asset language strings + +# v1.10.26.1 +## 01/03/2022 + +3. [](#bugfix) + * Fixed an issue with missing files reference by cached autoloader + +# v1.10.26 +## 01/03/2022 + +2. [](#improved) + * Updated SCSS compiler to v1.9 and other vendor libraries + * Fixed various deprecation warnings + * Localized dialog buttons and icons [#2207](https://github.com/getgrav/grav-plugin-admin/pull/2207) + * Updated copyright year + +# v1.10.25 +## 11/16/2021 + +3. [](#bugfix) + * Fixed unescaped messages in JSON responses + +# v1.10.24 +## 10/26/2021 + +1. [](#new) + * Require **Grav 1.7.24** +2. [](#improved) + * Use new `Http\Response` rather than deprecated `GPM\Response` +3. [](#bugfix) + * Fixed an issue with invalid HTML throwing errors on HTML security scanning + * Clear cache when installing plugins + +# v1.10.23 +## 09/29/2021 + +1. [](#new) + * Updated SCSS compiler to v1.8 +2. [](#improved) + * Updated with latest language strings from Crowdin.com +3. [](#bugfix) + * Fixed images from plugins/themes disappearing when saving twice + +# v1.10.22 +## 09/16/2021 + +1. [](#new) + * Updated SCSS compiler to v1.7 + +# v1.10.21 +## 09/14/2021 + +1. [](#new) + * Require **Grav 1.7.21** +2. [](#improved) + * Added a note about UTC times in scheduler AT syntax help + * Now using a monospaced text-based scheduler AT field in scheduler for simplicity + * Improved `Admin:data()` and `Admin::getConfigurationData()` to be more strict +3. [](#bugfix) + * Fixed configuration save location to point to existing config folder [#2176](https://github.com/getgrav/grav-plugin-admin/issues/2176) + +# v1.10.20 +## 09/01/2021 + +1. [](#bugfix) + * Fixed regression `Argument 4 passed to Grav\Plugin\Form\TwigExtension::prepareFormField() must be of the type array` [#2177](https://github.com/getgrav/grav-plugin-admin/issues/2177) + * Fixed `X-Frame-Options` to be `DENY` in all admin pages to prevent a clickjacking attack + +# v1.10.19 +## 08/31/2021 + +1. [](#new) + * Require **Grav 1.7.19** and **Form 5.1.0** and **Login 3.5.0** + * Updated SCSS compiler to v1.6 +2. [](#improved) + * Updated forms and nested fields to use new form logic + * Admin form now use layout `admin`, meaning you can create admin specific field templates by `forms/fields/myfield/admin-field.html.twig` + * Stop using `|tu` filter, Grav already has the same logic in `|t` for admin + * Remove unneeded escapes + * Allow removal of plugin when disabled [#2167](https://github.com/getgrav/grav-plugin-admin/issues/2167) +3. [](#bugfix) + * Fixed missing values in `fieldset` form field + # v1.10.18 ## 07/19/2021 diff --git a/plugins/admin/README.md b/plugins/admin/README.md index d894296..4a7d654 100644 --- a/plugins/admin/README.md +++ b/plugins/admin/README.md @@ -1,6 +1,6 @@ # Grav Standard Administration Panel Plugin -This **admin plugin** for [Grav](http://github.com/getgrav/grav) is an HTML user interface that provides a convenient way to configure Grav and easily create and modify pages. This will remain a totally optional plugin, and is not in any way required or needed to use Grav effectively. In fact, the admin provides an intentionally limited view to ensure it remains easy to use and not overwhelming. I'm sure power users will still prefer to work with the configuration files directly. +This **admin plugin** for [Grav](https://github.com/getgrav/grav) is an HTML user interface that provides a convenient way to configure Grav and easily create and modify pages. This will remain a totally optional plugin, and is not in any way required or needed to use Grav effectively. In fact, the admin provides an intentionally limited view to ensure it remains easy to use and not overwhelming. I'm sure power users will still prefer to work with the configuration files directly. ![](assets/admin-dashboard.png) @@ -33,7 +33,7 @@ This **admin plugin** for [Grav](http://github.com/getgrav/grav) is an HTML user We have tested internally, but we hope to use this public beta phase to identify, isolate, and fix issues related to the plugin to ensure it is as solid and reliable as possible. -For **live chatting**, please use the dedicated [Slack Chat Room](https://getgrav.org/slack) for discussions directly related to Grav. +For **live chatting**, please use the dedicated [Discord Chat Room](https://getgrav.org/discord) for discussions directly related to Grav. For **bugs, features, improvements**, please ensure you [create issues in the admin plugin GitHub repository](https://github.com/getgrav/grav-plugin-admin). @@ -99,7 +99,7 @@ By default, you can access the admin by pointing your browser to `http://yoursit # Standard Free & Paid Pro Versions -If you have been following the [blog](http://getgrav.org/blog), [Twitter](https://twitter.com/getgrav), [Slack chat](https://getgrav.org/slack), etc., you probably already know now that our intention is to provide two versions of this plugin. +If you have been following the [blog](https://getgrav.org/blog), [Twitter](https://twitter.com/getgrav), [Discord chat](https://getgrav.org/discord), etc., you probably already know now that our intention is to provide two versions of this plugin. The **standard free version**, is very powerful, and has more functionality than most commercial flat-file CMS systems. diff --git a/plugins/admin/admin.php b/plugins/admin/admin.php index e2ad433..e8d5eaf 100644 --- a/plugins/admin/admin.php +++ b/plugins/admin/admin.php @@ -119,6 +119,12 @@ class AdminPlugin extends Plugin 'list' => [ 'array' => true ], + 'elements' => [ + 'input@' => true + ], + 'element' => [ + 'input@' => false + ], 'file' => [ 'array' => true, 'media_field' => true, @@ -452,11 +458,11 @@ class AdminPlugin extends Plugin $legacyController = true; } + /** @var UserInterface $user */ + $user = $this->grav['user']; + // Replace page service with admin. if (empty($this->grav['page'])) { - /** @var UserInterface $user */ - $user = $this->grav['user']; - $this->grav['page'] = function () use ($user) { $page = new Page(); @@ -480,7 +486,7 @@ class AdminPlugin extends Plugin Admin::DEBUG && Admin::addDebugMessage("Admin page: {$this->template}"); $page->init(new \SplFileInfo(__DIR__ . "/pages/admin/{$this->template}.md")); - $page->slug(basename($this->template)); + $page->slug(Utils::basename($this->template)); return $page; } @@ -501,7 +507,7 @@ class AdminPlugin extends Plugin Admin::DEBUG && Admin::addDebugMessage("Admin page: plugin {$plugin->name}/{$this->template}"); $page->init(new \SplFileInfo($path)); - $page->slug(basename($this->template)); + $page->slug(Utils::basename($this->template)); return $page; } @@ -525,7 +531,7 @@ class AdminPlugin extends Plugin $error_file = $this->grav['locator']->findResource('plugins://admin/pages/admin/error.md'); $page = new Page(); $page->init(new \SplFileInfo($error_file)); - $page->slug(basename($this->route)); + $page->slug(Utils::basename($this->route)); $page->routable(true); } @@ -537,7 +543,7 @@ class AdminPlugin extends Plugin $login_file = $this->grav['locator']->findResource('plugins://admin/pages/admin/login.md'); $page = new Page(); $page->init(new \SplFileInfo($login_file)); - $page->slug(basename($this->route)); + $page->slug(Utils::basename($this->route)); unset($this->grav['page']); $this->grav['page'] = $page; } @@ -1304,7 +1310,7 @@ class AdminPlugin extends Plugin $options = []; $theme_files = glob(__dir__ . '/themes/grav/css/codemirror/themes/*.css'); foreach ($theme_files as $theme_file) { - $theme = basename(basename($theme_file, '.css')); + $theme = Utils::basename(Utils::basename($theme_file, '.css')); $options[$theme] = Inflector::titleize($theme); } } diff --git a/plugins/admin/admin.yaml b/plugins/admin/admin.yaml index e785124..c23f95b 100644 --- a/plugins/admin/admin.yaml +++ b/plugins/admin/admin.yaml @@ -26,7 +26,6 @@ session: edit_mode: normal frontend_preview_target: inline show_github_msg: true -pages_list_display_field: title admin_icons: line-awesome enable_auto_updates_check: true notifications: diff --git a/plugins/admin/blueprints.yaml b/plugins/admin/blueprints.yaml index 42d3fbd..cda97d3 100644 --- a/plugins/admin/blueprints.yaml +++ b/plugins/admin/blueprints.yaml @@ -1,13 +1,13 @@ name: Admin Panel slug: admin type: plugin -version: 1.10.18 +version: 1.10.30.2 description: Adds an advanced administration panel to manage your site icon: empire author: name: Team Grav email: devs@getgrav.org - url: http://getgrav.org + url: https://getgrav.org homepage: https://github.com/getgrav/grav-plugin-admin keywords: admin, plugin, manager, panel bugs: https://github.com/getgrav/grav-plugin-admin/issues @@ -15,11 +15,11 @@ docs: https://github.com/getgrav/grav-plugin-admin/blob/develop/README.md license: MIT dependencies: - - { name: grav, version: '>=1.7.10' } - - { name: form, version: '>=4.1.0' } - - { name: login, version: '>=3.3.5' } - - { name: email, version: '>=3.0.9' } - - { name: flex-objects, version: '>=1.0.0' } + - { name: grav, version: '>=1.7.30' } + - { name: form, version: '>=5.1.0' } + - { name: login, version: '>=3.6.2' } + - { name: email, version: '>=3.1.0' } + - { name: flex-objects, version: '>=1.1.0' } form: validation: loose @@ -202,12 +202,6 @@ form: type: bool help: PLUGIN_ADMIN.SHOW_GITHUB_LINK_HELP - pages_list_display_field: - type: text - size: small - label: PLUGIN_ADMIN.PAGES_LIST_DISPLAY_FIELD - help: PLUGIN_ADMIN.PAGES_LIST_DISPLAY_FIELD_HELP - enable_auto_updates_check: type: toggle label: PLUGIN_ADMIN.AUTO_UPDATES diff --git a/plugins/admin/classes/plugin/Admin.php b/plugins/admin/classes/plugin/Admin.php index e40344d..ef41488 100644 --- a/plugins/admin/classes/plugin/Admin.php +++ b/plugins/admin/classes/plugin/Admin.php @@ -10,9 +10,9 @@ use Grav\Common\File\CompiledYamlFile; use Grav\Common\Flex\Types\Users\UserObject; use Grav\Common\GPM\GPM; use Grav\Common\GPM\Licenses; -use Grav\Common\GPM\Response; use Grav\Common\Grav; use Grav\Common\Helpers\YamlLinter; +use Grav\Common\HTTP\Response; use Grav\Common\Language\Language; use Grav\Common\Language\LanguageCodes; use Grav\Common\Page\Collection; @@ -38,6 +38,7 @@ use Grav\Framework\Route\RouteFactory; use Grav\Plugin\AdminPlugin; use Grav\Plugin\Login\Login; use Grav\Plugin\Login\TwoFactorAuth\TwoFactorAuth; +use JsonException; use PicoFeed\Parser\MalformedXmlException; use Psr\Http\Message\ServerRequestInterface; use RocketTheme\Toolbox\Event\Event; @@ -867,115 +868,20 @@ class Admin * * @param string $type * @param array $post - * @return mixed + * @return object * @throws \RuntimeException */ public function data($type, array $post = []) { - static $data = []; - - if (isset($data[$type])) { - return $data[$type]; - } - if (!$post) { - $post = $this->grav['uri']->post(); - $post = $post['data'] ?? []; + $post = $this->preparePost($this->grav['uri']->post()['data'] ?? []); } - // Check to see if a data type is plugin-provided, before looking into core ones - $event = $this->grav->fireEvent('onAdminData', new Event(['type' => &$type])); - if ($event) { - if (isset($event['data_type'])) { - return $event['data_type']; - } - - if (is_string($event['type'])) { - $type = $event['type']; - } + try { + return $this->getConfigurationData($type, $post); + } catch (\RuntimeException $e) { + return new Data\Data(); } - - /** @var UniformResourceLocator $locator */ - $locator = $this->grav['locator']; - $filename = $locator->findResource("config://{$type}.yaml", true, true); - $file = CompiledYamlFile::instance($filename); - - if (preg_match('|plugins/|', $type)) { - /** @var Plugins $plugins */ - $plugins = $this->grav['plugins']; - $obj = $plugins->get(preg_replace('|plugins/|', '', $type)); - - if (!$obj) { - return []; - } - - $obj->merge($post); - $obj->file($file); - - $data[$type] = $obj; - } elseif (preg_match('|themes/|', $type)) { - /** @var Themes $themes */ - $themes = $this->grav['themes']; - $obj = $themes->get(preg_replace('|themes/|', '', $type)); - - if (!$obj) { - return []; - } - - $obj->merge($post); - $obj->file($file); - - $data[$type] = $obj; - } elseif (preg_match('|users?/|', $type)) { - /** @var UserCollectionInterface $users */ - $users = $this->grav['accounts']; - - $obj = $users->load(preg_replace('|users?/|', '', $type)); - $obj->update($this->cleanUserPost($post)); - - $data[$type] = $obj; - } elseif (preg_match('|config/|', $type)) { - $type = preg_replace('|config/|', '', $type); - $blueprints = $this->blueprints("config/{$type}"); - $config = $this->grav['config']; - $obj = new Data\Data($config->get($type, []), $blueprints); - $obj->merge($post); - - // FIXME: We shouldn't allow user to change configuration files in system folder! - $filename = $this->grav['locator']->findResource("config://{$type}.yaml") - ?: $this->grav['locator']->findResource("config://{$type}.yaml", true, true); - $file = CompiledYamlFile::instance($filename); - $obj->file($file); - $data[$type] = $obj; - } elseif (preg_match('|media-manager/|', $type)) { - $filename = base64_decode(preg_replace('|media-manager/|', '', $type)); - - $file = File::instance($filename); - - $pages = static::enablePages(); - - $obj = new \stdClass(); - $obj->title = $file->basename(); - $obj->path = $file->filename(); - $obj->file = $file; - $obj->page = $pages->get(dirname($obj->path)); - - $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])) { - $obj->metadata = new Data\Data($obj->page->media()[$filename]->metadata()); - } - - $data[$type] = $obj; - } else { - throw new \RuntimeException("Data type '{$type}' doesn't exist!"); - } - - return $data[$type]; } /** @@ -993,7 +899,16 @@ class Admin static $data = []; if (isset($data[$type])) { - return $data[$type]; + $obj = $data[$type]; + if ($post) { + if ($obj instanceof Data\Data) { + $obj = $this->mergePost($obj, $post); + } elseif ($obj instanceof UserInterface) { + $obj->update($this->cleanUserPost($post)); + } + } + + return $obj; } // Check to see if a data type is plugin-provided, before looking into core ones @@ -1010,61 +925,48 @@ class Admin /** @var UniformResourceLocator $locator */ $locator = $this->grav['locator']; - $filename = $locator->findResource("config://{$type}.yaml", true, true); + + // Configuration file will be saved to the existing config stream. + $filename = $locator->findResource('config://') . "/{$type}.yaml"; $file = CompiledYamlFile::instance($filename); if (preg_match('|plugins/|', $type)) { $obj = Plugins::get(preg_replace('|plugins/|', '', $type)); if (null === $obj) { - return new \stdClass(); + throw new \RuntimeException("Plugin '{$type}' doesn't exist!"); } - - if ($post) { - $obj = $this->mergePost($obj, $post); - } - $obj->file($file); - $data[$type] = $obj; } elseif (preg_match('|themes/|', $type)) { /** @var Themes $themes */ $themes = $this->grav['themes']; $obj = $themes->get(preg_replace('|themes/|', '', $type)); if (null === $obj) { - return new \stdClass(); + throw new \RuntimeException("Theme '{$type}' doesn't exist!"); } - - if ($post) { - $obj = $this->mergePost($obj, $post); - } - $obj->file($file); - $data[$type] = $obj; } elseif (preg_match('|users?/|', $type)) { /** @var UserCollectionInterface $users */ $users = $this->grav['accounts']; $obj = $users->load(preg_replace('|users?/|', '', $type)); - $obj->update($this->cleanUserPost($post)); - $data[$type] = $obj; } elseif (preg_match('|config/|', $type)) { $type = preg_replace('|config/|', '', $type); $blueprints = $this->blueprints("config/{$type}"); + if (!$blueprints->form()) { + throw new \RuntimeException("Configuration type '{$type}' doesn't exist!"); + } + + // Configuration file will be saved to the existing config stream. + $filename = $locator->findResource('config://') . "/{$type}.yaml"; + $file = CompiledYamlFile::instance($filename); $config = $this->grav['config']; $obj = new Data\Data($config->get($type, []), $blueprints); - if ($post) { - $obj = $this->mergePost($obj, $post); - } - - // FIXME: We shouldn't allow user to change configuration files in system folder! - $filename = $this->grav['locator']->findResource("config://{$type}.yaml") - ?: $this->grav['locator']->findResource("config://{$type}.yaml", true, true); - $file = CompiledYamlFile::instance($filename); $obj->file($file); - $data[$type] = $obj; + } elseif (preg_match('|media-manager/|', $type)) { $filename = base64_decode(preg_replace('|media-manager/|', '', $type)); @@ -1078,7 +980,7 @@ class Admin $obj->file = $file; $obj->page = $pages->get(dirname($obj->path)); - $fileInfo = pathinfo($obj->title); + $fileInfo = Utils::pathinfo($obj->title); $filename = str_replace(['@3x', '@2x'], '', $fileInfo['filename']); if (isset($fileInfo['extension'])) { $filename .= '.' . $fileInfo['extension']; @@ -1088,12 +990,20 @@ class Admin $obj->metadata = new Data\Data($obj->page->media()[$filename]->metadata()); } - $data[$type] = $obj; } else { throw new \RuntimeException("Data type '{$type}' doesn't exist!"); } - return $data[$type]; + $data[$type] = $obj; + if ($post) { + if ($obj instanceof Data\Data) { + $obj = $this->mergePost($obj, $post); + } elseif ($obj instanceof UserInterface) { + $obj->update($this->cleanUserPost($post)); + } + } + + return $obj; } /** @@ -2069,7 +1979,7 @@ class Admin $page = $path ? $pages->find($path, true) : $pages->root(); if (!$page) { - $slug = basename($path); + $slug = Utils::basename($path); if ($slug === '') { return null; @@ -2497,7 +2407,7 @@ class Admin /** * Get changelog for a given GPM package based on slug * - * @param null $slug + * @param string|null $slug * @return array */ public function getChangelog($slug = null) @@ -2518,4 +2428,66 @@ class Admin return $changelog; } + + /** + * Prepare and return POST data. + * + * @param array $post + * @return array + */ + public function preparePost($post): array + { + if (!is_array($post)) { + return []; + } + + unset($post['task']); + + // Decode JSON encoded fields and merge them to data. + if (isset($post['_json'])) { + $post = array_replace_recursive($post, $this->jsonDecode($post['_json'])); + unset($post['_json']); + } + + return $this->cleanDataKeys($post); + } + + /** + * Recursively JSON decode data. + * + * @param array $data + * @return array + * @throws JsonException + */ + private function jsonDecode(array $data): array + { + foreach ($data as &$value) { + if (is_array($value)) { + $value = $this->jsonDecode($value); + } else { + $value = json_decode($value, true, 512, JSON_THROW_ON_ERROR); + } + } + + return $data; + } + + /** + * @param array $source + * @return array + */ + private function cleanDataKeys(array $source): array + { + $out = []; + foreach ($source as $key => $value) { + $key = str_replace(['%5B', '%5D'], ['[', ']'], $key); + if (is_array($value)) { + $out[$key] = $this->cleanDataKeys($value); + } else { + $out[$key] = $value; + } + } + + return $out; + } } diff --git a/plugins/admin/classes/plugin/AdminBaseController.php b/plugins/admin/classes/plugin/AdminBaseController.php index d2f2935..8c546e0 100644 --- a/plugins/admin/classes/plugin/AdminBaseController.php +++ b/plugins/admin/classes/plugin/AdminBaseController.php @@ -19,6 +19,7 @@ use Grav\Common\Plugin; use Grav\Common\Theme; use Grav\Framework\Controller\Traits\ControllerResponseTrait; use Grav\Framework\RequestHandler\Exception\RequestException; +use JsonException; use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; use RocketTheme\Toolbox\Event\Event; @@ -34,56 +35,31 @@ class AdminBaseController { use ControllerResponseTrait; - /** - * @var Grav - */ + /** @var Grav */ public $grav; - - /** - * @var string - */ + /** @var string */ public $view; - - /** - * @var string - */ + /** @var string */ public $task; - - /** - * @var string - */ + /** @var string */ public $route; - - /** - * @var array - */ + /** @var array */ public $post; - - /** - * @var array|null - */ + /** @var array|null */ public $data; + /** @var array */ + public $blacklist_views = []; - /** - * @var \Grav\Common\Uri - */ + /** @var Uri */ protected $uri; - - /** - * @var Admin - */ + /** @var Admin */ protected $admin; - - /** - * @var string - */ + /** @var string */ protected $redirect; - - /** - * @var int - */ + /** @var int */ protected $redirectCode; + /** @var string[] */ protected $upload_errors = [ 0 => 'There is no error, the file uploaded with success', 1 => 'The uploaded file exceeds the max upload size', @@ -95,9 +71,6 @@ class AdminBaseController 8 => 'A PHP extension stopped the file upload' ]; - /** @var array */ - public $blacklist_views = []; - /** * Performs a task. * @@ -105,6 +78,10 @@ class AdminBaseController */ public function execute() { + if (null === $this->admin) { + $this->admin = $this->grav['admin']; + } + // Ignore blacklisted views. if (in_array($this->view, $this->blacklist_views, true)) { return false; @@ -294,7 +271,7 @@ class AdminBaseController $this->admin->json_response = [ 'status' => 'error', 'message' => sprintf($this->admin::translate('PLUGIN_ADMIN.FILEUPLOAD_UNABLE_TO_UPLOAD', null), - $filename, 'Bad filename') + htmlspecialchars($filename, ENT_QUOTES | ENT_HTML5, 'UTF-8'), 'Bad filename') ]; return false; @@ -314,7 +291,7 @@ class AdminBaseController $this->admin->json_response = [ 'status' => 'error', 'message' => sprintf($this->admin::translate('PLUGIN_ADMIN.FILEUPLOAD_PREVENT_SELF', null), - $settings->destination) + htmlspecialchars($settings->destination, ENT_QUOTES | ENT_HTML5, 'UTF-8')) ]; return false; @@ -325,7 +302,8 @@ class AdminBaseController $this->admin->json_response = [ 'status' => 'error', 'message' => sprintf($this->admin::translate('PLUGIN_ADMIN.FILEUPLOAD_UNABLE_TO_UPLOAD', null), - $filename, $this->upload_errors[$upload->file->error]) + htmlspecialchars($filename, ENT_QUOTES | ENT_HTML5, 'UTF-8'), + $this->upload_errors[$upload->file->error]) ]; return false; @@ -363,7 +341,7 @@ class AdminBaseController if ($isMime) { $match = preg_match('#' . $find . '$#', $mime); if (!$match) { - $errors[] = 'The MIME type "' . $mime . '" for the file "' . $filename . '" is not an accepted.'; + $errors[] = htmlspecialchars('The MIME type "' . $mime . '" for the file "' . $filename . '" is not an accepted.', ENT_QUOTES | ENT_HTML5, 'UTF-8'); } else { $accepted = true; break; @@ -371,7 +349,7 @@ class AdminBaseController } else { $match = preg_match('#' . $find . '$#', $filename); if (!$match) { - $errors[] = 'The File Extension for the file "' . $filename . '" is not an accepted.'; + $errors[] = htmlspecialchars('The File Extension for the file "' . $filename . '" is not an accepted.', ENT_QUOTES | ENT_HTML5, 'UTF-8'); } else { $accepted = true; break; @@ -396,14 +374,17 @@ class AdminBaseController // since php removes it from the upload location $tmp_dir = Admin::getTempDir(); $tmp_file = $upload->file->tmp_name; - $tmp = $tmp_dir . '/uploaded-files/' . basename($tmp_file); + $tmp = $tmp_dir . '/uploaded-files/' . Utils::basename($tmp_file); Folder::create(dirname($tmp)); if (!move_uploaded_file($tmp_file, $tmp)) { $this->admin->json_response = [ 'status' => 'error', - 'message' => sprintf($this->admin::translate('PLUGIN_ADMIN.FILEUPLOAD_UNABLE_TO_MOVE', null), '', - $tmp) + 'message' => sprintf( + $this->admin::translate('PLUGIN_ADMIN.FILEUPLOAD_UNABLE_TO_MOVE', null), + '', + htmlspecialchars($tmp, ENT_QUOTES | ENT_HTML5, 'UTF-8') + ) ]; return false; @@ -442,7 +423,7 @@ class AdminBaseController // Generate random name if required if ($settings->random_name) { // TODO: document - $extension = pathinfo($upload->file->name, PATHINFO_EXTENSION); + $extension = Utils::pathinfo($upload->file->name, PATHINFO_EXTENSION); $upload->file->name = Utils::generateRandomString(15) . '.' . $extension; } @@ -671,7 +652,6 @@ class AdminBaseController * Prepare and return POST data. * * @param array $post - * * @return array */ protected function getPost($post) @@ -688,25 +668,24 @@ class AdminBaseController unset($post['_json']); } - $post = $this->cleanDataKeys($post); - - return $post; + return $this->cleanDataKeys($post); } /** * Recursively JSON decode data. * - * @param array $data - * + * @param array $data * @return array + * @throws JsonException + * @internal Do not use directly! */ - protected function jsonDecode(array $data) + protected function jsonDecode(array $data): array { foreach ($data as &$value) { if (is_array($value)) { $value = $this->jsonDecode($value); } else { - $value = json_decode($value, true); + $value = json_decode($value, true, 512, JSON_THROW_ON_ERROR); } } @@ -716,19 +695,17 @@ class AdminBaseController /** * @param array $source * @return array + * @internal Do not use directly! */ - protected function cleanDataKeys($source = []) + protected function cleanDataKeys(array $source): array { $out = []; - - if (is_array($source)) { - foreach ($source as $key => $value) { - $key = str_replace(['%5B', '%5D'], ['[', ']'], $key); - if (is_array($value)) { - $out[$key] = $this->cleanDataKeys($value); - } else { - $out[$key] = $value; - } + foreach ($source as $key => $value) { + $key = str_replace(['%5B', '%5D'], ['[', ']'], $key); + if (is_array($value)) { + $out[$key] = $this->cleanDataKeys($value); + } else { + $out[$key] = $value; } } @@ -952,7 +929,7 @@ class AdminBaseController $type = $uri->param('type'); $field = $uri->param('field'); - $filename = basename($this->post['filename'] ?? ''); + $filename = Utils::basename($this->post['filename'] ?? ''); if ($filename === '') { $this->admin->json_response = [ 'status' => 'error', @@ -1091,7 +1068,7 @@ class AdminBaseController if ($file->exists()) { $resultRemoveMedia = $file->delete(); - $fileParts = pathinfo($filename); + $fileParts = Utils::pathinfo($filename); foreach (scandir($fileParts['dirname']) as $file) { $regex_pattern = '/' . preg_quote($fileParts['filename'], '/') . "@\d+x\." . $fileParts['extension'] . "(?:\.meta\.yaml)?$|" . preg_quote($fileParts['basename'], '/') . "\.meta\.yaml$/"; diff --git a/plugins/admin/classes/plugin/AdminController.php b/plugins/admin/classes/plugin/AdminController.php index e7d28c4..1a83753 100644 --- a/plugins/admin/classes/plugin/AdminController.php +++ b/plugins/admin/classes/plugin/AdminController.php @@ -22,6 +22,7 @@ use Grav\Common\Page\Medium\Medium; use Grav\Common\Page\Page; use Grav\Common\Page\Pages; use Grav\Common\Page\Collection; +use Grav\Common\Plugins; use Grav\Common\Security; use Grav\Common\User\Interfaces\UserCollectionInterface; use Grav\Common\User\Interfaces\UserInterface; @@ -35,6 +36,7 @@ use PicoFeed\Parser\MalformedXmlException; use Psr\Http\Message\ResponseInterface; use RocketTheme\Toolbox\Event\Event; use RocketTheme\Toolbox\File\File; +use RocketTheme\Toolbox\File\YamlFile; use RocketTheme\Toolbox\ResourceLocator\UniformResourceLocator; use Twig\Loader\FilesystemLoader; @@ -56,6 +58,7 @@ class AdminController extends AdminBaseController public function initialize(Grav $grav = null, $view = null, $task = null, $route = null, $post = null) { $this->grav = $grav; + $this->admin = $this->grav['admin']; $this->view = $view; $this->task = $task ?: 'display'; if (isset($post['data'])) { @@ -67,7 +70,6 @@ class AdminController extends AdminBaseController } $this->post = $this->getPost($post); $this->route = $route; - $this->admin = $this->grav['admin']; $this->grav->fireEvent('onAdminControllerInit', new Event(['controller' => &$this])); } @@ -165,7 +167,13 @@ class AdminController extends AdminBaseController // Not used if Flex-Objects plugin handles users. return $this->saveUser(); default: - return $this->saveDefault(); + if ($this->saveDefault()) { + $route = $this->grav['uri']::getCurrentRoute(); + $this->setRedirect($route->withGravParam('task', null)->toString(), 302); + $this->redirect(); + } + + return false; } } @@ -174,11 +182,11 @@ class AdminController extends AdminBaseController */ protected function saveDefault() { - // Handle standard data types. - $type = $this->getDataType(); - $obj = $this->admin->getConfigurationData($type, $this->data); - try { + // Handle standard data types. + $type = $this->getDataType(); + + $obj = $this->admin->getConfigurationData($type, $this->data); $obj->validate(); } catch (\Exception $e) { /** @var Debugger $debugger */ @@ -282,7 +290,7 @@ class AdminController extends AdminBaseController $debugger = $this->grav['debugger']; $debugger->addException($e); - $this->admin->json_response = ['status' => 'error', 'message' => $e->getMessage()]; + $this->admin->json_response = ['status' => 'error', 'message' => htmlspecialchars($e->getMessage(), ENT_QUOTES | ENT_HTML5, 'UTF-8')]; return false; } @@ -401,7 +409,7 @@ class AdminController extends AdminBaseController $debugger = $this->grav['debugger']; $debugger->addException($e); - $json_response = ['status' => 'error', 'message' => $e->getMessage()]; + $json_response = ['status' => 'error', 'message' => htmlspecialchars($e->getMessage(), ENT_QUOTES | ENT_HTML5, 'UTF-8')]; } $this->sendJsonResponse($json_response); @@ -484,7 +492,7 @@ class AdminController extends AdminBaseController $debugger = $this->grav['debugger']; $debugger->addException($e); - $json_response = ['status' => 'error', 'message' => $e->getMessage()]; + $json_response = ['status' => 'error', 'message' => htmlspecialchars($e->getMessage(), ENT_QUOTES | ENT_HTML5, 'UTF-8')]; } $this->sendJsonResponse($json_response); @@ -515,7 +523,7 @@ class AdminController extends AdminBaseController try { if ($download) { - $filename = basename(base64_decode(urldecode($download))); + $filename = Utils::basename(base64_decode(urldecode($download))); $file = $this->grav['locator']->findResource("backup://{$filename}", true); if (!$file || !Utils::endsWith($filename, '.zip', false)) { header('HTTP/1.1 401 Unauthorized'); @@ -534,7 +542,7 @@ class AdminController extends AdminBaseController $this->admin->json_response = [ 'status' => 'error', - 'message' => $this->admin::translate('PLUGIN_ADMIN.AN_ERROR_OCCURRED') . '. ' . $e->getMessage() + 'message' => $this->admin::translate('PLUGIN_ADMIN.AN_ERROR_OCCURRED') . '. ' . htmlspecialchars($e->getMessage(), ENT_QUOTES | ENT_HTML5, 'UTF-8') ]; return true; @@ -578,7 +586,7 @@ class AdminController extends AdminBaseController $backup = $this->grav['uri']->param('backup', null); if (null !== $backup) { - $filename = basename(base64_decode(urldecode($backup))); + $filename = Utils::basename(base64_decode(urldecode($backup))); $file = $this->grav['locator']->findResource("backup://{$filename}", true); if ($file && Utils::endsWith($filename, '.zip', false)) { @@ -623,10 +631,8 @@ class AdminController extends AdminBaseController return false; } - // Filter value and save it. - $this->post = ['enabled' => true]; - $obj = $this->prepareData($this->post); - $obj->save(); + $type = $this->getDataType(); + $this->updatePluginState($type, ['enabled' => true]); $this->post = ['_redirect' => 'plugins']; if ($this->grav['uri']->param('redirect')) { @@ -656,10 +662,8 @@ class AdminController extends AdminBaseController return false; } - // Filter value and save it. - $this->post = ['enabled' => false]; - $obj = $this->prepareData($this->post); - $obj->save(); + $type = $this->getDataType(); + $this->updatePluginState($type, ['enabled' => false]); $this->post = ['_redirect' => 'plugins']; $this->admin->setMessage($this->admin::translate('PLUGIN_ADMIN.SUCCESSFULLY_DISABLED_PLUGIN'), 'info'); @@ -669,6 +673,30 @@ class AdminController extends AdminBaseController return true; } + /** + * @param string $type + * @param array $value + * @return void + */ + protected function updatePluginState(string $type, array $value): void + { + $obj = Plugins::get(preg_replace('|plugins/|', '', $type)); + if (null === $obj) { + throw new \RuntimeException("Plugin '{$type}' doesn't exist!"); + } + + /** @var UniformResourceLocator $locator */ + $locator = $this->grav['locator']; + + // Configuration file will be saved to the existing config stream. + $filename = $locator->findResource('config://') . "/{$type}.yaml"; + + $file = YamlFile::instance($filename); + $contents = $value + $file->content(); + + $file->save($contents); + } + /** * Set the default theme. * @@ -911,7 +939,7 @@ class AdminController extends AdminBaseController $debugger = $this->grav['debugger']; $debugger->addException($e); - $this->admin->json_response = ['status' => 'error', 'message' => $e->getMessage()]; + $this->admin->json_response = ['status' => 'error', 'message' => htmlspecialchars($e->getMessage(), ENT_QUOTES | ENT_HTML5, 'UTF-8')]; return false; } @@ -955,7 +983,7 @@ class AdminController extends AdminBaseController $debugger = $this->grav['debugger']; $debugger->addException($e); - $this->admin->json_response = ['status' => 'error', 'message' => $e->getMessage()]; + $this->admin->json_response = ['status' => 'error', 'message' => htmlspecialchars($e->getMessage(), ENT_QUOTES | ENT_HTML5, 'UTF-8')]; return false; } @@ -998,7 +1026,7 @@ class AdminController extends AdminBaseController $debugger = $this->grav['debugger']; $debugger->addException($e); - $this->admin->json_response = ['status' => 'error', 'message' => $e->getMessage()]; + $this->admin->json_response = ['status' => 'error', 'message' => htmlspecialchars($e->getMessage(), ENT_QUOTES | ENT_HTML5, 'UTF-8')]; return false; } @@ -1053,7 +1081,7 @@ class AdminController extends AdminBaseController $msg = Utils::contains($msg, '401 Unauthorized') ? "ERROR: License key for this resource is invalid." : $msg; $msg = Utils::contains($msg, '404 Not Found') ? "ERROR: Resource not found" : $msg; - $this->admin->json_response = ['status' => 'error', 'message' => $msg]; + $this->admin->json_response = ['status' => 'error', 'message' => htmlspecialchars($msg, ENT_QUOTES | ENT_HTML5, 'UTF-8')]; return false; } @@ -1127,7 +1155,7 @@ class AdminController extends AdminBaseController $debugger = $this->grav['debugger']; $debugger->addException($e); - $json_response = ['status' => 'error', 'message' => $e->getMessage()]; + $json_response = ['status' => 'error', 'message' => htmlspecialchars($e->getMessage(), ENT_QUOTES | ENT_HTML5, 'UTF-8')]; $this->sendJsonResponse($json_response, 200); } @@ -2062,7 +2090,7 @@ class AdminController extends AdminBaseController $debugger = $this->grav['debugger']; $debugger->addException($e); - $this->admin->json_response = ['status' => 'error', 'message' => $e->getMessage()]; + $this->admin->json_response = ['status' => 'error', 'message' => htmlspecialchars($e->getMessage(), ENT_QUOTES | ENT_HTML5, 'UTF-8')]; return false; } @@ -2219,7 +2247,7 @@ class AdminController extends AdminBaseController $this->admin->json_response = [ 'status' => 'error', 'message' => sprintf($this->admin::translate('PLUGIN_ADMIN.FILEUPLOAD_UNABLE_TO_UPLOAD'), - $filename, 'Bad filename') + htmlspecialchars($filename, ENT_QUOTES | ENT_HTML5, 'UTF-8'), 'Bad filename') ]; return false; @@ -2238,7 +2266,7 @@ class AdminController extends AdminBaseController // Check extension - $extension = strtolower(pathinfo($filename, PATHINFO_EXTENSION)); + $extension = strtolower(Utils::pathinfo($filename, PATHINFO_EXTENSION)); // If not a supported type, return if (!$extension || !$config->get("media.types.{$extension}")) { @@ -2287,7 +2315,7 @@ class AdminController extends AdminBaseController // Add metadata if needed $include_metadata = Grav::instance()['config']->get('system.media.auto_metadata_exif', false); - $basename = str_replace(['@3x', '@2x'], '', pathinfo($filename, PATHINFO_BASENAME)); + $basename = str_replace(['@3x', '@2x'], '', Utils::pathinfo($filename, PATHINFO_BASENAME)); $metadata = []; @@ -2417,7 +2445,7 @@ class AdminController extends AdminBaseController return false; } - $filename = !empty($this->post['filename']) ? basename($this->post['filename']) : null; + $filename = !empty($this->post['filename']) ? Utils::basename($this->post['filename']) : null; // Handle bad filenames. if (!$filename || !Utils::checkFilename($filename)) { @@ -2436,7 +2464,7 @@ class AdminController extends AdminBaseController if ($locator->isStream($targetPath)) { $targetPath = $locator->findResource($targetPath, true, true); } - $fileParts = pathinfo($filename); + $fileParts = Utils::pathinfo($filename); $found = false; @@ -2447,7 +2475,7 @@ class AdminController extends AdminBaseController if (!$result) { $this->admin->json_response = [ 'status' => 'error', - 'message' => $this->admin::translate('PLUGIN_ADMIN.FILE_COULD_NOT_BE_DELETED') . ': ' . $filename + 'message' => $this->admin::translate('PLUGIN_ADMIN.FILE_COULD_NOT_BE_DELETED') . ': ' . htmlspecialchars($filename, ENT_QUOTES | ENT_HTML5, 'UTF-8') ]; return false; @@ -2468,7 +2496,7 @@ class AdminController extends AdminBaseController if (!$result) { $this->admin->json_response = [ 'status' => 'error', - 'message' => $this->admin::translate('PLUGIN_ADMIN.FILE_COULD_NOT_BE_DELETED') . ': ' . $filename + 'message' => $this->admin::translate('PLUGIN_ADMIN.FILE_COULD_NOT_BE_DELETED') . ': ' . htmlspecialchars($filename, ENT_QUOTES | ENT_HTML5, 'UTF-8') ]; return false; @@ -2483,7 +2511,7 @@ class AdminController extends AdminBaseController if (!$found) { $this->admin->json_response = [ 'status' => 'error', - 'message' => $this->admin::translate('PLUGIN_ADMIN.FILE_NOT_FOUND') . ': ' . $filename + 'message' => $this->admin::translate('PLUGIN_ADMIN.FILE_NOT_FOUND') . ': ' . htmlspecialchars($filename, ENT_QUOTES | ENT_HTML5, 'UTF-8') ]; return false; @@ -2494,7 +2522,7 @@ class AdminController extends AdminBaseController $this->admin->json_response = [ 'status' => 'success', - 'message' => $this->admin::translate('PLUGIN_ADMIN.FILE_DELETED') . ': ' . $filename + 'message' => $this->admin::translate('PLUGIN_ADMIN.FILE_DELETED') . ': ' . htmlspecialchars($filename, ENT_QUOTES | ENT_HTML5, 'UTF-8') ]; return true; @@ -2620,7 +2648,7 @@ class AdminController extends AdminBaseController $payload = [ 'name' => $file_page ? $file_page->title() : $fileName, 'value' => $file_page ? $file_page->rawRoute() : $file_path, - 'item-key' => basename($file_page ? $file_page->route() : $file_path), + 'item-key' => Utils::basename($file_page ? $file_page->route() : $file_path), 'filename' => $fileName, 'extension' => $type === 'dir' ? '' : $fileInfo->getExtension(), 'type' => $type, diff --git a/plugins/admin/classes/plugin/AdminForm.php b/plugins/admin/classes/plugin/AdminForm.php index 668ddbd..3402136 100644 --- a/plugins/admin/classes/plugin/AdminForm.php +++ b/plugins/admin/classes/plugin/AdminForm.php @@ -3,7 +3,7 @@ /** * @package Grav\Plugin\Admin * - * @copyright Copyright (c) 2015 - 2021 Trilby Media, LLC. All rights reserved. + * @copyright Copyright (c) 2015 - 2022 Trilby Media, LLC. All rights reserved. * @license MIT License; see LICENSE file for details. */ diff --git a/plugins/admin/classes/plugin/Controllers/AbstractController.php b/plugins/admin/classes/plugin/Controllers/AbstractController.php index ff7a8ff..4949727 100644 --- a/plugins/admin/classes/plugin/Controllers/AbstractController.php +++ b/plugins/admin/classes/plugin/Controllers/AbstractController.php @@ -292,7 +292,7 @@ abstract class AbstractController implements RequestHandlerInterface $response = [ 'code' => $code, 'status' => 'error', - 'message' => $message + 'message' => htmlspecialchars($message, ENT_QUOTES | ENT_HTML5, 'UTF-8') ]; $accept = $this->getAccept(['application/json', 'text/html']); diff --git a/plugins/admin/classes/plugin/Controllers/AdminController.php b/plugins/admin/classes/plugin/Controllers/AdminController.php index 600cfb5..7b4c576 100644 --- a/plugins/admin/classes/plugin/Controllers/AdminController.php +++ b/plugins/admin/classes/plugin/Controllers/AdminController.php @@ -3,7 +3,7 @@ /** * @package Grav\Plugin\Admin * - * @copyright Copyright (c) 2015 - 2021 Trilby Media, LLC. All rights reserved. + * @copyright Copyright (c) 2015 - 2022 Trilby Media, LLC. All rights reserved. * @license MIT License; see LICENSE file for details. */ diff --git a/plugins/admin/classes/plugin/Controllers/Login/LoginController.php b/plugins/admin/classes/plugin/Controllers/Login/LoginController.php index 4baf402..44790e3 100644 --- a/plugins/admin/classes/plugin/Controllers/Login/LoginController.php +++ b/plugins/admin/classes/plugin/Controllers/Login/LoginController.php @@ -3,7 +3,7 @@ /** * @package Grav\Plugin\Admin * - * @copyright Copyright (c) 2015 - 2021 Trilby Media, LLC. All rights reserved. + * @copyright Copyright (c) 2015 - 2022 Trilby Media, LLC. All rights reserved. * @license MIT License; see LICENSE file for details. */ @@ -15,6 +15,7 @@ use Grav\Common\Page\Pages; use Grav\Common\Uri; use Grav\Common\User\Interfaces\UserCollectionInterface; use Grav\Common\User\Interfaces\UserInterface; +use Grav\Common\Utils; use Grav\Framework\RequestHandler\Exception\PageExpiredException; use Grav\Framework\RequestHandler\Exception\RequestException; use Grav\Plugin\Admin\Admin; @@ -111,7 +112,7 @@ class LoginController extends AdminController { $uri = (string)$this->getRequest()->getUri(); - $ext = pathinfo($uri, PATHINFO_EXTENSION); + $ext = Utils::pathinfo($uri, PATHINFO_EXTENSION); $accept = $this->getAccept(['application/json', 'text/html']); if ($ext === 'json' || $accept === 'application/json') { return $this->createErrorResponse(new RequestException($this->getRequest(), $this->translate('PLUGIN_ADMIN.LOGGED_OUT'), 401)); @@ -140,7 +141,7 @@ class LoginController extends AdminController } $post = $this->getPost(); - $credentials = $post['data'] ?? []; + $credentials = (array)($post['data'] ?? []); $login = $this->getLogin(); $config = $this->getConfig(); @@ -275,11 +276,17 @@ class LoginController extends AdminController $twoFa = null; } - $code = $data['2fa_code'] ?? null; - $secret = $user->twofa_secret ?? null; + $code = $data['2fa_code'] ?? ''; + $secret = $user->twofa_secret ?? ''; + $twofa_valid = $twoFa->verifyCode($secret, $code); + + $yubikey_otp = $data['yubikey_otp'] ?? ''; + $yubikey_id = $user->yubikey_id ?? ''; + $yubikey_valid = $twoFa->verifyYubikeyOTP($yubikey_id, $yubikey_otp); + $redirect = (string)$this->getRequest()->getUri(); - if (null === $twoFa || !$user->authenticated || !$code || !$secret || !$twoFa->verifyCode($secret, $code)) { + if (null === $twoFa || !$user->authenticated || (!$twofa_valid && !$yubikey_valid) ) { Admin::DEBUG && Admin::addDebugMessage('Admin login: 2FA check failed, log out!'); // Failed 2FA auth, logout and redirect to the current page. diff --git a/plugins/admin/classes/plugin/Gpm.php b/plugins/admin/classes/plugin/Gpm.php index 3b6b3f9..61975d0 100644 --- a/plugins/admin/classes/plugin/Gpm.php +++ b/plugins/admin/classes/plugin/Gpm.php @@ -7,8 +7,8 @@ use Grav\Common\Grav; use Grav\Common\GPM\GPM as GravGPM; use Grav\Common\GPM\Licenses; use Grav\Common\GPM\Installer; -use Grav\Common\GPM\Response; use Grav\Common\GPM\Upgrader; +use Grav\Common\HTTP\Response; use Grav\Common\Filesystem\Folder; use Grav\Common\GPM\Common\Package; @@ -118,6 +118,8 @@ class Gpm } } + Cache::clearCache(); + return $messages ?: true; } @@ -190,6 +192,8 @@ class Gpm } } + Cache::clearCache(); + return true; } @@ -277,6 +281,7 @@ class Gpm } Folder::delete($tmp_zip); + Cache::clearCache(); return true; } @@ -311,7 +316,7 @@ class Gpm $bad_chars = array_merge(array_map('chr', range(0, 31)), ['<', '>', ':', '"', '/', '\\', '|', '?', '*']); - $filename = $package->slug . str_replace($bad_chars, '', basename($package->zipball_url)); + $filename = $package->slug . str_replace($bad_chars, '', \Grav\Common\Utils::basename($package->zipball_url)); $filename = preg_replace('/[\\\\\/:"*?&<>|]+/m', '-', $filename); file_put_contents($tmp_dir . DS . $filename . '.zip', $contents); @@ -358,7 +363,7 @@ class Gpm $error[] = '

Grav has increased the minimum PHP requirement.
'; $error[] = 'You are currently running PHP ' . phpversion() . ''; $error[] = ', but PHP ' . $upgrader->minPHPVersion() . ' is required.

'; - $error[] = '

Additional information

'; + $error[] = '

Additional information

'; Installer::setError(implode("\n", $error)); diff --git a/plugins/admin/classes/plugin/Router.php b/plugins/admin/classes/plugin/Router.php index 2c86661..c9e3217 100644 --- a/plugins/admin/classes/plugin/Router.php +++ b/plugins/admin/classes/plugin/Router.php @@ -67,6 +67,6 @@ class Router extends ProcessorBase $this->stopTimer(); // Never allow admin pages to be rendered in ,