diff --git a/CHANGELOG.md b/CHANGELOG.md index c27ca3ad..3f90bf2d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,22 @@ +# v1.0.7 +## 01/15/2016 + +1. [](#new) + * Added onAdminDashboard event + * Added onAdminSave event + * New lang strings for reverse proxy toggle +1. [](#improved) + * More robust YAML file checking in config folders + * Removed deprecated menu event + * Removed old logs code + * Used new onAdminDashboard event for current dashboard widgets +1. [](#bugfix) + * Fix for missing access checks on config pages #397 + * Fix parent not loaded on admin form save #587 + * When no route field is added to a page blueprint, add it as page root + * Fix for wrong page count (will show dynamic added pages in count too - Need to fix this) + * Fix for IE/Edge saving forms #391 + # v1.0.6 ## 01/07/2016 diff --git a/admin.php b/admin.php index 0c80ca52..0d96b067 100644 --- a/admin.php +++ b/admin.php @@ -17,6 +17,10 @@ use RocketTheme\Toolbox\Session\Session; class AdminPlugin extends Plugin { + public $features = [ + 'blueprints' => 1000, + ]; + /** * @var bool */ @@ -96,10 +100,10 @@ class AdminPlugin extends Plugin // check for existence of a user account $account_dir = $file_path = $this->grav['locator']->findResource('account://'); - $user_check = (array) glob($account_dir . '/*.yaml'); + $user_check = glob($account_dir . '/*.yaml'); // If no users found, go to register - if (!count($user_check) > 0) { + if ($user_check == false || count((array)$user_check) == 0) { if (!$this->isAdminPath()) { $this->grav->redirect($this->base); } @@ -119,12 +123,11 @@ class AdminPlugin extends Plugin * - 'password1' for password format * - 'password2' for equality to password1 * - * @param object $form The form * @param string $type The field type * @param string $value The field value * @param string $extra Any extra value required * - * @return mixed + * @return bool */ protected function validate($type, $value, $extra = '') { @@ -134,22 +137,21 @@ class AdminPlugin extends Plugin return false; } return true; - break; case 'password1': if (!preg_match('/(?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{8,}/', $value)) { return false; } return true; - break; case 'password2': if (strcmp($value, $extra)) { return false; } return true; - break; } + + return false; } /** @@ -255,7 +257,7 @@ class AdminPlugin extends Plugin } // Replace themes service with admin. - $this->grav['themes'] = function ($c) { + $this->grav['themes'] = function () { require_once __DIR__ . '/classes/themes.php'; return new Themes($this->grav); }; @@ -328,6 +330,8 @@ class AdminPlugin extends Plugin // make sure page is not frozen! unset($this->grav['page']); + $this->admin->pagesCount(); + // Replace page service with admin. $this->grav['page'] = function () use ($self) { $page = new Page; @@ -352,6 +356,8 @@ class AdminPlugin extends Plugin return $page; } } + + return null; }; if (empty($this->grav['page'])) { @@ -405,16 +411,12 @@ class AdminPlugin extends Plugin { $twig = $this->grav['twig']; - // Dynamic type support - $format = $this->uri->extension(); - $ext = '.' . ($format ? $format : 'html') . TWIG_EXT; - $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->config->get('plugins.admin.route'), '/'); $twig->twig_vars['base_url_relative'] = $twig->twig_vars['base_url_simple'] . '/' . $twig->twig_vars['admin_route']; - $twig->twig_vars['theme_url'] = '/user/plugins/admin/themes/' . $this->theme; + $twig->twig_vars['theme_url'] = $this->grav['locator']->findResource('plugin://admin/themes/' . $this->theme, false); $twig->twig_vars['base_url'] = $twig->twig_vars['base_url_relative']; $twig->twig_vars['base_path'] = GRAV_ROOT; $twig->twig_vars['admin'] = $this->admin; @@ -538,15 +540,6 @@ class AdminPlugin extends Plugin throw new \RuntimeException('One of the required plugins is missing or not enabled'); } - // Double check we have system.yaml and site.yaml - $config_files[] = $this->grav['locator']->findResource('user://config') . '/system.yaml'; - $config_files[] = $this->grav['locator']->findResource('user://config') . '/site.yaml'; - foreach ($config_files as $config_file) { - if (!file_exists($config_file)) { - touch($config_file); - } - } - // Initialize Admin Language if needed /** @var Language $language */ $language = $this->grav['language']; @@ -570,9 +563,19 @@ class AdminPlugin extends Plugin $this->admin = new Admin($this->grav, $this->base, $this->template, $this->route); + // And store the class into DI container. $this->grav['admin'] = $this->admin; + // Double check we have system.yam, site.yaml etc + $config_path = $this->grav['locator']->findResource('user://config'); + foreach ($this->admin->configurations() as $config_file) { + $config_file = "{$config_path}/{$config_file}.yaml"; + if (!file_exists($config_file)) { + touch($config_file); + } + } + // Get theme for admin $this->theme = $this->config->get('plugins.admin.theme', 'grav'); diff --git a/blueprints.yaml b/blueprints.yaml index 0a2f95c9..90b8876a 100644 --- a/blueprints.yaml +++ b/blueprints.yaml @@ -1,5 +1,5 @@ name: Admin Panel -version: 1.0.6 +version: 1.0.7 description: Adds an advanced administration panel to manage your site icon: empire author: @@ -27,12 +27,12 @@ form: enabled: type: hidden - label: Plugin status + label: PLUGIN_ADMIN.PLUGIN_STATUS highlight: 1 default: 0 options: - 1: Enabled - 0: Disabled + 1: PLUGIN_ADMIN.ENABLED + 0: PLUGIN_ADMIN.DISABLED validate: type: bool @@ -64,8 +64,8 @@ form: highlight: 1 default: 1 options: - 1: Enabled - 0: Disabled + 1: PLUGIN_ADMIN.ENABLED + 0: PLUGIN_ADMIN.DISABLED validate: type: bool help: Use Google custom fonts. Disable this to use Helvetica. Useful when using Cyrillic and other languages with unsupported characters. @@ -79,8 +79,8 @@ form: highlight: 1 default: 1 options: - 1: Enabled - 0: Disabled + 1: PLUGIN_ADMIN.ENABLED + 0: PLUGIN_ADMIN.DISABLED validate: type: bool help: Show the "Found an issue? Please report it on GitHub." message. @@ -91,8 +91,8 @@ form: highlight: 1 default: 1 options: - 1: Enabled - 0: Disabled + 1: PLUGIN_ADMIN.ENABLED + 0: PLUGIN_ADMIN.DISABLED validate: type: bool help: Shows an informative message, in the admin panel, when an update is available. @@ -112,8 +112,8 @@ form: highlight: 1 default: 1 options: - 1: Enabled - 0: Disabled + 1: PLUGIN_ADMIN.ENABLED + 0: PLUGIN_ADMIN.DISABLED validate: type: bool help: Ask the user confirmation when deleting a page @@ -129,8 +129,8 @@ form: highlight: 1 default: 1 options: - 1: Enabled - 0: Disabled + 1: PLUGIN_ADMIN.ENABLED + 0: PLUGIN_ADMIN.DISABLED validate: type: bool help: Enable the visitors stats collecting feature diff --git a/classes/admin.php b/classes/admin.php index 2ad43c7e..90c25833 100644 --- a/classes/admin.php +++ b/classes/admin.php @@ -15,7 +15,7 @@ use Grav\Common\User\User; use Grav\Common\Utils; use RocketTheme\Toolbox\File\File; use RocketTheme\Toolbox\File\JsonFile; -use RocketTheme\Toolbox\File\LogFile; +use RocketTheme\Toolbox\ResourceLocator\UniformResourceIterator; use RocketTheme\Toolbox\ResourceLocator\UniformResourceLocator; use RocketTheme\Toolbox\Session\Message; use RocketTheme\Toolbox\Session\Session; @@ -71,15 +71,15 @@ class Admin public $user; /** - * @var Lang - */ - protected $lang; - - /** - * @var Grav\Common\GPM\GPM + * @var GPM */ protected $gpm; + /** + * @var int + */ + protected $pages_count; + /** * Constructor. * @@ -184,8 +184,7 @@ class Admin /** @var Grav $grav */ $grav = $this->grav; - $this->setMessage($this->translate('PLUGIN_ADMIN.LOGIN_LOGGED_IN', [$this->user->language]), 'info'); - + $this->setMessage($this->translate('PLUGIN_ADMIN.LOGIN_LOGGED_IN'), 'info'); $redirect_route = $this->uri->route(); $grav->redirect($redirect_route); } @@ -340,9 +339,12 @@ class Admin $type = preg_replace('|config/|', '', $type); $blueprints = $this->blueprints("config/{$type}"); $config = $this->grav['config']; - $obj = new Data\Data($config->get($type), $blueprints); + $obj = new Data\Data($config->get($type, []), $blueprints); $obj->merge($post); - $file = CompiledYamlFile::instance($this->grav['locator']->findResource("config://{$type}.yaml")); + // 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; } else { @@ -386,6 +388,8 @@ class Admin /** * Get all routes. * + * @param bool $unique + * * @return array */ public function routes($unique = false) @@ -406,12 +410,13 @@ class Admin * * @return array */ - public function countPages() + public function pagesCount() { - $routable = $this->grav['pages']->all()->routable(); - $modular = $this->grav['pages']->all()->modular(); + if (!$this->pages_count) { + $this->pages_count = count($this->grav['pages']->all()); + } - return count($routable) + count($modular); + return $this->pages_count; } /** @@ -451,6 +456,8 @@ class Admin /** * Get all plugins. * + * @param bool $local + * * @return array */ public function plugins($local = true) @@ -458,7 +465,7 @@ class Admin $gpm = $this->gpm(); if (!$gpm) { - return; + return false; } return $local ? $gpm->getInstalledPlugins() : $gpm->getRepositoryPlugins()->filter(function ( @@ -472,6 +479,8 @@ class Admin /** * Get all themes. * + * @param bool $local + * * @return array */ public function themes($local = true) @@ -479,7 +488,7 @@ class Admin $gpm = $this->gpm(); if (!$gpm) { - return; + return false; } return $local ? $gpm->getInstalledThemes() : $gpm->getRepositoryThemes()->filter(function ($package, $slug) use @@ -496,7 +505,7 @@ class Admin * * @param integer $count number of pages to pull back * - * @return array + * @return array|null */ public function latestPages($count = 10) { @@ -506,7 +515,7 @@ class Admin $latest = array(); if(is_null($pages->routes())){ - return; + return null; } foreach ($pages->routes() as $url => $path) { @@ -698,18 +707,19 @@ class Admin } /** - * Return the configuration files found + * Return the found configuration blueprints * * @return array */ public static function configurations() { $configurations = []; - $path = Grav::instance()['locator']->findResource('user://config'); - /** @var \DirectoryIterator $directory */ - foreach (new \DirectoryIterator($path) as $file) { - if ($file->isDir() || $file->isDot() || !preg_match('/^[^.].*.yaml$/', $file->getFilename())) { + /** @var UniformResourceIterator $iterator */ + $iterator = Grav::instance()['locator']->getIterator('blueprints://config'); + + foreach ($iterator as $file) { + if ($file->isDir() || !preg_match('/^[^.].*.yaml$/', $file->getFilename())) { continue; } $configurations[] = basename($file->getBasename(), '.yaml'); @@ -744,6 +754,7 @@ class Admin $pages = Grav::instance()['pages']; $route = '/' . ltrim(Grav::instance()['admin']->route, '/'); + /** @var Page $page */ $page = $pages->dispatch($route); $parent_route = null; if ($page) { @@ -824,14 +835,11 @@ class Admin /** * Translate a string to the user-defined language * - * @param $string the string to translate + * @param array|mixed $args + * + * @return string */ - public function translate($string) - { - return $this->_translate($string, [$this->grav['user']->authenticated ? $this->grav['user']->language : 'en']); - } - - public function _translate($args, Array $languages = null, $array_support = false, $html_out = false) + public function translate($args) { if (is_array($args)) { $lookup = array_shift($args); @@ -840,6 +848,8 @@ class Admin $args = []; } + $languages = [$this->grav['user']->authenticated ? $this->grav['user']->language : 'en']; + if ($lookup) { if (empty($languages) || reset($languages) == null) { if ($this->grav['config']->get('system.languages.translations_fallback', true)) { @@ -848,22 +858,19 @@ class Admin $languages = (array)$this->grav['language']->getDefault(); } } - } else { - $languages = ['en']; } - foreach ((array)$languages as $lang) { - $translation = $this->grav['language']->getTranslation($lang, $lookup, $array_support); + $translation = $this->grav['language']->getTranslation($lang, $lookup); if (!$translation) { $language = $this->grav['language']->getDefault() ?: 'en'; - $translation = $this->grav['language']->getTranslation($language, $lookup, $array_support); + $translation = $this->grav['language']->getTranslation($language, $lookup); } if (!$translation) { $language = 'en'; - $translation = $this->grav['language']->getTranslation($language, $lookup, $array_support); + $translation = $this->grav['language']->getTranslation($language, $lookup); } if ($translation) { @@ -878,6 +885,10 @@ class Admin return $lookup; } + /** + * @param string $php_format + * @return string + */ function dateformat2Kendo($php_format) { $SYMBOLS_MATCHING = array( diff --git a/classes/controller.php b/classes/controller.php index d5761740..c30784ae 100644 --- a/classes/controller.php +++ b/classes/controller.php @@ -8,7 +8,7 @@ use Grav\Common\GPM\Installer; use Grav\Common\Grav; use Grav\Common\Uri; use Grav\Common\Data; -use Grav\Common\Page; +use Grav\Common\Page\Page; use Grav\Common\Page\Pages; use Grav\Common\Page\Collection; use Grav\Common\Plugin; @@ -16,10 +16,10 @@ use Grav\Common\Theme; use Grav\Common\User\User; use Grav\Common\Utils; use Grav\Common\Backup\ZipBackup; -use Grav\Common\Markdown\Parsedown; -use Grav\Common\Markdown\ParsedownExtra; +use RocketTheme\Toolbox\Event\Event; use RocketTheme\Toolbox\File\File; use RocketTheme\Toolbox\File\JsonFile; +use Symfony\Component\Yaml\Exception\ParseException; use Symfony\Component\Yaml\Yaml; class AdminController @@ -380,7 +380,7 @@ class AdminController protected function taskClearCache() { if (!$this->authorizeTask('clear cache', ['admin.cache', 'admin.super'])) { - return; + return false; } // get optional cleartype param @@ -411,7 +411,7 @@ class AdminController { $param_sep = $this->grav['config']->get('system.param_sep', ':'); if (!$this->authorizeTask('backup', ['admin.maintenance', 'admin.super'])) { - return; + return false; } $download = $this->grav['uri']->param('download'); @@ -562,7 +562,7 @@ class AdminController protected function taskListmedia() { if (!$this->authorizeTask('list media', ['admin.pages', 'admin.super'])) { - return; + return false; } $page = $this->admin->page(true); @@ -583,11 +583,13 @@ class AdminController /** * Handles adding a media file to a page + * + * @return bool True if the action was performed. */ protected function taskAddmedia() { if (!$this->authorizeTask('add media', ['admin.pages', 'admin.super'])) { - return; + return false; } $page = $this->admin->page(true); @@ -597,7 +599,7 @@ class AdminController if (!isset($_FILES['file']['error']) || is_array($_FILES['file']['error'])) { $this->admin->json_response = ['status' => 'error', 'message' => $this->admin->translate('PLUGIN_ADMIN.INVALID_PARAMETERS')]; - return; + return false; } // Check $_FILES['file']['error'] value. @@ -606,44 +608,47 @@ class AdminController break; case UPLOAD_ERR_NO_FILE: $this->admin->json_response = ['status' => 'error', 'message' => $this->admin->translate('PLUGIN_ADMIN.NO_FILES_SENT')]; - return; + return false; case UPLOAD_ERR_INI_SIZE: case UPLOAD_ERR_FORM_SIZE: $this->admin->json_response = ['status' => 'error', 'message' => $this->admin->translate('PLUGIN_ADMIN.EXCEEDED_FILESIZE_LIMIT')]; - return; + return false; default: $this->admin->json_response = ['status' => 'error', 'message' => $this->admin->translate('PLUGIN_ADMIN.UNKNOWN_ERRORS')]; - return; + 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) { $this->admin->json_response = ['status' => 'error', 'message' => $this->admin->translate('PLUGIN_ADMIN.EXCEEDED_GRAV_FILESIZE_LIMIT')]; - return; + return false; } // Check extension $fileParts = pathinfo($_FILES['file']['name']); - $fileExt = strtolower($fileParts['extension']); - // If not a supported type, return - if (!$config->get("media.{$fileExt}")) { - $this->admin->json_response = ['status' => 'error', 'message' => $this->admin->translate('PLUGIN_ADMIN.UNSUPPORTED_FILE_TYPE') . ': '.$fileExt]; - return; + $fileExt = ''; + if (isset($fileParts['extension'])) { + $fileExt = strtolower($fileParts['extension']); } + // If not a supported type, return + if (!$fileExt || !$config->get("media.{$fileExt}")) { + $this->admin->json_response = ['status' => 'error', 'message' => $this->admin->translate('PLUGIN_ADMIN.UNSUPPORTED_FILE_TYPE') . ': '.$fileExt]; + return false; + } // Upload it if (!move_uploaded_file($_FILES['file']['tmp_name'], sprintf('%s/%s', $page->path(), $_FILES['file']['name']))) { $this->admin->json_response = ['status' => 'error', 'message' => $this->admin->translate('PLUGIN_ADMIN.FAILED_TO_MOVE_UPLOADED_FILE')]; - return; + return false; } $this->admin->json_response = ['status' => 'success', 'message' => $this->admin->translate('PLUGIN_ADMIN.FILE_UPLOADED_SUCCESSFULLY')]; - return; + return true; } /** @@ -654,7 +659,7 @@ class AdminController protected function taskDelmedia() { if (!$this->authorizeTask('delete media', ['admin.pages', 'admin.super'])) { - return; + return false; } $page = $this->admin->page(true); @@ -666,7 +671,7 @@ class AdminController $filename = !empty($this->post['filename']) ? $this->post['filename'] : null; if ($filename) { - $targetPath = $page->path().'/'.$filename; + $targetPath = $page->path() . '/' . $filename; if (file_exists($targetPath)) { if (unlink($targetPath)) { @@ -677,18 +682,20 @@ class AdminController } else { //Try with responsive images @1x, @2x, @3x $ext = pathinfo($targetPath, PATHINFO_EXTENSION); - $filename = $page->path() . '/'. basename($targetPath, ".$ext"); - $responsiveTargetPath = $filename . '@1x.' . $ext; + $fullPathFilename = $page->path() . '/'. basename($targetPath, ".$ext"); + $responsiveTargetPath = $fullPathFilename . '@1x.' . $ext; + $deletedResponsiveImage = false; if (file_exists($responsiveTargetPath) && unlink($responsiveTargetPath)) { $deletedResponsiveImage = true; } - $responsiveTargetPath = $filename . '@2x.' . $ext; + $responsiveTargetPath = $fullPathFilename . '@2x.' . $ext; if (file_exists($responsiveTargetPath) && unlink($responsiveTargetPath)) { $deletedResponsiveImage = true; } - $responsiveTargetPath = $filename . '@3x.' . $ext; + + $responsiveTargetPath = $fullPathFilename . '@3x.' . $ext; if (file_exists($responsiveTargetPath) && unlink($responsiveTargetPath)) { $deletedResponsiveImage = true; } @@ -709,6 +716,8 @@ class AdminController /** * Process the page Markdown + * + * @return bool True if the action was performed. */ protected function taskProcessMarkdown() { @@ -734,11 +743,13 @@ class AdminController $html = $page->content(); $this->admin->json_response = ['status' => 'success', 'message' => $html]; - return true; } catch (\Exception $e) { $this->admin->json_response = ['status' => 'error', 'message' => $e->getMessage()]; + return false; } + + return true; } /** @@ -749,7 +760,7 @@ class AdminController public function taskEnable() { if (!$this->authorizeTask('enable plugin', ['admin.plugins', 'admin.super'])) { - return; + return false; } if ($this->view != 'plugins') { @@ -775,7 +786,7 @@ class AdminController public function taskDisable() { if (!$this->authorizeTask('disable plugin', ['admin.plugins', 'admin.super'])) { - return; + return false; } if ($this->view != 'plugins') { @@ -801,7 +812,7 @@ class AdminController public function taskActivate() { if (!$this->authorizeTask('activate theme', ['admin.themes', 'admin.super'])) { - return; + return false; } if ($this->view != 'themes') { @@ -840,7 +851,7 @@ class AdminController { $type = $this->view === 'plugins' ? 'plugins' : 'themes'; if (!$this->authorizeTask('install ' . $type, ['admin.' . $type, 'admin.super'])) { - return; + return false; } require_once __DIR__ . '/gpm.php'; @@ -915,7 +926,7 @@ class AdminController foreach ($permissions as $type => $p) { if (!$this->authorizeTask('update ' . $type , $p)) { - return; + return false; } } @@ -951,7 +962,7 @@ class AdminController { $type = $this->view === 'plugins' ? 'plugins' : 'themes'; if (!$this->authorizeTask('uninstall ' . $type, ['admin.' . $type, 'admin.super'])) { - return; + return false; } require_once __DIR__ . '/gpm.php'; @@ -971,6 +982,11 @@ class AdminController return true; } + /** + * @param string $key + * @param string $file + * @return bool + */ private function cleanFilesData($key, $file) { $config = $this->grav['config']; @@ -1031,6 +1047,11 @@ class AdminController return $cleanFiles[$key]; } + /** + * @param string $needle + * @param array|string $haystack + * @return bool + */ private function match_in_array($needle, $haystack) { foreach ((array)$haystack as $item) { @@ -1042,6 +1063,10 @@ class AdminController return false; } + /** + * @param mixed $obj + * @return mixed + */ private function processFiles($obj) { foreach ((array)$_FILES as $key => $file) { @@ -1108,6 +1133,29 @@ class AdminController return true; } + /* + * @param string $frontmatter + * @return bool + */ + 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) { + return false; + } + + return true; + } + /** * Handles form and saves the input data if its valid. * @@ -1116,21 +1164,27 @@ class AdminController public function taskSave() { if (!$this->authorizeTask('save', $this->dataPermissions())) { - return; + return false; } $data = $this->post; + $config = $this->grav['config']; + // Special handler for pages data. if ($this->view == 'pages') { - /** @var Page\Pages $pages */ + /** @var Pages $pages */ $pages = $this->grav['pages']; - $config = $this->grav['config']; // Find new parent page in order to build the path. $route = !isset($data['route']) ? dirname($this->admin->route) : $data['route']; $obj = $this->admin->page(true); + if (isset($data['frontmatter']) && !$this->checkValidFrontmatter($data['frontmatter'])) { + $this->admin->setMessage($this->admin->translate('PLUGIN_ADMIN.INVALID_FRONTMATTER_COULD_NOT_SAVE'), 'error'); + return false; + } + //Handle system.home.hide_in_urls $hide_home_route = $config->get('system.home.hide_in_urls', false); if ($hide_home_route) { @@ -1187,6 +1241,9 @@ class AdminController } if ($obj) { + // Event to manipulate data before saving the object + $this->grav->fireEvent('onAdminSave', new Event(['object' => &$obj])); + $obj->save(true); $this->admin->setMessage($this->admin->translate('PLUGIN_ADMIN.SUCCESSFULLY_SAVED'), 'info'); } @@ -1203,7 +1260,7 @@ class AdminController } // Always redirect if a page route was changed, to refresh it - if ($obj instanceof Page\Page) { + if ($obj instanceof Page) { if (method_exists($obj, 'unsetRouteSlug')) { $obj->unsetRouteSlug(); } @@ -1287,7 +1344,7 @@ class AdminController protected function taskCopy() { if (!$this->authorizeTask('copy page', ['admin.pages', 'admin.super'])) { - return; + return false; } // Only applies to pages. @@ -1296,7 +1353,7 @@ class AdminController } try { - /** @var Page\Pages $pages */ + /** @var Pages $pages */ $pages = $this->grav['pages']; $data = $this->post; @@ -1345,7 +1402,7 @@ class AdminController protected function taskReorder() { if (!$this->authorizeTask('reorder pages', ['admin.pages', 'admin.super'])) { - return; + return false; } // Only applies to pages. @@ -1366,7 +1423,7 @@ class AdminController protected function taskDelete() { if (!$this->authorizeTask('delete page', ['admin.pages', 'admin.super'])) { - return; + return false; } // Only applies to pages. @@ -1443,7 +1500,7 @@ class AdminController protected function taskSaveas() { if (!$this->authorizeTask('save', $this->dataPermissions())) { - return; + return false; } $data = $this->post; @@ -1480,7 +1537,7 @@ class AdminController $aFile = File::instance($path); $aFile->save(); - $aPage = new Page\Page(); + $aPage = new Page(); $aPage->init(new \SplFileInfo($path), $language .'.md'); $aPage->header($obj->header()); $aPage->rawMarkdown($obj->rawMarkdown()); diff --git a/classes/gpm.php b/classes/gpm.php index 38b72757..7e9962e8 100644 --- a/classes/gpm.php +++ b/classes/gpm.php @@ -1,7 +1,7 @@ false ]; - public static function install($packages, $options) + /** + * @param Package[]|string[]|string $packages + * @param array $options + * @return bool + */ + public static function install($packages, array $options) { $options = array_merge(self::$options, $options); @@ -93,13 +96,24 @@ class Gpm return true; } - public static function update($packages, $options) + /** + * @param Package[]|string[]|string $packages + * @param array $options + * @return bool + */ + public static function update($packages, array $options) { $options['overwrite'] = true; + return static::install($packages, $options); } - public static function uninstall($packages, $options) + /** + * @param Package[]|string[]|string $packages + * @param array $options + * @return bool + */ + public static function uninstall($packages, array $options) { $options = array_merge(self::$options, $options); @@ -124,7 +138,7 @@ class Gpm foreach ($packages as $package) { - $location = self::getGrav()['locator']->findResource($package->package_type . '://' . $package->slug); + $location = Grav::instance()['locator']->findResource($package->package_type . '://' . $package->slug); // Check destination Installer::isValidDestination($location); @@ -144,11 +158,15 @@ class Gpm return true; } - private static function download($package) + /** + * @param Package $package + * @return string + */ + private static function download(Package $package) { $contents = Response::get($package->zipball_url, []); - $cache_dir = self::getGrav()['locator']->findResource('cache://', true); + $cache_dir = Grav::instance()['locator']->findResource('cache://', true); $cache_dir = $cache_dir . DS . 'tmp/Grav-' . uniqid(); Folder::mkdir($cache_dir); @@ -159,7 +177,12 @@ class Gpm return $cache_dir . DS . $filename . '.zip'; } - private static function _downloadSelfupgrade($package, $tmp) + /** + * @param array $package + * @param string $tmp + * @return string + */ + private static function _downloadSelfupgrade(array $package, $tmp) { $output = Response::get($package['download'], []); Folder::mkdir($tmp); @@ -167,6 +190,9 @@ class Gpm return $tmp . DS . $package['name']; } + /** + * @return bool + */ public static function selfupgrade() { $upgrader = new Upgrader(); diff --git a/classes/popularity.php b/classes/popularity.php index 71555a0f..eacf2653 100644 --- a/classes/popularity.php +++ b/classes/popularity.php @@ -3,16 +3,15 @@ namespace Grav\Plugin; use Grav\Common\Config\Config; use Grav\Common\Grav; -use Grav\Common\Plugins; -use Grav\Common\Themes; use Grav\Common\Page\Page; use Grav\Common\Data; -use Grav\Common\GravTrait; +/** + * Class Popularity + * @package Grav\Plugin + */ class Popularity { - use GravTrait; - /** @var Config */ protected $config; protected $data_path; @@ -36,9 +35,9 @@ class Popularity public function __construct() { - $this->config = self::getGrav()['config']; + $this->config = Grav::instance()['config']; - $this->data_path = self::$grav['locator']->findResource('log://popularity', true, true); + $this->data_path = Grav::instance()['locator']->findResource('log://popularity', true, true); $this->daily_file = $this->data_path.'/'.self::DAILY_FILE; $this->monthly_file = $this->data_path.'/'.self::MONTHLY_FILE; $this->totals_file = $this->data_path.'/'.self::TOTALS_FILE; @@ -49,13 +48,13 @@ class Popularity public function trackHit() { // Don't track bot or crawler requests - if (!self::getGrav()['browser']->isHuman()) { + if (!Grav::instance()['browser']->isHuman()) { return; } /** @var Page $page */ - $page = self::getGrav()['page']; - $relative_url = str_replace(self::getGrav()['base_url_relative'], '', $page->url()); + $page = Grav::instance()['page']; + $relative_url = str_replace(Grav::instance()['base_url_relative'], '', $page->url()); // Don't track error pages or pages that have no route if ($page->template() == 'error' || !$page->route()) { @@ -79,7 +78,7 @@ class Popularity $this->updateDaily(); $this->updateMonthly(); $this->updateTotals($page->route()); - $this->updateVisitors(self::getGrav()['uri']->ip()); + $this->updateVisitors(Grav::instance()['uri']->ip()); } @@ -110,6 +109,9 @@ class Popularity file_put_contents($this->daily_file, json_encode($this->daily_data)); } + /** + * @return array + */ public function getDailyChartData() { if (!$this->daily_data) { @@ -123,13 +125,16 @@ class Popularity $data = array(); foreach ($chart_data as $date => $count) { - $labels[] = self::getGrav()['grav']['admin']->translate(['PLUGIN_ADMIN.' . strtoupper(date('D', strtotime($date)))]); + $labels[] = Grav::instance()['grav']['admin']->translate(['PLUGIN_ADMIN.' . strtoupper(date('D', strtotime($date)))]); $data[] = $count; } return array('labels' => json_encode($labels), 'data' => json_encode($data)); } + /** + * @return int + */ public function getDailyTotal() { if (!$this->daily_data) { @@ -143,6 +148,9 @@ class Popularity } } + /** + * @return int + */ public function getWeeklyTotal() { if (!$this->daily_data) { @@ -160,6 +168,9 @@ class Popularity return $total; } + /** + * @return int + */ public function getMonthlyTotal() { if (!$this->monthly_data) { @@ -197,6 +208,9 @@ class Popularity file_put_contents($this->monthly_file, json_encode($this->monthly_data)); } + /** + * @return array + */ protected function getMonthyChartData() { if (!$this->monthly_data) { @@ -213,6 +227,9 @@ class Popularity return array('labels' => $labels, 'data' => $data); } + /** + * @param string $url + */ protected function updateTotals($url) { if (!$this->totals_data) { @@ -229,6 +246,9 @@ class Popularity file_put_contents($this->totals_file, json_encode($this->totals_data)); } + /** + * @param string $ip + */ protected function updateVisitors($ip) { if (!$this->visitors_data) { @@ -246,6 +266,10 @@ class Popularity file_put_contents($this->visitors_file, json_encode($this->visitors_data)); } + /** + * @param string $path + * @return array + */ protected function getData($path) { if (file_exists($path)) { diff --git a/languages/en.yaml b/languages/en.yaml index 74d95e32..e73e5908 100644 --- a/languages/en.yaml +++ b/languages/en.yaml @@ -474,4 +474,5 @@ PLUGIN_ADMIN: SESSION_HTTPONLY_HELP: "If true, indicates that cookies should be used only over HTTP, and JavaScript modification is not allowed" REVERSE_PROXY: "Reverse Proxy" REVERSE_PROXY_HELP: "Enable this if you are behind a reverse proxy and you are having trouble with URLs containing incorrect ports" - ADD_FOLDER: "Add Folder" \ No newline at end of file + INVALID_FRONTMATTER_COULD_NOT_SAVE: "Invalid frontmatter, could not save" + ADD_FOLDER: "Add Folder" diff --git a/pages/admin/config.md b/pages/admin/config.md index e69de29b..aa5fef18 100644 --- a/pages/admin/config.md +++ b/pages/admin/config.md @@ -0,0 +1,7 @@ +--- +title: Config + +access: + admin.configuration: true + admin.super: true +--- diff --git a/pages/admin/dashboard.md b/pages/admin/dashboard.md index eb1f7e26..83962023 100644 --- a/pages/admin/dashboard.md +++ b/pages/admin/dashboard.md @@ -5,87 +5,3 @@ access: admin.login: true admin.super: true --- - -Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod -tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, -quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo -consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse -cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non -proident, sunt in culpa qui officia deserunt mollit anim id est laborum. - -Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod -tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, -quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo -consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse -cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non -proident, sunt in culpa qui officia deserunt mollit anim id est laborum. - -Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod -tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, -quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo -consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse -cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non -proident, sunt in culpa qui officia deserunt mollit anim id est laborum. - -Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod -tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, -quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo -consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse -cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non -proident, sunt in culpa qui officia deserunt mollit anim id est laborum. - -Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod -tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, -quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo -consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse -cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non -proident, sunt in culpa qui officia deserunt mollit anim id est laborum. - -Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod -tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, -quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo -consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse -cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non -proident, sunt in culpa qui officia deserunt mollit anim id est laborum. - -Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod -tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, -quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo -consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse -cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non -proident, sunt in culpa qui officia deserunt mollit anim id est laborum. - -Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod -tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, -quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo -consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse -cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non -proident, sunt in culpa qui officia deserunt mollit anim id est laborum. - -Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod -tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, -quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo -consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse -cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non -proident, sunt in culpa qui officia deserunt mollit anim id est laborum. - -Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod -tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, -quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo -consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse -cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non -proident, sunt in culpa qui officia deserunt mollit anim id est laborum. - -Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod -tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, -quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo -consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse -cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non -proident, sunt in culpa qui officia deserunt mollit anim id est laborum. - -Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod -tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, -quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo -consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse -cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non -proident, sunt in culpa qui officia deserunt mollit anim id est laborum. diff --git a/themes/grav/js/forms/form.js b/themes/grav/js/forms/form.js index 6d2fd72f..92745879 100644 --- a/themes/grav/js/forms/form.js +++ b/themes/grav/js/forms/form.js @@ -333,6 +333,19 @@ } } + //Prevent issue caused by a IE / Edge bug sending an empty form with just `route` and `task` + var numberOfProperties = 0; + for ( var prop in values ) { + if (values.hasOwnProperty(prop)) { + numberOfProperties++; + } + } + if (numberOfProperties == 2) { + if (values.route && values.task) { + return; + } + } + return form.appendTo('body').submit(); } else { return $.ajax({ method: method, url: action, data: values }); diff --git a/themes/grav/js/mdeditor.js b/themes/grav/js/mdeditor.js index b10ada41..f207ce2f 100644 --- a/themes/grav/js/mdeditor.js +++ b/themes/grav/js/mdeditor.js @@ -149,7 +149,7 @@ filename = filename.replace(/@3x|@2x|@1x/, ''); filename = filename.replace(/\(/g, '%28'); filename = filename.replace(/\)/g, '%29'); - if (filename.match(/\.(jpg|jpeg|png|gif)$/)) { + if (filename.toLowerCase().match(/\.(jpg|jpeg|png|gif)$/)) { editor.doc.replaceSelection('![](' + filename + ')'); } else { editor.doc.replaceSelection('[' + decodeURI(filename) + '](' + filename + ')'); diff --git a/themes/grav/js/pages-all.js b/themes/grav/js/pages-all.js index 895c78c2..6dd0fcd7 100644 --- a/themes/grav/js/pages-all.js +++ b/themes/grav/js/pages-all.js @@ -280,7 +280,7 @@ $(function(){ confirm.open(); }); - $('a[href]:not([href^=#])').on('click', function(e){ + $('a[href]:not([href^="#"])').on('click', function(e){ if (root.currentValues != getState()){ e.preventDefault(); diff --git a/themes/grav/templates/config.html.twig b/themes/grav/templates/config.html.twig index 5e0d439d..ad567767 100644 --- a/themes/grav/templates/config.html.twig +++ b/themes/grav/templates/config.html.twig @@ -43,7 +43,8 @@ {% for configuration in admin.configurations %} - {% if configuration != 'system' and configuration != 'site' and admin.data('config/' ~ configuration).blueprints.fields is not empty %} + {% set current_blueprints = admin.data('config/' ~ configuration).blueprints.toArray() %} + {% if configuration != 'system' and configuration != 'site' and not current_blueprints.form.hidden and current_blueprints.form.fields is not empty %}
  • {% if config_slug == configuration %}{% else %}{% endif %} {{ configuration|tu|capitalize }} diff --git a/themes/grav/templates/forms/fields/order/order.html.twig b/themes/grav/templates/forms/fields/order/order.html.twig index 7675912e..aad5b139 100644 --- a/themes/grav/templates/forms/fields/order/order.html.twig +++ b/themes/grav/templates/forms/fields/order/order.html.twig @@ -25,7 +25,7 @@ {% if siblings|length < 200 %}
      {% for page in siblings %} -
    • {{ page.title() }}
    • +
    • {{ page.title|e }}
    • {% endfor %}
    {% else %} diff --git a/themes/grav/templates/forms/fields/pagemedia/pagemedia.html.twig b/themes/grav/templates/forms/fields/pagemedia/pagemedia.html.twig index cac0e609..47fd5287 100644 --- a/themes/grav/templates/forms/fields/pagemedia/pagemedia.html.twig +++ b/themes/grav/templates/forms/fields/pagemedia/pagemedia.html.twig @@ -57,7 +57,14 @@ addRemoveLinks: false, dictRemoveFileConfirmation: '[placeholder]', acceptedFiles: $('[data-media-types]').data('media-types'), - previewTemplate: "
    ", + previewTemplate: "
    \n
    \n " + + "
    \n " + + "
    \n \n
    \n " + + "
    \n "+ + "
    \n
    \n " + + "
    \n" + + "Delete\n" + + "Insert\n
    ", init: function() { thisDropzone = this; $.get(URI + '/task{{ config.system.param_sep }}listmedia/admin-nonce{{ config.system.param_sep }}' + GravAdmin.config.admin_nonce, function(data) { @@ -73,7 +80,7 @@ thisDropzone.files.push(mockFile); thisDropzone.options.addedfile.call(thisDropzone, mockFile); - if (filename.match(/\.(jpg|jpeg|png|gif)$/)) { + if (filename.toLowerCase().match(/\.(jpg|jpeg|png|gif)$/)) { thisDropzone.options.thumbnail.call(thisDropzone, mockFile, data.url); } }); diff --git a/themes/grav/templates/forms/fields/tabs/tabs.html.twig b/themes/grav/templates/forms/fields/tabs/tabs.html.twig index f28d87c0..9589c623 100644 --- a/themes/grav/templates/forms/fields/tabs/tabs.html.twig +++ b/themes/grav/templates/forms/fields/tabs/tabs.html.twig @@ -9,13 +9,22 @@ {% endif %} {% if field.fields %} - {% for tab in field.fields %}{% endfor %} + {% for tab in field.fields %} + {% if tab.type == 'tab' %} + + + {% endif %} + {% endfor %}
    {% for field in field.fields %} - {% set value = data.value(field.name) %} -
    - {% include ["forms/fields/#{field.type}/#{field.type}.html.twig", 'forms/fields/text/text.html.twig'] %} -
    + {% if field.type == 'tab' %} + {% set value = data.value(field.name) %} +
    + {% include ["forms/fields/#{field.type}/#{field.type}.html.twig", 'forms/fields/text/text.html.twig'] %} +
    + {% endif %} {% endfor %}
    {% endif %} diff --git a/themes/grav/templates/login.html.twig b/themes/grav/templates/login.html.twig index 3bfad306..195857b6 100644 --- a/themes/grav/templates/login.html.twig +++ b/themes/grav/templates/login.html.twig @@ -12,6 +12,7 @@
    {% if notAuthorized %} {{ 'PLUGIN_ADMIN.BACK'|tu }} + {% else %} {% if not authenticated %} {{ 'PLUGIN_ADMIN.LOGIN_BTN_FORGOT'|tu }} diff --git a/themes/grav/templates/pages.html.twig b/themes/grav/templates/pages.html.twig index bc5b13bb..9181c918 100644 --- a/themes/grav/templates/pages.html.twig +++ b/themes/grav/templates/pages.html.twig @@ -89,7 +89,7 @@ 0 ? 'data-toggle="children"' : ''}} data-hint="{{ description|trim(' • ') }}" class="hint--bottom"> - {{ p.title }} + {{ p.title|e }} {% if p.language %} {{p.language}} @@ -203,7 +203,7 @@

    {{ "PLUGIN_ADMIN.ADD_PAGE"|tu }}

    {% elseif mode == 'edit' %}

    - {{ context.exists ? "PLUGIN_ADMIN.EDIT"|tu ~ " #{context.menu}" : "PLUGIN_ADMIN.CREATE"|tu ~ " #{context.menu}" }} + {{ context.exists ? "PLUGIN_ADMIN.EDIT"|tu ~ " #{context.menu|e}" : "PLUGIN_ADMIN.CREATE"|tu ~ " #{context.menu|e}" }}

    {% else %}

    {{ "PLUGIN_ADMIN.MANAGE_PAGES"|tu }}

    diff --git a/themes/grav/templates/partials/base.html.twig b/themes/grav/templates/partials/base.html.twig index 3b7d127d..651f1d09 100644 --- a/themes/grav/templates/partials/base.html.twig +++ b/themes/grav/templates/partials/base.html.twig @@ -11,9 +11,11 @@ {% endif %} {% if header.robots %} + {% else %} + {% endif %} - + {% block stylesheets %} {% do assets.addCss(theme_url~'/css-compiled/nucleus.css') %} diff --git a/themes/grav/templates/partials/dashboard-pages.html.twig b/themes/grav/templates/partials/dashboard-pages.html.twig index 89cd08fe..8838fd8f 100644 --- a/themes/grav/templates/partials/dashboard-pages.html.twig +++ b/themes/grav/templates/partials/dashboard-pages.html.twig @@ -6,7 +6,11 @@

    {{ "PLUGIN_ADMIN.LATEST_PAGE_UPDATES"|tu }}

    {% for latest in admin.latestPages if admin.latestPages %} - + + + {% endfor %}
    {{ latest.title }}{{ latest.route }}{{ latest.modified|nicetime }}
    + {{ latest.title|e }}{{ latest.route }}{{ latest.modified|nicetime }} +
    diff --git a/themes/grav/templates/partials/nav.html.twig b/themes/grav/templates/partials/nav.html.twig index 43065372..d84c1cd3 100644 --- a/themes/grav/templates/partials/nav.html.twig +++ b/themes/grav/templates/partials/nav.html.twig @@ -9,12 +9,12 @@ {#{% if admin.authorize %}#} @@ -34,7 +34,7 @@ {{ "PLUGIN_ADMIN.PAGES"|tu }} - {{ admin.countPages }} + {{ admin.pagesCount }}
  • diff --git a/twig/AdminTwigExtension.php b/twig/AdminTwigExtension.php index 295e8567..94ed0439 100644 --- a/twig/AdminTwigExtension.php +++ b/twig/AdminTwigExtension.php @@ -1,14 +1,23 @@ grav = Grav::instance(); @@ -37,7 +46,7 @@ class AdminTwigExtension extends \Twig_Extension public function tuFilter() { - return $this->grav['admin']->translate(func_get_args(), [$this->grav['user']->authenticated ? $this->lang : 'en']); + return $this->grav['admin']->translate(func_get_args()); } public function toYamlFilter($value, $inline = true)