Merge branch 'develop' into feature/new-folder-modal

This commit is contained in:
Flavio Copes
2016-02-04 21:05:00 +01:00
23 changed files with 354 additions and 244 deletions

View File

@@ -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 # v1.0.6
## 01/07/2016 ## 01/07/2016

View File

@@ -17,6 +17,10 @@ use RocketTheme\Toolbox\Session\Session;
class AdminPlugin extends Plugin class AdminPlugin extends Plugin
{ {
public $features = [
'blueprints' => 1000,
];
/** /**
* @var bool * @var bool
*/ */
@@ -96,10 +100,10 @@ class AdminPlugin extends Plugin
// check for existence of a user account // check for existence of a user account
$account_dir = $file_path = $this->grav['locator']->findResource('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 no users found, go to register
if (!count($user_check) > 0) { if ($user_check == false || count((array)$user_check) == 0) {
if (!$this->isAdminPath()) { if (!$this->isAdminPath()) {
$this->grav->redirect($this->base); $this->grav->redirect($this->base);
} }
@@ -119,12 +123,11 @@ class AdminPlugin extends Plugin
* - 'password1' for password format * - 'password1' for password format
* - 'password2' for equality to password1 * - 'password2' for equality to password1
* *
* @param object $form The form
* @param string $type The field type * @param string $type The field type
* @param string $value The field value * @param string $value The field value
* @param string $extra Any extra value required * @param string $extra Any extra value required
* *
* @return mixed * @return bool
*/ */
protected function validate($type, $value, $extra = '') protected function validate($type, $value, $extra = '')
{ {
@@ -134,22 +137,21 @@ class AdminPlugin extends Plugin
return false; return false;
} }
return true; return true;
break;
case 'password1': case 'password1':
if (!preg_match('/(?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{8,}/', $value)) { if (!preg_match('/(?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{8,}/', $value)) {
return false; return false;
} }
return true; return true;
break;
case 'password2': case 'password2':
if (strcmp($value, $extra)) { if (strcmp($value, $extra)) {
return false; return false;
} }
return true; return true;
break;
} }
return false;
} }
/** /**
@@ -255,7 +257,7 @@ class AdminPlugin extends Plugin
} }
// Replace themes service with admin. // Replace themes service with admin.
$this->grav['themes'] = function ($c) { $this->grav['themes'] = function () {
require_once __DIR__ . '/classes/themes.php'; require_once __DIR__ . '/classes/themes.php';
return new Themes($this->grav); return new Themes($this->grav);
}; };
@@ -328,6 +330,8 @@ class AdminPlugin extends Plugin
// make sure page is not frozen! // make sure page is not frozen!
unset($this->grav['page']); unset($this->grav['page']);
$this->admin->pagesCount();
// Replace page service with admin. // Replace page service with admin.
$this->grav['page'] = function () use ($self) { $this->grav['page'] = function () use ($self) {
$page = new Page; $page = new Page;
@@ -352,6 +356,8 @@ class AdminPlugin extends Plugin
return $page; return $page;
} }
} }
return null;
}; };
if (empty($this->grav['page'])) { if (empty($this->grav['page'])) {
@@ -405,16 +411,12 @@ class AdminPlugin extends Plugin
{ {
$twig = $this->grav['twig']; $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['location'] = $this->template;
$twig->twig_vars['base_url_relative_frontend'] = $twig->twig_vars['base_url_relative'] ?: '/'; $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['admin_route'] = trim($this->config->get('plugins.admin.route'), '/');
$twig->twig_vars['base_url_relative'] = $twig->twig_vars['base_url_relative'] =
$twig->twig_vars['base_url_simple'] . '/' . $twig->twig_vars['admin_route']; $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_url'] = $twig->twig_vars['base_url_relative'];
$twig->twig_vars['base_path'] = GRAV_ROOT; $twig->twig_vars['base_path'] = GRAV_ROOT;
$twig->twig_vars['admin'] = $this->admin; $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'); 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 // Initialize Admin Language if needed
/** @var Language $language */ /** @var Language $language */
$language = $this->grav['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); $this->admin = new Admin($this->grav, $this->base, $this->template, $this->route);
// And store the class into DI container. // And store the class into DI container.
$this->grav['admin'] = $this->admin; $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 // Get theme for admin
$this->theme = $this->config->get('plugins.admin.theme', 'grav'); $this->theme = $this->config->get('plugins.admin.theme', 'grav');

View File

@@ -1,5 +1,5 @@
name: Admin Panel name: Admin Panel
version: 1.0.6 version: 1.0.7
description: Adds an advanced administration panel to manage your site description: Adds an advanced administration panel to manage your site
icon: empire icon: empire
author: author:
@@ -27,12 +27,12 @@ form:
enabled: enabled:
type: hidden type: hidden
label: Plugin status label: PLUGIN_ADMIN.PLUGIN_STATUS
highlight: 1 highlight: 1
default: 0 default: 0
options: options:
1: Enabled 1: PLUGIN_ADMIN.ENABLED
0: Disabled 0: PLUGIN_ADMIN.DISABLED
validate: validate:
type: bool type: bool
@@ -64,8 +64,8 @@ form:
highlight: 1 highlight: 1
default: 1 default: 1
options: options:
1: Enabled 1: PLUGIN_ADMIN.ENABLED
0: Disabled 0: PLUGIN_ADMIN.DISABLED
validate: validate:
type: bool type: bool
help: Use Google custom fonts. Disable this to use Helvetica. Useful when using Cyrillic and other languages with unsupported characters. 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 highlight: 1
default: 1 default: 1
options: options:
1: Enabled 1: PLUGIN_ADMIN.ENABLED
0: Disabled 0: PLUGIN_ADMIN.DISABLED
validate: validate:
type: bool type: bool
help: Show the "Found an issue? Please report it on GitHub." message. help: Show the "Found an issue? Please report it on GitHub." message.
@@ -91,8 +91,8 @@ form:
highlight: 1 highlight: 1
default: 1 default: 1
options: options:
1: Enabled 1: PLUGIN_ADMIN.ENABLED
0: Disabled 0: PLUGIN_ADMIN.DISABLED
validate: validate:
type: bool type: bool
help: Shows an informative message, in the admin panel, when an update is available. help: Shows an informative message, in the admin panel, when an update is available.
@@ -112,8 +112,8 @@ form:
highlight: 1 highlight: 1
default: 1 default: 1
options: options:
1: Enabled 1: PLUGIN_ADMIN.ENABLED
0: Disabled 0: PLUGIN_ADMIN.DISABLED
validate: validate:
type: bool type: bool
help: Ask the user confirmation when deleting a page help: Ask the user confirmation when deleting a page
@@ -129,8 +129,8 @@ form:
highlight: 1 highlight: 1
default: 1 default: 1
options: options:
1: Enabled 1: PLUGIN_ADMIN.ENABLED
0: Disabled 0: PLUGIN_ADMIN.DISABLED
validate: validate:
type: bool type: bool
help: Enable the visitors stats collecting feature help: Enable the visitors stats collecting feature

View File

@@ -15,7 +15,7 @@ use Grav\Common\User\User;
use Grav\Common\Utils; use Grav\Common\Utils;
use RocketTheme\Toolbox\File\File; use RocketTheme\Toolbox\File\File;
use RocketTheme\Toolbox\File\JsonFile; use RocketTheme\Toolbox\File\JsonFile;
use RocketTheme\Toolbox\File\LogFile; use RocketTheme\Toolbox\ResourceLocator\UniformResourceIterator;
use RocketTheme\Toolbox\ResourceLocator\UniformResourceLocator; use RocketTheme\Toolbox\ResourceLocator\UniformResourceLocator;
use RocketTheme\Toolbox\Session\Message; use RocketTheme\Toolbox\Session\Message;
use RocketTheme\Toolbox\Session\Session; use RocketTheme\Toolbox\Session\Session;
@@ -71,15 +71,15 @@ class Admin
public $user; public $user;
/** /**
* @var Lang * @var GPM
*/
protected $lang;
/**
* @var Grav\Common\GPM\GPM
*/ */
protected $gpm; protected $gpm;
/**
* @var int
*/
protected $pages_count;
/** /**
* Constructor. * Constructor.
* *
@@ -184,8 +184,7 @@ class Admin
/** @var Grav $grav */ /** @var Grav $grav */
$grav = $this->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(); $redirect_route = $this->uri->route();
$grav->redirect($redirect_route); $grav->redirect($redirect_route);
} }
@@ -340,9 +339,12 @@ class Admin
$type = preg_replace('|config/|', '', $type); $type = preg_replace('|config/|', '', $type);
$blueprints = $this->blueprints("config/{$type}"); $blueprints = $this->blueprints("config/{$type}");
$config = $this->grav['config']; $config = $this->grav['config'];
$obj = new Data\Data($config->get($type), $blueprints); $obj = new Data\Data($config->get($type, []), $blueprints);
$obj->merge($post); $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); $obj->file($file);
$data[$type] = $obj; $data[$type] = $obj;
} else { } else {
@@ -386,6 +388,8 @@ class Admin
/** /**
* Get all routes. * Get all routes.
* *
* @param bool $unique
*
* @return array * @return array
*/ */
public function routes($unique = false) public function routes($unique = false)
@@ -406,12 +410,13 @@ class Admin
* *
* @return array * @return array
*/ */
public function countPages() public function pagesCount()
{ {
$routable = $this->grav['pages']->all()->routable(); if (!$this->pages_count) {
$modular = $this->grav['pages']->all()->modular(); $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. * Get all plugins.
* *
* @param bool $local
*
* @return array * @return array
*/ */
public function plugins($local = true) public function plugins($local = true)
@@ -458,7 +465,7 @@ class Admin
$gpm = $this->gpm(); $gpm = $this->gpm();
if (!$gpm) { if (!$gpm) {
return; return false;
} }
return $local ? $gpm->getInstalledPlugins() : $gpm->getRepositoryPlugins()->filter(function ( return $local ? $gpm->getInstalledPlugins() : $gpm->getRepositoryPlugins()->filter(function (
@@ -472,6 +479,8 @@ class Admin
/** /**
* Get all themes. * Get all themes.
* *
* @param bool $local
*
* @return array * @return array
*/ */
public function themes($local = true) public function themes($local = true)
@@ -479,7 +488,7 @@ class Admin
$gpm = $this->gpm(); $gpm = $this->gpm();
if (!$gpm) { if (!$gpm) {
return; return false;
} }
return $local ? $gpm->getInstalledThemes() : $gpm->getRepositoryThemes()->filter(function ($package, $slug) use 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 * @param integer $count number of pages to pull back
* *
* @return array * @return array|null
*/ */
public function latestPages($count = 10) public function latestPages($count = 10)
{ {
@@ -506,7 +515,7 @@ class Admin
$latest = array(); $latest = array();
if(is_null($pages->routes())){ if(is_null($pages->routes())){
return; return null;
} }
foreach ($pages->routes() as $url => $path) { foreach ($pages->routes() as $url => $path) {
@@ -698,18 +707,19 @@ class Admin
} }
/** /**
* Return the configuration files found * Return the found configuration blueprints
* *
* @return array * @return array
*/ */
public static function configurations() public static function configurations()
{ {
$configurations = []; $configurations = [];
$path = Grav::instance()['locator']->findResource('user://config');
/** @var \DirectoryIterator $directory */ /** @var UniformResourceIterator $iterator */
foreach (new \DirectoryIterator($path) as $file) { $iterator = Grav::instance()['locator']->getIterator('blueprints://config');
if ($file->isDir() || $file->isDot() || !preg_match('/^[^.].*.yaml$/', $file->getFilename())) {
foreach ($iterator as $file) {
if ($file->isDir() || !preg_match('/^[^.].*.yaml$/', $file->getFilename())) {
continue; continue;
} }
$configurations[] = basename($file->getBasename(), '.yaml'); $configurations[] = basename($file->getBasename(), '.yaml');
@@ -744,6 +754,7 @@ class Admin
$pages = Grav::instance()['pages']; $pages = Grav::instance()['pages'];
$route = '/' . ltrim(Grav::instance()['admin']->route, '/'); $route = '/' . ltrim(Grav::instance()['admin']->route, '/');
/** @var Page $page */
$page = $pages->dispatch($route); $page = $pages->dispatch($route);
$parent_route = null; $parent_route = null;
if ($page) { if ($page) {
@@ -824,14 +835,11 @@ class Admin
/** /**
* Translate a string to the user-defined language * Translate a string to the user-defined language
* *
* @param $string the string to translate * @param array|mixed $args
*
* @return string
*/ */
public function translate($string) public function translate($args)
{
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)
{ {
if (is_array($args)) { if (is_array($args)) {
$lookup = array_shift($args); $lookup = array_shift($args);
@@ -840,6 +848,8 @@ class Admin
$args = []; $args = [];
} }
$languages = [$this->grav['user']->authenticated ? $this->grav['user']->language : 'en'];
if ($lookup) { if ($lookup) {
if (empty($languages) || reset($languages) == null) { if (empty($languages) || reset($languages) == null) {
if ($this->grav['config']->get('system.languages.translations_fallback', true)) { if ($this->grav['config']->get('system.languages.translations_fallback', true)) {
@@ -848,22 +858,19 @@ class Admin
$languages = (array)$this->grav['language']->getDefault(); $languages = (array)$this->grav['language']->getDefault();
} }
} }
} else {
$languages = ['en'];
} }
foreach ((array)$languages as $lang) { foreach ((array)$languages as $lang) {
$translation = $this->grav['language']->getTranslation($lang, $lookup, $array_support); $translation = $this->grav['language']->getTranslation($lang, $lookup);
if (!$translation) { if (!$translation) {
$language = $this->grav['language']->getDefault() ?: 'en'; $language = $this->grav['language']->getDefault() ?: 'en';
$translation = $this->grav['language']->getTranslation($language, $lookup, $array_support); $translation = $this->grav['language']->getTranslation($language, $lookup);
} }
if (!$translation) { if (!$translation) {
$language = 'en'; $language = 'en';
$translation = $this->grav['language']->getTranslation($language, $lookup, $array_support); $translation = $this->grav['language']->getTranslation($language, $lookup);
} }
if ($translation) { if ($translation) {
@@ -878,6 +885,10 @@ class Admin
return $lookup; return $lookup;
} }
/**
* @param string $php_format
* @return string
*/
function dateformat2Kendo($php_format) function dateformat2Kendo($php_format)
{ {
$SYMBOLS_MATCHING = array( $SYMBOLS_MATCHING = array(

View File

@@ -8,7 +8,7 @@ use Grav\Common\GPM\Installer;
use Grav\Common\Grav; use Grav\Common\Grav;
use Grav\Common\Uri; use Grav\Common\Uri;
use Grav\Common\Data; use Grav\Common\Data;
use Grav\Common\Page; use Grav\Common\Page\Page;
use Grav\Common\Page\Pages; use Grav\Common\Page\Pages;
use Grav\Common\Page\Collection; use Grav\Common\Page\Collection;
use Grav\Common\Plugin; use Grav\Common\Plugin;
@@ -16,10 +16,10 @@ use Grav\Common\Theme;
use Grav\Common\User\User; use Grav\Common\User\User;
use Grav\Common\Utils; use Grav\Common\Utils;
use Grav\Common\Backup\ZipBackup; use Grav\Common\Backup\ZipBackup;
use Grav\Common\Markdown\Parsedown; use RocketTheme\Toolbox\Event\Event;
use Grav\Common\Markdown\ParsedownExtra;
use RocketTheme\Toolbox\File\File; use RocketTheme\Toolbox\File\File;
use RocketTheme\Toolbox\File\JsonFile; use RocketTheme\Toolbox\File\JsonFile;
use Symfony\Component\Yaml\Exception\ParseException;
use Symfony\Component\Yaml\Yaml; use Symfony\Component\Yaml\Yaml;
class AdminController class AdminController
@@ -380,7 +380,7 @@ class AdminController
protected function taskClearCache() protected function taskClearCache()
{ {
if (!$this->authorizeTask('clear cache', ['admin.cache', 'admin.super'])) { if (!$this->authorizeTask('clear cache', ['admin.cache', 'admin.super'])) {
return; return false;
} }
// get optional cleartype param // get optional cleartype param
@@ -411,7 +411,7 @@ class AdminController
{ {
$param_sep = $this->grav['config']->get('system.param_sep', ':'); $param_sep = $this->grav['config']->get('system.param_sep', ':');
if (!$this->authorizeTask('backup', ['admin.maintenance', 'admin.super'])) { if (!$this->authorizeTask('backup', ['admin.maintenance', 'admin.super'])) {
return; return false;
} }
$download = $this->grav['uri']->param('download'); $download = $this->grav['uri']->param('download');
@@ -562,7 +562,7 @@ class AdminController
protected function taskListmedia() protected function taskListmedia()
{ {
if (!$this->authorizeTask('list media', ['admin.pages', 'admin.super'])) { if (!$this->authorizeTask('list media', ['admin.pages', 'admin.super'])) {
return; return false;
} }
$page = $this->admin->page(true); $page = $this->admin->page(true);
@@ -583,11 +583,13 @@ class AdminController
/** /**
* Handles adding a media file to a page * Handles adding a media file to a page
*
* @return bool True if the action was performed.
*/ */
protected function taskAddmedia() protected function taskAddmedia()
{ {
if (!$this->authorizeTask('add media', ['admin.pages', 'admin.super'])) { if (!$this->authorizeTask('add media', ['admin.pages', 'admin.super'])) {
return; return false;
} }
$page = $this->admin->page(true); $page = $this->admin->page(true);
@@ -597,7 +599,7 @@ class AdminController
if (!isset($_FILES['file']['error']) || is_array($_FILES['file']['error'])) { if (!isset($_FILES['file']['error']) || is_array($_FILES['file']['error'])) {
$this->admin->json_response = ['status' => 'error', 'message' => $this->admin->translate('PLUGIN_ADMIN.INVALID_PARAMETERS')]; $this->admin->json_response = ['status' => 'error', 'message' => $this->admin->translate('PLUGIN_ADMIN.INVALID_PARAMETERS')];
return; return false;
} }
// Check $_FILES['file']['error'] value. // Check $_FILES['file']['error'] value.
@@ -606,44 +608,47 @@ class AdminController
break; break;
case UPLOAD_ERR_NO_FILE: case UPLOAD_ERR_NO_FILE:
$this->admin->json_response = ['status' => 'error', 'message' => $this->admin->translate('PLUGIN_ADMIN.NO_FILES_SENT')]; $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_INI_SIZE:
case UPLOAD_ERR_FORM_SIZE: case UPLOAD_ERR_FORM_SIZE:
$this->admin->json_response = ['status' => 'error', 'message' => $this->admin->translate('PLUGIN_ADMIN.EXCEEDED_FILESIZE_LIMIT')]; $this->admin->json_response = ['status' => 'error', 'message' => $this->admin->translate('PLUGIN_ADMIN.EXCEEDED_FILESIZE_LIMIT')];
return; return false;
default: default:
$this->admin->json_response = ['status' => 'error', 'message' => $this->admin->translate('PLUGIN_ADMIN.UNKNOWN_ERRORS')]; $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); $grav_limit = $config->get('system.media.upload_limit', 0);
// You should also check filesize here. // You should also check filesize here.
if ($grav_limit > 0 && $_FILES['file']['size'] > $grav_limit) { 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')]; $this->admin->json_response = ['status' => 'error', 'message' => $this->admin->translate('PLUGIN_ADMIN.EXCEEDED_GRAV_FILESIZE_LIMIT')];
return; return false;
} }
// Check extension // Check extension
$fileParts = pathinfo($_FILES['file']['name']); $fileParts = pathinfo($_FILES['file']['name']);
$fileExt = strtolower($fileParts['extension']);
// If not a supported type, return $fileExt = '';
if (!$config->get("media.{$fileExt}")) { if (isset($fileParts['extension'])) {
$this->admin->json_response = ['status' => 'error', 'message' => $this->admin->translate('PLUGIN_ADMIN.UNSUPPORTED_FILE_TYPE') . ': '.$fileExt]; $fileExt = strtolower($fileParts['extension']);
return;
} }
// 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 // Upload it
if (!move_uploaded_file($_FILES['file']['tmp_name'], sprintf('%s/%s', $page->path(), $_FILES['file']['name']))) { 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')]; $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')]; $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() protected function taskDelmedia()
{ {
if (!$this->authorizeTask('delete media', ['admin.pages', 'admin.super'])) { if (!$this->authorizeTask('delete media', ['admin.pages', 'admin.super'])) {
return; return false;
} }
$page = $this->admin->page(true); $page = $this->admin->page(true);
@@ -666,7 +671,7 @@ class AdminController
$filename = !empty($this->post['filename']) ? $this->post['filename'] : null; $filename = !empty($this->post['filename']) ? $this->post['filename'] : null;
if ($filename) { if ($filename) {
$targetPath = $page->path().'/'.$filename; $targetPath = $page->path() . '/' . $filename;
if (file_exists($targetPath)) { if (file_exists($targetPath)) {
if (unlink($targetPath)) { if (unlink($targetPath)) {
@@ -677,18 +682,20 @@ class AdminController
} else { } else {
//Try with responsive images @1x, @2x, @3x //Try with responsive images @1x, @2x, @3x
$ext = pathinfo($targetPath, PATHINFO_EXTENSION); $ext = pathinfo($targetPath, PATHINFO_EXTENSION);
$filename = $page->path() . '/'. basename($targetPath, ".$ext"); $fullPathFilename = $page->path() . '/'. basename($targetPath, ".$ext");
$responsiveTargetPath = $filename . '@1x.' . $ext; $responsiveTargetPath = $fullPathFilename . '@1x.' . $ext;
$deletedResponsiveImage = false; $deletedResponsiveImage = false;
if (file_exists($responsiveTargetPath) && unlink($responsiveTargetPath)) { if (file_exists($responsiveTargetPath) && unlink($responsiveTargetPath)) {
$deletedResponsiveImage = true; $deletedResponsiveImage = true;
} }
$responsiveTargetPath = $filename . '@2x.' . $ext; $responsiveTargetPath = $fullPathFilename . '@2x.' . $ext;
if (file_exists($responsiveTargetPath) && unlink($responsiveTargetPath)) { if (file_exists($responsiveTargetPath) && unlink($responsiveTargetPath)) {
$deletedResponsiveImage = true; $deletedResponsiveImage = true;
} }
$responsiveTargetPath = $filename . '@3x.' . $ext;
$responsiveTargetPath = $fullPathFilename . '@3x.' . $ext;
if (file_exists($responsiveTargetPath) && unlink($responsiveTargetPath)) { if (file_exists($responsiveTargetPath) && unlink($responsiveTargetPath)) {
$deletedResponsiveImage = true; $deletedResponsiveImage = true;
} }
@@ -709,6 +716,8 @@ class AdminController
/** /**
* Process the page Markdown * Process the page Markdown
*
* @return bool True if the action was performed.
*/ */
protected function taskProcessMarkdown() protected function taskProcessMarkdown()
{ {
@@ -734,11 +743,13 @@ class AdminController
$html = $page->content(); $html = $page->content();
$this->admin->json_response = ['status' => 'success', 'message' => $html]; $this->admin->json_response = ['status' => 'success', 'message' => $html];
return true;
} catch (\Exception $e) { } catch (\Exception $e) {
$this->admin->json_response = ['status' => 'error', 'message' => $e->getMessage()]; $this->admin->json_response = ['status' => 'error', 'message' => $e->getMessage()];
return false; return false;
} }
return true;
} }
/** /**
@@ -749,7 +760,7 @@ class AdminController
public function taskEnable() public function taskEnable()
{ {
if (!$this->authorizeTask('enable plugin', ['admin.plugins', 'admin.super'])) { if (!$this->authorizeTask('enable plugin', ['admin.plugins', 'admin.super'])) {
return; return false;
} }
if ($this->view != 'plugins') { if ($this->view != 'plugins') {
@@ -775,7 +786,7 @@ class AdminController
public function taskDisable() public function taskDisable()
{ {
if (!$this->authorizeTask('disable plugin', ['admin.plugins', 'admin.super'])) { if (!$this->authorizeTask('disable plugin', ['admin.plugins', 'admin.super'])) {
return; return false;
} }
if ($this->view != 'plugins') { if ($this->view != 'plugins') {
@@ -801,7 +812,7 @@ class AdminController
public function taskActivate() public function taskActivate()
{ {
if (!$this->authorizeTask('activate theme', ['admin.themes', 'admin.super'])) { if (!$this->authorizeTask('activate theme', ['admin.themes', 'admin.super'])) {
return; return false;
} }
if ($this->view != 'themes') { if ($this->view != 'themes') {
@@ -840,7 +851,7 @@ class AdminController
{ {
$type = $this->view === 'plugins' ? 'plugins' : 'themes'; $type = $this->view === 'plugins' ? 'plugins' : 'themes';
if (!$this->authorizeTask('install ' . $type, ['admin.' . $type, 'admin.super'])) { if (!$this->authorizeTask('install ' . $type, ['admin.' . $type, 'admin.super'])) {
return; return false;
} }
require_once __DIR__ . '/gpm.php'; require_once __DIR__ . '/gpm.php';
@@ -915,7 +926,7 @@ class AdminController
foreach ($permissions as $type => $p) { foreach ($permissions as $type => $p) {
if (!$this->authorizeTask('update ' . $type , $p)) { if (!$this->authorizeTask('update ' . $type , $p)) {
return; return false;
} }
} }
@@ -951,7 +962,7 @@ class AdminController
{ {
$type = $this->view === 'plugins' ? 'plugins' : 'themes'; $type = $this->view === 'plugins' ? 'plugins' : 'themes';
if (!$this->authorizeTask('uninstall ' . $type, ['admin.' . $type, 'admin.super'])) { if (!$this->authorizeTask('uninstall ' . $type, ['admin.' . $type, 'admin.super'])) {
return; return false;
} }
require_once __DIR__ . '/gpm.php'; require_once __DIR__ . '/gpm.php';
@@ -971,6 +982,11 @@ class AdminController
return true; return true;
} }
/**
* @param string $key
* @param string $file
* @return bool
*/
private function cleanFilesData($key, $file) private function cleanFilesData($key, $file)
{ {
$config = $this->grav['config']; $config = $this->grav['config'];
@@ -1031,6 +1047,11 @@ class AdminController
return $cleanFiles[$key]; return $cleanFiles[$key];
} }
/**
* @param string $needle
* @param array|string $haystack
* @return bool
*/
private function match_in_array($needle, $haystack) private function match_in_array($needle, $haystack)
{ {
foreach ((array)$haystack as $item) { foreach ((array)$haystack as $item) {
@@ -1042,6 +1063,10 @@ class AdminController
return false; return false;
} }
/**
* @param mixed $obj
* @return mixed
*/
private function processFiles($obj) private function processFiles($obj)
{ {
foreach ((array)$_FILES as $key => $file) { foreach ((array)$_FILES as $key => $file) {
@@ -1108,6 +1133,29 @@ class AdminController
return true; 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. * Handles form and saves the input data if its valid.
* *
@@ -1116,21 +1164,27 @@ class AdminController
public function taskSave() public function taskSave()
{ {
if (!$this->authorizeTask('save', $this->dataPermissions())) { if (!$this->authorizeTask('save', $this->dataPermissions())) {
return; return false;
} }
$data = $this->post; $data = $this->post;
$config = $this->grav['config'];
// Special handler for pages data. // Special handler for pages data.
if ($this->view == 'pages') { if ($this->view == 'pages') {
/** @var Page\Pages $pages */ /** @var Pages $pages */
$pages = $this->grav['pages']; $pages = $this->grav['pages'];
$config = $this->grav['config'];
// Find new parent page in order to build the path. // Find new parent page in order to build the path.
$route = !isset($data['route']) ? dirname($this->admin->route) : $data['route']; $route = !isset($data['route']) ? dirname($this->admin->route) : $data['route'];
$obj = $this->admin->page(true); $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 //Handle system.home.hide_in_urls
$hide_home_route = $config->get('system.home.hide_in_urls', false); $hide_home_route = $config->get('system.home.hide_in_urls', false);
if ($hide_home_route) { if ($hide_home_route) {
@@ -1187,6 +1241,9 @@ class AdminController
} }
if ($obj) { if ($obj) {
// Event to manipulate data before saving the object
$this->grav->fireEvent('onAdminSave', new Event(['object' => &$obj]));
$obj->save(true); $obj->save(true);
$this->admin->setMessage($this->admin->translate('PLUGIN_ADMIN.SUCCESSFULLY_SAVED'), 'info'); $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 // 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')) { if (method_exists($obj, 'unsetRouteSlug')) {
$obj->unsetRouteSlug(); $obj->unsetRouteSlug();
} }
@@ -1287,7 +1344,7 @@ class AdminController
protected function taskCopy() protected function taskCopy()
{ {
if (!$this->authorizeTask('copy page', ['admin.pages', 'admin.super'])) { if (!$this->authorizeTask('copy page', ['admin.pages', 'admin.super'])) {
return; return false;
} }
// Only applies to pages. // Only applies to pages.
@@ -1296,7 +1353,7 @@ class AdminController
} }
try { try {
/** @var Page\Pages $pages */ /** @var Pages $pages */
$pages = $this->grav['pages']; $pages = $this->grav['pages'];
$data = $this->post; $data = $this->post;
@@ -1345,7 +1402,7 @@ class AdminController
protected function taskReorder() protected function taskReorder()
{ {
if (!$this->authorizeTask('reorder pages', ['admin.pages', 'admin.super'])) { if (!$this->authorizeTask('reorder pages', ['admin.pages', 'admin.super'])) {
return; return false;
} }
// Only applies to pages. // Only applies to pages.
@@ -1366,7 +1423,7 @@ class AdminController
protected function taskDelete() protected function taskDelete()
{ {
if (!$this->authorizeTask('delete page', ['admin.pages', 'admin.super'])) { if (!$this->authorizeTask('delete page', ['admin.pages', 'admin.super'])) {
return; return false;
} }
// Only applies to pages. // Only applies to pages.
@@ -1443,7 +1500,7 @@ class AdminController
protected function taskSaveas() protected function taskSaveas()
{ {
if (!$this->authorizeTask('save', $this->dataPermissions())) { if (!$this->authorizeTask('save', $this->dataPermissions())) {
return; return false;
} }
$data = $this->post; $data = $this->post;
@@ -1480,7 +1537,7 @@ class AdminController
$aFile = File::instance($path); $aFile = File::instance($path);
$aFile->save(); $aFile->save();
$aPage = new Page\Page(); $aPage = new Page();
$aPage->init(new \SplFileInfo($path), $language .'.md'); $aPage->init(new \SplFileInfo($path), $language .'.md');
$aPage->header($obj->header()); $aPage->header($obj->header());
$aPage->rawMarkdown($obj->rawMarkdown()); $aPage->rawMarkdown($obj->rawMarkdown());

View File

@@ -1,7 +1,7 @@
<?php <?php
namespace Grav\Plugin\Admin; namespace Grav\Plugin\Admin;
use Grav\Common\GravTrait; use Grav;
use Grav\Common\GPM\GPM as GravGPM; use Grav\Common\GPM\GPM as GravGPM;
use Grav\Common\GPM\Installer; use Grav\Common\GPM\Installer;
use Grav\Common\GPM\Response; use Grav\Common\GPM\Response;
@@ -11,8 +11,6 @@ use Grav\Common\GPM\Common\Package;
class Gpm class Gpm
{ {
use GravTrait;
// Probably should move this to Grav DI container? // Probably should move this to Grav DI container?
protected static $GPM; protected static $GPM;
public static function GPM() public static function GPM()
@@ -36,7 +34,12 @@ class Gpm
'theme' => false 'theme' => 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); $options = array_merge(self::$options, $options);
@@ -93,13 +96,24 @@ class Gpm
return true; 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; $options['overwrite'] = true;
return static::install($packages, $options); 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); $options = array_merge(self::$options, $options);
@@ -124,7 +138,7 @@ class Gpm
foreach ($packages as $package) { 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 // Check destination
Installer::isValidDestination($location); Installer::isValidDestination($location);
@@ -144,11 +158,15 @@ class Gpm
return true; return true;
} }
private static function download($package) /**
* @param Package $package
* @return string
*/
private static function download(Package $package)
{ {
$contents = Response::get($package->zipball_url, []); $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(); $cache_dir = $cache_dir . DS . 'tmp/Grav-' . uniqid();
Folder::mkdir($cache_dir); Folder::mkdir($cache_dir);
@@ -159,7 +177,12 @@ class Gpm
return $cache_dir . DS . $filename . '.zip'; 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'], []); $output = Response::get($package['download'], []);
Folder::mkdir($tmp); Folder::mkdir($tmp);
@@ -167,6 +190,9 @@ class Gpm
return $tmp . DS . $package['name']; return $tmp . DS . $package['name'];
} }
/**
* @return bool
*/
public static function selfupgrade() public static function selfupgrade()
{ {
$upgrader = new Upgrader(); $upgrader = new Upgrader();

View File

@@ -3,16 +3,15 @@ namespace Grav\Plugin;
use Grav\Common\Config\Config; use Grav\Common\Config\Config;
use Grav\Common\Grav; use Grav\Common\Grav;
use Grav\Common\Plugins;
use Grav\Common\Themes;
use Grav\Common\Page\Page; use Grav\Common\Page\Page;
use Grav\Common\Data; use Grav\Common\Data;
use Grav\Common\GravTrait;
/**
* Class Popularity
* @package Grav\Plugin
*/
class Popularity class Popularity
{ {
use GravTrait;
/** @var Config */ /** @var Config */
protected $config; protected $config;
protected $data_path; protected $data_path;
@@ -36,9 +35,9 @@ class Popularity
public function __construct() 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->daily_file = $this->data_path.'/'.self::DAILY_FILE;
$this->monthly_file = $this->data_path.'/'.self::MONTHLY_FILE; $this->monthly_file = $this->data_path.'/'.self::MONTHLY_FILE;
$this->totals_file = $this->data_path.'/'.self::TOTALS_FILE; $this->totals_file = $this->data_path.'/'.self::TOTALS_FILE;
@@ -49,13 +48,13 @@ class Popularity
public function trackHit() public function trackHit()
{ {
// Don't track bot or crawler requests // Don't track bot or crawler requests
if (!self::getGrav()['browser']->isHuman()) { if (!Grav::instance()['browser']->isHuman()) {
return; return;
} }
/** @var Page $page */ /** @var Page $page */
$page = self::getGrav()['page']; $page = Grav::instance()['page'];
$relative_url = str_replace(self::getGrav()['base_url_relative'], '', $page->url()); $relative_url = str_replace(Grav::instance()['base_url_relative'], '', $page->url());
// Don't track error pages or pages that have no route // Don't track error pages or pages that have no route
if ($page->template() == 'error' || !$page->route()) { if ($page->template() == 'error' || !$page->route()) {
@@ -79,7 +78,7 @@ class Popularity
$this->updateDaily(); $this->updateDaily();
$this->updateMonthly(); $this->updateMonthly();
$this->updateTotals($page->route()); $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)); file_put_contents($this->daily_file, json_encode($this->daily_data));
} }
/**
* @return array
*/
public function getDailyChartData() public function getDailyChartData()
{ {
if (!$this->daily_data) { if (!$this->daily_data) {
@@ -123,13 +125,16 @@ class Popularity
$data = array(); $data = array();
foreach ($chart_data as $date => $count) { 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; $data[] = $count;
} }
return array('labels' => json_encode($labels), 'data' => json_encode($data)); return array('labels' => json_encode($labels), 'data' => json_encode($data));
} }
/**
* @return int
*/
public function getDailyTotal() public function getDailyTotal()
{ {
if (!$this->daily_data) { if (!$this->daily_data) {
@@ -143,6 +148,9 @@ class Popularity
} }
} }
/**
* @return int
*/
public function getWeeklyTotal() public function getWeeklyTotal()
{ {
if (!$this->daily_data) { if (!$this->daily_data) {
@@ -160,6 +168,9 @@ class Popularity
return $total; return $total;
} }
/**
* @return int
*/
public function getMonthlyTotal() public function getMonthlyTotal()
{ {
if (!$this->monthly_data) { if (!$this->monthly_data) {
@@ -197,6 +208,9 @@ class Popularity
file_put_contents($this->monthly_file, json_encode($this->monthly_data)); file_put_contents($this->monthly_file, json_encode($this->monthly_data));
} }
/**
* @return array
*/
protected function getMonthyChartData() protected function getMonthyChartData()
{ {
if (!$this->monthly_data) { if (!$this->monthly_data) {
@@ -213,6 +227,9 @@ class Popularity
return array('labels' => $labels, 'data' => $data); return array('labels' => $labels, 'data' => $data);
} }
/**
* @param string $url
*/
protected function updateTotals($url) protected function updateTotals($url)
{ {
if (!$this->totals_data) { if (!$this->totals_data) {
@@ -229,6 +246,9 @@ class Popularity
file_put_contents($this->totals_file, json_encode($this->totals_data)); file_put_contents($this->totals_file, json_encode($this->totals_data));
} }
/**
* @param string $ip
*/
protected function updateVisitors($ip) protected function updateVisitors($ip)
{ {
if (!$this->visitors_data) { if (!$this->visitors_data) {
@@ -246,6 +266,10 @@ class Popularity
file_put_contents($this->visitors_file, json_encode($this->visitors_data)); file_put_contents($this->visitors_file, json_encode($this->visitors_data));
} }
/**
* @param string $path
* @return array
*/
protected function getData($path) protected function getData($path)
{ {
if (file_exists($path)) { if (file_exists($path)) {

View File

@@ -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" 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: "Reverse Proxy"
REVERSE_PROXY_HELP: "Enable this if you are behind a reverse proxy and you are having trouble with URLs containing incorrect ports" 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" INVALID_FRONTMATTER_COULD_NOT_SAVE: "Invalid frontmatter, could not save"
ADD_FOLDER: "Add Folder"

View File

@@ -0,0 +1,7 @@
---
title: Config
access:
admin.configuration: true
admin.super: true
---

View File

@@ -5,87 +5,3 @@ access:
admin.login: true admin.login: true
admin.super: 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.

View File

@@ -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(); return form.appendTo('body').submit();
} else { } else {
return $.ajax({ method: method, url: action, data: values }); return $.ajax({ method: method, url: action, data: values });

View File

@@ -149,7 +149,7 @@
filename = filename.replace(/@3x|@2x|@1x/, ''); filename = filename.replace(/@3x|@2x|@1x/, '');
filename = filename.replace(/\(/g, '%28'); filename = filename.replace(/\(/g, '%28');
filename = filename.replace(/\)/g, '%29'); filename = filename.replace(/\)/g, '%29');
if (filename.match(/\.(jpg|jpeg|png|gif)$/)) { if (filename.toLowerCase().match(/\.(jpg|jpeg|png|gif)$/)) {
editor.doc.replaceSelection('![](' + filename + ')'); editor.doc.replaceSelection('![](' + filename + ')');
} else { } else {
editor.doc.replaceSelection('[' + decodeURI(filename) + '](' + filename + ')'); editor.doc.replaceSelection('[' + decodeURI(filename) + '](' + filename + ')');

View File

@@ -280,7 +280,7 @@ $(function(){
confirm.open(); confirm.open();
}); });
$('a[href]:not([href^=#])').on('click', function(e){ $('a[href]:not([href^="#"])').on('click', function(e){
if (root.currentValues != getState()){ if (root.currentValues != getState()){
e.preventDefault(); e.preventDefault();

View File

@@ -43,7 +43,8 @@
</li> </li>
{% for configuration in admin.configurations %} {% 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 %}
<li {% if config_slug == configuration %}class="active"{% endif %}> <li {% if config_slug == configuration %}class="active"{% endif %}>
{% if config_slug == configuration %}<span>{% else %}<a href="{{ base_url_relative }}/config/{{configuration}}">{% endif %} {% if config_slug == configuration %}<span>{% else %}<a href="{{ base_url_relative }}/config/{{configuration}}">{% endif %}
{{ configuration|tu|capitalize }} {{ configuration|tu|capitalize }}

View File

@@ -25,7 +25,7 @@
{% if siblings|length < 200 %} {% if siblings|length < 200 %}
<ul id="ordering" class="{{ field.classes }}"> <ul id="ordering" class="{{ field.classes }}">
{% for page in siblings %} {% for page in siblings %}
<li class="{% if page.order == value %}drag-handle{% else %}ignore{% endif %}" data-id="{{ page.slug }}">{{ page.title() }}</li> <li class="{% if page.order == value %}drag-handle{% else %}ignore{% endif %}" data-id="{{ page.slug }}">{{ page.title|e }}</li>
{% endfor %} {% endfor %}
</ul> </ul>
{% else %} {% else %}

View File

@@ -57,7 +57,14 @@
addRemoveLinks: false, addRemoveLinks: false,
dictRemoveFileConfirmation: '[placeholder]', dictRemoveFileConfirmation: '[placeholder]',
acceptedFiles: $('[data-media-types]').data('media-types'), acceptedFiles: $('[data-media-types]').data('media-types'),
previewTemplate: "<div class=\"dz-preview dz-file-preview\">\n <div class=\"dz-details\">\n <div class=\"dz-filename\"><span data-dz-name></span></div>\n <div class=\"dz-size\" data-dz-size></div>\n <img data-dz-thumbnail />\n </div>\n <div class=\"dz-progress\"><span class=\"dz-upload\" data-dz-uploadprogress></span></div>\n <div class=\"dz-success-mark\"><span>✔</span></div>\n <div class=\"dz-error-mark\"><span>✘</span></div>\n <div class=\"dz-error-message\"><span data-dz-errormessage></span></div>\n<a class=\"dz-remove\" href=\"javascript:undefined;\" data-dz-remove>Delete</a>\n<a class=\"dz-insert\" href=\"javascript:undefined;\" data-dz-insert>Insert</a>\n</div>", previewTemplate: "<div class=\"dz-preview dz-file-preview\">\n <div class=\"dz-details\">\n " +
"<div class=\"dz-filename\"><span data-dz-name></span></div>\n " +
"<div class=\"dz-size\" data-dz-size></div>\n <img data-dz-thumbnail />\n </div>\n " +
"<div class=\"dz-progress\"><span class=\"dz-upload\" data-dz-uploadprogress></span></div>\n "+
"<div class=\"dz-success-mark\"><span>✔</span></div>\n <div class=\"dz-error-mark\"><span>✘</span></div>\n " +
"<div class=\"dz-error-message\"><span data-dz-errormessage></span></div>\n" +
"<a class=\"dz-remove\" href=\"javascript:undefined;\" data-dz-remove>Delete</a>\n" +
"<a class=\"dz-insert\" href=\"javascript:undefined;\" data-dz-insert>Insert</a>\n</div>",
init: function() { init: function() {
thisDropzone = this; thisDropzone = this;
$.get(URI + '/task{{ config.system.param_sep }}listmedia/admin-nonce{{ config.system.param_sep }}' + GravAdmin.config.admin_nonce, function(data) { $.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.files.push(mockFile);
thisDropzone.options.addedfile.call(thisDropzone, 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); thisDropzone.options.thumbnail.call(thisDropzone, mockFile, data.url);
} }
}); });

View File

@@ -9,13 +9,22 @@
{% endif %} {% endif %}
{% if field.fields %} {% if field.fields %}
{% for tab in field.fields %}<input type="radio" name="tab" id="tab{{ loop.index }}" class="tab-head" {{ active == loop.index ? 'checked="checked"' : '' }}/><label for="tab{{ loop.index }}">{% if grav.twig.twig.filters['tu'] is defined %}{{ tab.title|tu }}{% else %}{{ tab.title|t }}{% endif %}</label>{% endfor %} {% for tab in field.fields %}
{% if tab.type == 'tab' %}
<input type="radio" name="tab" id="tab{{ loop.index }}" class="tab-head" {{ active == loop.index ? 'checked="checked"' : '' }}/>
<label for="tab{{ loop.index }}">
{% if grav.twig.twig.filters['tu'] is defined %}{{ tab.title|tu }}{% else %}{{ tab.title|t }}{% endif %}
</label>
{% endif %}
{% endfor %}
<div class="tab-body-wrapper"> <div class="tab-body-wrapper">
{% for field in field.fields %} {% for field in field.fields %}
{% set value = data.value(field.name) %} {% if field.type == 'tab' %}
<div id="tab-body-{{ loop.index }}" class="tab-body"> {% set value = data.value(field.name) %}
{% include ["forms/fields/#{field.type}/#{field.type}.html.twig", 'forms/fields/text/text.html.twig'] %} <div id="tab-body-{{ loop.index }}" class="tab-body">
</div> {% include ["forms/fields/#{field.type}/#{field.type}.html.twig", 'forms/fields/text/text.html.twig'] %}
</div>
{% endif %}
{% endfor %} {% endfor %}
</div> </div>
{% endif %} {% endif %}

View File

@@ -12,6 +12,7 @@
<div class="form-actions secondary-accent"> <div class="form-actions secondary-accent">
{% if notAuthorized %} {% if notAuthorized %}
<a class="button secondary" onclick="window.history.back()"><i class="fa fa-reply"></i> {{ 'PLUGIN_ADMIN.BACK'|tu }}</a> <a class="button secondary" onclick="window.history.back()"><i class="fa fa-reply"></i> {{ 'PLUGIN_ADMIN.BACK'|tu }}</a>
<button type="submit" class="button primary" name="task" value="logout"><i class="fa fa-sign-in"></i> {{ 'PLUGIN_ADMIN.LOGOUT'|tu }}</button>
{% else %} {% else %}
{% if not authenticated %} {% if not authenticated %}
<a class="button secondary" href="{{ base_url_relative }}/forgot"><i class="fa fa-exclamation-circle"></i> {{ 'PLUGIN_ADMIN.LOGIN_BTN_FORGOT'|tu }}</a> <a class="button secondary" href="{{ base_url_relative }}/forgot"><i class="fa fa-exclamation-circle"></i> {{ 'PLUGIN_ADMIN.LOGIN_BTN_FORGOT'|tu }}</a>

View File

@@ -89,7 +89,7 @@
<span {{ p.children(0).count > 0 ? 'data-toggle="children"' : ''}} data-hint="{{ description|trim(' &bull; ') }}" class="hint--bottom"> <span {{ p.children(0).count > 0 ? 'data-toggle="children"' : ''}} data-hint="{{ description|trim(' &bull; ') }}" class="hint--bottom">
<i class="page-icon fa fa-fw fa-circle-o {{ p.children(0).count > 0 ? 'children-closed' : ''}} {{ p.modular ? 'modular' : (not p.routable ? 'not-routable' : (not p.visible ? 'not-visible' : (not p.page ? 'folder' : ''))) }}"></i> <i class="page-icon fa fa-fw fa-circle-o {{ p.children(0).count > 0 ? 'children-closed' : ''}} {{ p.modular ? 'modular' : (not p.routable ? 'not-routable' : (not p.visible ? 'not-visible' : (not p.page ? 'folder' : ''))) }}"></i>
</span> </span>
<a href="{{ page_url }}" class="page-edit">{{ p.title }}</a> <a href="{{ page_url }}" class="page-edit">{{ p.title|e }}</a>
{% if p.language %} {% if p.language %}
<span class="badge lang {% if p.language == admin_lang %}info{% endif %}">{{p.language}}</span> <span class="badge lang {% if p.language == admin_lang %}info{% endif %}">{{p.language}}</span>
@@ -203,7 +203,7 @@
<h1><i class="fa fa-fw fa-file-text-o"></i> {{ "PLUGIN_ADMIN.ADD_PAGE"|tu }}</h1> <h1><i class="fa fa-fw fa-file-text-o"></i> {{ "PLUGIN_ADMIN.ADD_PAGE"|tu }}</h1>
{% elseif mode == 'edit' %} {% elseif mode == 'edit' %}
<h1><i class="fa fa-fw fa-file-text-o"></i> <h1><i class="fa fa-fw fa-file-text-o"></i>
{{ context.exists ? "PLUGIN_ADMIN.EDIT"|tu ~ " <i>#{context.menu}</i>" : "PLUGIN_ADMIN.CREATE"|tu ~ " <i>#{context.menu}</i>" }} {{ context.exists ? "PLUGIN_ADMIN.EDIT"|tu ~ " <i>#{context.menu|e}</i>" : "PLUGIN_ADMIN.CREATE"|tu ~ " <i>#{context.menu|e}</i>" }}
</h1> </h1>
{% else %} {% else %}
<h1><i class="fa fa-fw fa-file-text-o"></i> {{ "PLUGIN_ADMIN.MANAGE_PAGES"|tu }}</h1> <h1><i class="fa fa-fw fa-file-text-o"></i> {{ "PLUGIN_ADMIN.MANAGE_PAGES"|tu }}</h1>

View File

@@ -11,9 +11,11 @@
{% endif %} {% endif %}
{% if header.robots %} {% if header.robots %}
<meta name="robots" content="{{ header.robots }}"> <meta name="robots" content="{{ header.robots }}">
{% else %}
<meta name="robots" content="noindex, nofollow">
{% endif %} {% endif %}
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="icon" type="image/png" href="{{ base_url_simple }}{{ theme_url }}/images/favicon.png"> <link rel="icon" type="image/png" href="{{ base_url_simple }}/{{ theme_url }}/images/favicon.png">
{% block stylesheets %} {% block stylesheets %}
{% do assets.addCss(theme_url~'/css-compiled/nucleus.css') %} {% do assets.addCss(theme_url~'/css-compiled/nucleus.css') %}

View File

@@ -6,7 +6,11 @@
<h1>{{ "PLUGIN_ADMIN.LATEST_PAGE_UPDATES"|tu }}</h1> <h1>{{ "PLUGIN_ADMIN.LATEST_PAGE_UPDATES"|tu }}</h1>
<table> <table>
{% for latest in admin.latestPages if admin.latestPages %} {% for latest in admin.latestPages if admin.latestPages %}
<tr><td class="double page-title"><a href="{{ base_url }}/pages/{{ latest.route|trim('/') }}"><i class="fa fa-fw fa-file-o"></i> {{ latest.title }}</a></td><td class="double page-route">{{ latest.route }}</td><td><b class="last-modified">{{ latest.modified|nicetime }}</b></td></tr> <tr>
<td class="double page-title">
<a href="{{ base_url }}/pages/{{ latest.route|trim('/') }}"><i class="fa fa-fw fa-file-o"></i> {{ latest.title|e }}</a></td><td class="double page-route">{{ latest.route }}</td><td><b class="last-modified">{{ latest.modified|nicetime }}</b>
</td>
</tr>
{% endfor %} {% endfor %}
</table> </table>
</div> </div>

View File

@@ -9,12 +9,12 @@
{#{% if admin.authorize %}#} {#{% if admin.authorize %}#}
<div id="admin-user-details"> <div id="admin-user-details">
<a href="{{ base_url_relative }}/users/{{ admin.user.username }}"> <a href="{{ base_url_relative }}/users/{{ admin.user.username|e }}">
<img src="//www.gravatar.com/avatar/{{ admin.user.email|md5 }}?s=32" /> <img src="//www.gravatar.com/avatar/{{ admin.user.email|md5 }}?s=32" />
<div class="admin-user-names"> <div class="admin-user-names">
<h4>{{ admin.user.fullname }}</h4> <h4>{{ admin.user.fullname|e }}</h4>
<h5>{{ admin.user.title }}</h5> <h5>{{ admin.user.title|e }}</h5>
</div> </div>
</a> </a>
</div> </div>
@@ -34,7 +34,7 @@
<a href="{{ base_url_relative }}/pages"> <a href="{{ base_url_relative }}/pages">
<i class="fa fa-fw fa-file-text-o"></i> {{ "PLUGIN_ADMIN.PAGES"|tu }} <i class="fa fa-fw fa-file-text-o"></i> {{ "PLUGIN_ADMIN.PAGES"|tu }}
<span class="badges"> <span class="badges">
<span class="badge count">{{ admin.countPages }}</span> <span class="badge count">{{ admin.pagesCount }}</span>
</span> </span>
</a> </a>
</li> </li>

View File

@@ -1,14 +1,23 @@
<?php <?php
namespace Grav\Plugin; namespace Grav\Plugin;
use \Grav\Common\Grav; use Grav\Common\Grav;
use Grav\Common\Language\Language;
use Symfony\Component\Yaml\Yaml; use Symfony\Component\Yaml\Yaml;
use Symfony\Component\Yaml\Dumper; use Symfony\Component\Yaml\Parser;
class AdminTwigExtension extends \Twig_Extension class AdminTwigExtension extends \Twig_Extension
{ {
/**
* @var Grav
*/
protected $grav; protected $grav;
/**
* @var Language $lang
*/
protected $lang;
public function __construct() public function __construct()
{ {
$this->grav = Grav::instance(); $this->grav = Grav::instance();
@@ -37,7 +46,7 @@ class AdminTwigExtension extends \Twig_Extension
public function tuFilter() 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) public function toYamlFilter($value, $inline = true)