Merge branch 'release/0.9.36'

This commit is contained in:
Andy Miller
2015-08-11 19:38:18 -06:00
16 changed files with 254 additions and 48 deletions

View File

@@ -1,3 +1,19 @@
# v0.9.36
## 08/11/2015
1. [](#new)
* Added a new `newuser` CLI command to create user accounts
* Added `default` blueprint for all templates
* Support `user` and `system` language translation merging
1. [](#improved)
* Added isSymlink method in GPM to determine if Grav is symbolically linked or not
* Refactored page recursing
* Updated blueprints to use new toggles
* Updated blueprints to use current date for date format fields
* Updated composer.phar
* Use sessions for admin even when disabled for site
* Use `GRAV_ROOT` in session identifier
# v0.9.35
## 08/06/2015
@@ -5,13 +21,12 @@
* Added `body_classes` field
* Added `visiblity` toggle and help tooltips on new page form
* Added new `Page.unsetRoute()` method to allow admin to regenerate the route
1. [](#improved)
2. [](#improved)
* User save no longer stores username each time
* Page list form field now shows all pages except root
* Removed required option from page title
* Added configuration settings for running Nginx in sub directory
1. [](#bugfix)
* Fixed issue with GPM and cURL throwing `Undefined offset: 1` error
3. [](#bugfix)
* Fixed deep translation merging
* Fixed broken **metadata** merging with site defaults
* Fixed broken **summary** field

Binary file not shown.

View File

@@ -41,5 +41,6 @@ $app->addCommands(array(
new Grav\Console\Cli\ClearCacheCommand(),
new Grav\Console\Cli\BackupCommand(),
new Grav\Console\Cli\NewProjectCommand(),
new Grav\Console\Cli\NewUserCommand(),
));
$app->run();

View File

@@ -50,31 +50,31 @@ form:
'': 'Default (Server Timezone)'
pages.dateformat.short:
type: select
type: dateformat
size: medium
classes: fancy
label: Short date format
help: "Set the short date format that can be used by themes"
default: "jS M Y"
options:
"F jS \\a\\t g:ia": "January 1st at 11:59pm"
"l jS of F g:i A": "Monday 1st of January at 11:59 PM"
"D, m M Y G:i:s": "Mon, 01 Jan 2014 23:59:00"
"d-m-y G:i": "01-01-14 23:59"
"jS M Y": "10th Feb 2014"
"F jS \\a\\t g:ia": Date1
"l jS of F g:i A": Date2
"D, m M Y G:i:s": Date3
"d-m-y G:i": Date4
"jS M Y": Date5
pages.dateformat.long:
type: select
type: dateformat
size: medium
classes: fancy
label: Long date format
help: "Set the long date format that can be used by themes"
options:
"F jS \\a\\t g:ia": "January 1st at 11:59pm"
"l jS of F g:i A": "Monday 1st of January at 11:59 PM"
"D, m M Y G:i:s": "Mon, 01 Jan 2014 23:59:00"
"d-m-y G:i": "01-01-14 23:59"
"jS M Y": "10th Feb 2014"
"F jS \\a\\t g:ia": Date1
"l jS of F g:i A": Date2
"D, m M Y G:i:s": Date3
"d-m-y G:i": Date4
"jS M Y": Date5
pages.order.by:
type: select
@@ -180,7 +180,7 @@ form:
languages.session_store_active:
type: toggle
label: Active language in session
help: "Support translations in Grav, plugins and extensions"
help: "Store the active language in the session"
highlight: 0
options:
1: Yes

View File

@@ -50,12 +50,12 @@ form:
fields:
header.published:
type: toggle
toggleable: true
label: Published
help: "By default, a page is published unless you explicitly set published: false or via a publish_date being in the future, or unpublish_date in the past"
highlight: 1
size: medium
options:
'': Global
1: Yes
0: No
validate:
@@ -232,11 +232,11 @@ form:
header.visible:
type: toggle
toggleable: true
label: Visible
help: "Determines if a page is visible in the navigation."
highlight: 1
options:
'': Global
1: Enabled
0: Disabled
validate:
@@ -244,12 +244,11 @@ form:
header.routable:
type: toggle
toggleable: true
label: Routable
help: If this page is reachable by a URL
highlight: 1
default: ''
options:
'': Global
1: Enabled
0: Disabled
validate:
@@ -257,10 +256,10 @@ form:
header.cache_enable:
type: toggle
toggleable: true
label: Caching
highlight: 1
options:
'': Global
1: Enabled
0: Disabled
validate:

View File

@@ -2,7 +2,7 @@
// Some standard defines
define('GRAV', true);
define('GRAV_VERSION', '0.9.35');
define('GRAV_VERSION', '0.9.36');
define('DS', '/');
// Directories and Paths

View File

@@ -373,10 +373,11 @@ class Config extends Data
$content = $lang_file->content();
$this->languages->mergeRecursive($content);
}
unset($languageFiles['user/plugins']);
}
if (isset($languageFiles['system/languages'])) {
foreach ((array) $languageFiles['system/languages'] as $lang => $item) {
foreach ($languageFiles as $location) {
foreach ($location as $lang => $item) {
$lang_file = CompiledYamlFile::instance($item['file']);
$content = $lang_file->content();
$this->languages->join($lang, $content, '/');

View File

@@ -287,4 +287,14 @@ class Installer
{
return self::$error;
}
/**
* Allows to manually set an error
* @param $error the Error code
*/
public static function setError($error)
{
self::$error = $error;
}
}

View File

@@ -87,4 +87,9 @@ class Grav extends AbstractPackageCollection
{
return version_compare(GRAV_VERSION, $this->getVersion(), '<');
}
public function isSymlink()
{
return is_link(GRAV_ROOT . DS . 'index.php');
}
}

View File

@@ -80,4 +80,14 @@ class Upgrader
{
return version_compare($this->getLocalVersion(), $this->getRemoteVersion(), "<");
}
/**
* Checks if Grav is currently symbolically linked
* @return boolean True if Grav is symlinked, False otherwise.
*/
public function isSymlink()
{
return $this->remote->isSymlink();
}
}

View File

@@ -257,7 +257,7 @@ class Grav extends Container
$this['session']->close();
}
if ($this['uri']->isExternal($route)) {
if ($uri->isExternal($route)) {
$url = $route;
} else {
$url = rtrim($uri->rootUrl(), '/') .'/'. trim($route, '/');
@@ -277,7 +277,6 @@ class Grav extends Container
{
/** @var Language $language */
$language = $this['language'];
$config = $this['config'];
if ($language->enabled()) {
return $this->redirect($language->getLanguage() . $route, $code);
@@ -413,7 +412,7 @@ class Grav extends Container
}
/**
* This attempts to fine media, other files, and download them
* This attempts to find media, other files, and download them
* @param $page
* @param $path
*/

View File

@@ -705,16 +705,16 @@ class Page
$pages = self::getGrav()['pages'];
$blueprint = $pages->blueprints($this->blueprintName());
$fields = $blueprint->fields();
$edit_mode = self::getGrav()['admin'] ? self::getGrav()['config']->get('plugins.admin.edit_mode') : null;
// override if you only want 'normal' mode
if (empty($fields) && self::getGrav()['admin'] && self::getGrav()['config']->get('plugins.admin.edit_mode', 'auto') == 'normal') {
if (empty($fields) && ($edit_mode == 'auto' || $edit_mode == 'normal')) {
$blueprint = $pages->blueprints('default');
}
// override if you only want 'expert' mode
if (!empty($fields) && self::getGrav()['admin'] && self::getGrav()['config']->get('plugins.admin.edit_mode', 'auto') == 'expert') {
if (!empty($fields) && $edit_mode == 'expert') {
$blueprint = $pages->blueprints('');
}

View File

@@ -514,7 +514,7 @@ class Pages
/** @var UniformResourceLocator $locator */
$locator = $this->grav['locator'];
$pagesDir = $locator->findResource('page://');
$pages_dir = $locator->findResource('page://');
if ($config->get('system.cache.enabled')) {
/** @var Cache $cache */
@@ -529,10 +529,10 @@ class Pages
$last_modified = 0;
break;
case 'folder':
$last_modified = Folder::lastModifiedFolder($pagesDir);
$last_modified = Folder::lastModifiedFolder($pages_dir);
break;
default:
$last_modified = Folder::lastModifiedFile($pagesDir);
$last_modified = Folder::lastModifiedFile($pages_dir);
}
$page_cache_id = md5(USER_DIR.$last_modified.$language->getActive().$config->checksum());
@@ -541,25 +541,46 @@ class Pages
if (!$this->instances) {
$this->grav['debugger']->addMessage('Page cache missed, rebuilding pages..');
$this->recurse($pagesDir);
$this->buildRoutes();
// recurse pages and cache result
$this->resetPages($pages_dir, $page_cache_id);
// save pages, routes, taxonomy, and sort to cache
$cache->save(
$page_cache_id,
array($this->instances, $this->routes, $this->children, $taxonomy->taxonomy(), $this->sort)
);
} else {
// If pages was found in cache, set the taxonomy
$this->grav['debugger']->addMessage('Page cache hit.');
$taxonomy->taxonomy($taxonomy_map);
}
} else {
$this->recurse($pagesDir);
$this->recurse($pages_dir);
$this->buildRoutes();
}
}
/**
* Accessible method to manually reset the pages cache
*
* @param $pages_dir
* @param $page_cache_id
*/
public function resetPages($pages_dir, $page_cache_id)
{
$this->recurse($pages_dir);
$this->buildRoutes();
// cache if needed
if ($this->grav['config']->get('system.cache.enabled')) {
/** @var Cache $cache */
$cache = $this->grav['cache'];
/** @var Taxonomy $taxonomy */
$taxonomy = $this->grav['taxonomy'];
// save pages, routes, taxonomy, and sort to cache
$cache->save(
$page_cache_id,
array($this->instances, $this->routes, $this->children, $taxonomy->taxonomy(), $this->sort)
);
}
}
/**
* Recursive function to load & build page relationships.
*

View File

@@ -49,6 +49,9 @@ class Types implements \ArrayAccess, \Iterator, \Countable
$this->systemBlueprints = $this->findBlueprints('blueprints://pages');
}
// register default by default
$this->register('default');
foreach (Folder::all($path, $options) as $type) {
$this->register($type);
}

View File

@@ -20,17 +20,23 @@ class Session extends \RocketTheme\Toolbox\Session\Session
$uri = $this->grav['uri'];
$config = $this->grav['config'];
if ($config->get('system.session.enabled')) {
// Only activate admin if we're inside the admin path.
$is_admin = false;
$is_admin = false;
$session_timeout = $config->get('system.session.timeout', 1800);
$session_path = $config->get('system.session.path', '/' . ltrim($uri->rootUrl(false), '/'));
// Activate admin if we're inside the admin path.
if ($config->get('plugins.admin.enabled')) {
$route = $config->get('plugins.admin.route');
$base = '/' . trim($route, '/');
if (substr($uri->route(), 0, strlen($base)) == $base) {
$session_timeout = $config->get('plugins.admin.session.timeout', 1800);
$is_admin = true;
}
}
if ($config->get('system.session.enabled') || $is_admin) {
$session_timeout = $config->get('system.session.timeout', 1800);
$session_path = $config->get('system.session.path', '/' . ltrim($uri->rootUrl(false), '/'));
// Define session service.
parent::__construct(
@@ -38,8 +44,8 @@ class Session extends \RocketTheme\Toolbox\Session\Session
$session_path
);
$site_identifier = $config->get('site.title', 'unknown');
$this->setName($config->get('system.session.name', 'grav_site') . '_' . substr(md5($site_identifier), 0, 7) . ($is_admin ? '_admin' : ''));
$unique_identifier = GRAV_ROOT;
$this->setName($config->get('system.session.name', 'grav_site') . '_' . substr(md5($unique_identifier), 0, 7) . ($is_admin ? '_admin' : ''));
$this->start();
setcookie(session_name(), session_id(), time() + $session_timeout, $session_path);
}

View File

@@ -0,0 +1,136 @@
<?php
namespace Grav\Console\Cli;
use Grav\Common\Data\Blueprints;
use Grav\Common\File\CompiledYamlFile;
use Grav\Common\Filesystem\Folder;
use Grav\Common\User\User;
use Grav\Console\ConsoleTrait;
use RuntimeException;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Formatter\OutputFormatterStyle;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Question\ChoiceQuestion;
use Symfony\Component\Console\Question\Question;
/**
* Class CleanCommand
* @package Grav\Console\Cli
*/
class NewUserCommand extends Command
{
use ConsoleTrait;
/**
* Configure the command
*/
protected function configure()
{
$this
->setName("newuser")
->setDescription("Creates a new user")
->setHelp('The <info>newuser</info> creates a new user file in user/accounts/ folder');
}
/**
* @param InputInterface $input
* @param OutputInterface $output
*
* @return int|null|void
*/
protected function execute(InputInterface $input, OutputInterface $output)
{
$this->setupConsole($input, $output);
$helper = $this->getHelper('question');
$data = [];
$this->output->writeln('<green>Create new user</green>');
$this->output->writeln('');
// Get username and validate
$question = new Question('Enter a <yellow>username</yellow>: ', 'admin');
$question->setValidator(function ($value) {
if (!preg_match('/^[a-z0-9_-]{3,16}$/', $value)) {
throw new RuntimeException(
'Username should be between 3 and 16 comprised of lowercase letters, numbers, underscores and hyphens'
);
}
if (file_exists(self::getGrav()['locator']->findResource('user://accounts/' . $value . YAML_EXT))) {
throw new RuntimeException(
'Username "'.$value.'" already exists, please pick another username'
);
}
return $value;
});
$username = $helper->ask($this->input, $this->output, $question);
// Get password and validate
$question = new Question('Enter a <yellow>password</yellow>: ');
$question->setValidator(function ($value) {
if (!preg_match('/(?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{8,}/', $value)) {
throw new RuntimeException('Password must contain at least one number and one uppercase and lowercase letter, and at least 8 or more characters');
}
return $value;
});
$data['password'] = $helper->ask($this->input, $this->output, $question);
// Get email and validate
$question = new Question('Enter an <yellow>email</yellow>: ');
$question->setValidator(function ($value) {
if (!preg_match('/^([a-z0-9_\.-]+)@([\da-z\.-]+)\.([a-z\.]{2,6})$/', $value)) {
throw new RuntimeException(
'Not a valid email address'
);
}
return $value;
});
$data['email'] = $helper->ask($this->input, $this->output, $question);
// Choose permissions
$question = new ChoiceQuestion(
'Please choose a set of <yellow>permissions</yellow>:',
array('a'=>'admin access', 's'=>'site access', 'b'=>'admin and site access'),
'a'
);
$question->setErrorMessage('permissions %s is invalid.');
$permissions_choice = $helper->ask($this->input, $this->output, $question);
switch ($permissions_choice) {
case 'a':
$data['access']['admin'] = ['login' => true, 'super' => true];
break;
case 's':
$data['access']['site'] = ['login' => true];
break;
case 'b':
$data['access']['admin'] = ['login' => true, 'super' => true];
$data['access']['site'] = ['login' => true];
}
// Get fullname
$question = new Question('Enter a <yellow>fullname</yellow>: ');
$question->setValidator(function ($value) {
if ($value === null or trim($value) == '') {
throw new RuntimeException(
'Fullname is required'
);
}
return $value;
});
$data['fullname'] = $helper->ask($this->input, $this->output, $question);
// Get title
$question = new Question('Enter a <yellow>title</yellow>: ');
$data['title'] = $helper->ask($this->input, $this->output, $question);
// Create user object and save it
$user = new User($data);
$file = CompiledYamlFile::instance(self::getGrav()['locator']->findResource('user://accounts/' . $username . YAML_EXT, true, true));
$user->file($file);
$user->save();
$this->output->writeln('');
$this->output->writeln('<green>Success!</green> User <cyan>'. $username .'</cyan> created.');
}
}