diff --git a/CHANGELOG.md b/CHANGELOG.md index 5d8dbb944..68405c868 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,23 @@ 1. [](#improved) * Make it possible to include debug bar also into non-HTML responses +# v1.3.3 +## xx/xx/2017 + +1. [](#new) + * Added support for 2-Factor Authentication in admin profile + * Added `gaussianBlur` media method [#1623](https://github.com/getgrav/grav/pull/1623) + * Added new `|chunk_split()` Twig filter + * Added new `tl` Twig filter/function to spport specific translations [#1618](https://github.com/getgrav/grav/issues/1618) +1. [](#improved) + * Added options to `Page::summary()` to support size without HTML tags [#1554](https://github.com/getgrav/grav/issues/1554) + * Forced `natsort` on plugins to ensure consistent plugin load ordering across platforms [#1614](https://github.com/getgrav/grav/issues/1614) + * Use new `multilevel` field to handle Asset Collections [#1201](https://github.com/getgrav/grav-plugin-admin/issues/1201) + * Added support for redis `password` option [#1620](https://github.com/getgrav/grav/issues/1620) + * Use 302 rather than 301 redirects by default [#1619](https://github.com/getgrav/grav/issues/1619) +1. [](#bugfix) + * Fixed UTF8 2 character support in `Page::summary()` [#1554](https://github.com/getgrav/grav/issues/1554) + # v1.3.2 ## 08/16/2017 diff --git a/composer.json b/composer.json index deb9d2990..2b1d2258f 100644 --- a/composer.json +++ b/composer.json @@ -21,7 +21,7 @@ "filp/whoops": "~2.0", "matthiasmullie/minify": "^1.3", "monolog/monolog": "~1.0", - "gregwar/image": "~2.0", + "gregwar/image": "2.*", "donatj/phpuseragentparser": "~0.3", "pimple/pimple": "~3.0", "rockettheme/toolbox": "~1.3", diff --git a/system/blueprints/config/system.yaml b/system/blueprints/config/system.yaml index 9ed7a0753..33c98547e 100644 --- a/system/blueprints/config/system.yaml +++ b/system/blueprints/config/system.yaml @@ -616,6 +616,12 @@ form: help: PLUGIN_ADMIN.REDIS_PORT_HELP placeholder: "6379" + cache.redis.password: + type: text + size: small + label: PLUGIN_ADMIN.REDIS_PASSWORD + + twig: type: section @@ -806,10 +812,12 @@ form: type: bool assets.collections: - type: array + type: multilevel label: PLUGIN_ADMIN.COLLECTIONS placeholder_key: collection_name placeholder_value: collection_path + validate: + type: array errors: type: section diff --git a/system/blueprints/user/account.yaml b/system/blueprints/user/account.yaml index 3d4d7cb78..29628c40a 100644 --- a/system/blueprints/user/account.yaml +++ b/system/blueprints/user/account.yaml @@ -66,6 +66,37 @@ form: default: 'en' help: PLUGIN_ADMIN.LANGUAGE_HELP + twofa_check: + type: conditional + condition: config.plugins.admin.twofa_enabled + + fields: + + twofa: + title: PLUGIN_ADMIN.2FA_TITLE + type: section + underline: true + + twofa_enabled: + type: toggle + label: PLUGIN_ADMIN.2FA_ENABLED + classes: twofa-toggle + highlight: 1 + default: 0 + options: + 1: PLUGIN_ADMIN.YES + 0: PLUGIN_ADMIN.NO + validate: + type: bool + + + twofa_secret: + type: 2fa_secret + outerclasses: 'twofa-secret' + label: PLUGIN_ADMIN.2FA_SECRET + sublabel: PLUGIN_ADMIN.2FA_SECRET_HELP + + security: title: PLUGIN_ADMIN.ACCESS_LEVELS type: section diff --git a/system/config/system.yaml b/system/config/system.yaml index 382c496be..a6110c4a8 100644 --- a/system/config/system.yaml +++ b/system/config/system.yaml @@ -58,8 +58,8 @@ pages: etag: false # Set the etag header tag vary_accept_encoding: false # Add `Vary: Accept-Encoding` header redirect_default_route: false # Automatically redirect to a page's default route - redirect_default_code: 301 # Default code to use for redirects - redirect_trailing_slash: true # Handle automatically or 301 redirect a trailing / URL + redirect_default_code: 302 # Default code to use for redirects + redirect_trailing_slash: true # Handle automatically or 302 redirect a trailing / URL ignore_files: [.DS_Store] # Files to ignore in Pages ignore_folders: [.git, .idea] # Folders to ignore in Pages ignore_hidden: true # Ignore all Hidden files and folders diff --git a/system/src/Grav/Common/Cache.php b/system/src/Grav/Common/Cache.php index 6746ab359..254f6fdd0 100644 --- a/system/src/Grav/Common/Cache.php +++ b/system/src/Grav/Common/Cache.php @@ -240,6 +240,7 @@ class Cache extends Getters case 'redis': $redis = new \Redis(); $socket = $this->config->get('system.cache.redis.socket', false); + $password = $this->config->get('system.cache.redis.password', false); if ($socket) { $redis->connect($socket); @@ -248,6 +249,11 @@ class Cache extends Getters $this->config->get('system.cache.redis.port', 6379)); } + // Authenticate with password if set + if ($password && !$redis->auth($password)) { + throw new \RedisException('Redis authentication failed'); + } + $driver = new DoctrineCache\RedisCache(); $driver->setRedis($redis); break; diff --git a/system/src/Grav/Common/Grav.php b/system/src/Grav/Common/Grav.php index 4f5b16f80..71b3f63a9 100644 --- a/system/src/Grav/Common/Grav.php +++ b/system/src/Grav/Common/Grav.php @@ -175,7 +175,7 @@ class Grav extends Container } if ($code === null) { - $code = $this['config']->get('system.pages.redirect_default_code', 301); + $code = $this['config']->get('system.pages.redirect_default_code', 302); } if (isset($this['session'])) { diff --git a/system/src/Grav/Common/Page/Medium/ImageMedium.php b/system/src/Grav/Common/Page/Medium/ImageMedium.php index 8cfcb1017..23b5b3ce7 100644 --- a/system/src/Grav/Common/Page/Medium/ImageMedium.php +++ b/system/src/Grav/Common/Page/Medium/ImageMedium.php @@ -51,7 +51,7 @@ class ImageMedium extends Medium 'resize', 'forceResize', 'cropResize', 'crop', 'zoomCrop', 'negate', 'brightness', 'contrast', 'grayscale', 'emboss', 'smooth', 'sharp', 'edge', 'colorize', 'sepia', 'enableProgressive', - 'rotate', 'flip', 'fixOrientation' + 'rotate', 'flip', 'fixOrientation', 'gaussianBlur' ]; /** diff --git a/system/src/Grav/Common/Page/Page.php b/system/src/Grav/Common/Page/Page.php index 2395697ba..1915f0ec7 100644 --- a/system/src/Grav/Common/Page/Page.php +++ b/system/src/Grav/Common/Page/Page.php @@ -116,7 +116,7 @@ class Page * Initializes the page instance variables based on a file * * @param \SplFileInfo $file The file information for the .md file that the page represents - * @param string $extension + * @param string $extension * * @return $this */ @@ -331,7 +331,8 @@ class Page $frontmatter_file = $this->path . '/' . $this->folder . '/frontmatter.yaml'; if (file_exists($frontmatter_file)) { $frontmatter_data = (array)Yaml::parse(file_get_contents($frontmatter_file)); - $this->header = (object)array_replace_recursive($frontmatter_data, (array)$this->header); + $this->header = (object)array_replace_recursive($frontmatter_data, + (array)$this->header); } // Process frontmatter with Twig if enabled if (Grav::instance()['config']->get('system.pages.frontmatter.process_twig') === true) { @@ -485,9 +486,11 @@ class Page * * @param int $size Max summary size. * + * @param boolean $textOnly Only count text size. + * * @return string */ - public function summary($size = null) + public function summary($size = null, $textOnly = false) { $config = (array)Grav::instance()['config']->get('site.summary'); if (isset($this->header->summary)) { @@ -501,11 +504,12 @@ class Page // Set up variables to process summary from page or from custom summary if ($this->summary === null) { - $content = $this->content(); + $content = $textOnly ? strip_tags($this->content()) : $this->content(); $summary_size = $this->summary_size; } else { - $content = $this->summary; - $summary_size = mb_strlen($this->summary); + $content = strip_tags($this->summary); + // Use mb_strwidth to deal with the 2 character widths characters + $summary_size = mb_strwidth($content, 'utf-8'); } // Return calculated summary based on summary divider's position @@ -514,7 +518,12 @@ class Page if (!in_array($format, ['short', 'long'])) { return $content; } elseif (($format === 'short') && isset($summary_size)) { - return mb_substr($content, 0, $summary_size); + // Use mb_strimwidth to slice the string + if (mb_strwidth($content, 'utf8') > $summary_size) { + return mb_strimwidth($content, 0, $summary_size); + } else { + return $content; + } } // Get summary size from site config's file @@ -530,6 +539,15 @@ class Page $size = 300; } + // Only return string but not html, wrap whatever html tag you want when using + if ($textOnly) { + if (mb_strwidth($content, 'utf-8') <= $size) { + return $content; + } + + return mb_strimwidth($content, 0, $size, '...', 'utf-8'); + } + $summary = Utils::truncateHTML($content, $size); return html_entity_decode($summary); @@ -590,7 +608,7 @@ class Page $process_markdown = $this->shouldProcess('markdown'); - $process_twig = $this->shouldProcess('twig') || $this->modularTwig() ; + $process_twig = $this->shouldProcess('twig') || $this->modularTwig(); $cache_enable = isset($this->header->cache_enable) ? $this->header->cache_enable : $config->get('system.cache.enabled', true); @@ -801,7 +819,7 @@ class Page * Get value from a page variable (used mostly for creating edit forms). * * @param string $name Variable name. - * @param mixed $default + * @param mixed $default * * @return mixed */ @@ -1078,7 +1096,7 @@ class Page public function toArray() { return [ - 'header' => (array)$this->header(), + 'header' => (array)$this->header(), 'content' => (string)$this->value('content') ]; } @@ -1486,9 +1504,9 @@ class Page foreach ($value as $property => $prop_value) { $prop_key = $key . ":" . $property; $this->metadata[$prop_key] = [ - 'name' => $prop_key, + 'name' => $prop_key, 'property' => $prop_key, - 'content' => htmlspecialchars($prop_value, ENT_QUOTES, 'UTF-8') + 'content' => htmlspecialchars($prop_value, ENT_QUOTES, 'UTF-8') ]; } } else { @@ -1497,7 +1515,7 @@ class Page if (in_array($key, $header_tag_http_equivs)) { $this->metadata[$key] = [ 'http_equiv' => $key, - 'content' => htmlspecialchars($value, ENT_QUOTES, 'UTF-8') + 'content' => htmlspecialchars($value, ENT_QUOTES, 'UTF-8') ]; } elseif ($key == 'charset') { $this->metadata[$key] = ['charset' => htmlspecialchars($value, ENT_QUOTES, 'UTF-8')]; @@ -1505,7 +1523,10 @@ class Page // if it's a social metadata with separator, render as property $separator = strpos($key, ':'); $hasSeparator = $separator && $separator < strlen($key) - 1; - $entry = ['name' => $key, 'content' => htmlspecialchars($value, ENT_QUOTES, 'UTF-8')]; + $entry = [ + 'name' => $key, + 'content' => htmlspecialchars($value, ENT_QUOTES, 'UTF-8') + ]; if ($hasSeparator) { $entry['property'] = $key; @@ -1589,6 +1610,7 @@ class Page * Returns the canonical URL for a page * * @param bool $include_lang + * * @return string */ public function canonical($include_lang = true) @@ -1603,6 +1625,7 @@ class Page * @param bool $canonical true to return the canonical URL * @param bool $include_lang * @param bool $raw_route + * * @return string The url. */ public function url($include_host = false, $canonical = false, $include_lang = true, $raw_route = false) @@ -2327,7 +2350,7 @@ class Page * Helper method to return an ancestor page. * * @param string $url The url of the page - * @param bool $lookup Name of the parent folder + * @param bool $lookup Name of the parent folder * * @return \Grav\Common\Page\Page page you were looking for if it exists */ @@ -2343,7 +2366,7 @@ class Page * Helper method to return an ancestor page to inherit from. The current * page object is returned. * - * @param string $field Name of the parent folder + * @param string $field Name of the parent folder * * @return Page */ @@ -2355,11 +2378,12 @@ class Page return $inherited; } + /** * Helper method to return an ancestor field only to inherit from. The * first occurrence of an ancestor field will be returned if at all. * - * @param string $field Name of the parent folder + * @param string $field Name of the parent folder * * @return array */ @@ -2373,7 +2397,7 @@ class Page /** * Method that contains shared logic for inherited() and inheritedField() * - * @param string $field Name of the parent folder + * @param string $field Name of the parent folder * * @return array */ @@ -2383,11 +2407,12 @@ class Page /** @var Pages $pages */ $inherited = $pages->inherited($this->route, $field); - $inheritedParams = (array) $inherited->value('header.' . $field); - $currentParams = (array) $this->value('header.' . $field); - if($inheritedParams && is_array($inheritedParams)) { + $inheritedParams = (array)$inherited->value('header.' . $field); + $currentParams = (array)$this->value('header.' . $field); + if ($inheritedParams && is_array($inheritedParams)) { $currentParams = array_replace_recursive($inheritedParams, $currentParams); } + return [$inherited, $currentParams]; } @@ -2395,7 +2420,7 @@ class Page * Helper method to return a page. * * @param string $url the url of the page - * @param bool $all + * @param bool $all * * @return \Grav\Common\Page\Page page you were looking for if it exists */ @@ -2411,7 +2436,7 @@ class Page * Get a collection of pages in the current context. * * @param string|array $params - * @param boolean $pagination + * @param boolean $pagination * * @return Collection * @throws \InvalidArgumentException @@ -2747,7 +2772,7 @@ class Page // Reorder all moved pages. foreach ($siblings as $slug => $page) { - $order = intval(trim($page->order(),'.')); + $order = intval(trim($page->order(), '.')); $counter++; if ($order) { diff --git a/system/src/Grav/Common/Plugins.php b/system/src/Grav/Common/Plugins.php index 340caf15d..25fb356ad 100644 --- a/system/src/Grav/Common/Plugins.php +++ b/system/src/Grav/Common/Plugins.php @@ -27,13 +27,18 @@ class Plugins extends Iterator $locator = Grav::instance()['locator']; $iterator = $locator->getIterator('plugins://'); - foreach ($iterator as $directory) { + + $plugins = []; + foreach($iterator as $directory) { if (!$directory->isDir()) { continue; } + $plugins[] = $directory->getBasename(); + } - $plugin = $directory->getBasename(); + natsort($plugins); + foreach ($plugins as $plugin) { $this->add($this->loadPlugin($plugin)); } } diff --git a/system/src/Grav/Common/Twig/TwigExtension.php b/system/src/Grav/Common/Twig/TwigExtension.php index 932e27d03..c791e0fed 100644 --- a/system/src/Grav/Common/Twig/TwigExtension.php +++ b/system/src/Grav/Common/Twig/TwigExtension.php @@ -67,6 +67,8 @@ class TwigExtension extends \Twig_Extension new \Twig_SimpleFilter('*ize', [$this, 'inflectorFilter']), new \Twig_SimpleFilter('absolute_url', [$this, 'absoluteUrlFilter']), new \Twig_SimpleFilter('contains', [$this, 'containsFilter']), + new \Twig_SimpleFilter('chunk_split', [$this, 'chunkSplitFilter']), + new \Twig_SimpleFilter('nicenumber', [$this, 'niceNumberFunc']), new \Twig_SimpleFilter('defined', [$this, 'definedDefaultFilter']), new \Twig_SimpleFilter('ends_with', [$this, 'endsWithFilter']), @@ -91,6 +93,7 @@ class TwigExtension extends \Twig_Extension new \Twig_SimpleFilter('sort_by_key', [$this, 'sortByKeyFilter']), new \Twig_SimpleFilter('starts_with', [$this, 'startsWithFilter']), new \Twig_SimpleFilter('t', [$this, 'translate']), + new \Twig_SimpleFilter('tl', [$this, 'translateLanguage']), new \Twig_SimpleFilter('ta', [$this, 'translateArray']), new \Twig_SimpleFilter('truncate', ['\Grav\Common\Utils', 'truncate']), new \Twig_SimpleFilter('truncate_html', ['\Grav\Common\Utils', 'truncateHTML']), @@ -126,6 +129,7 @@ class TwigExtension extends \Twig_Extension new \Twig_SimpleFunction('regex_replace', [$this, 'regexReplace']), new \Twig_SimpleFunction('string', [$this, 'stringFunc']), new \Twig_simpleFunction('t', [$this, 'translate']), + new \Twig_simpleFunction('tl', [$this, 'translateLanguage']), new \Twig_simpleFunction('ta', [$this, 'translateArray']), new \Twig_SimpleFunction('url', [$this, 'urlFunc']), new \Twig_SimpleFunction('json_decode', [$this, 'jsonDecodeFilter']), @@ -378,6 +382,19 @@ class TwigExtension extends \Twig_Extension return $array; } + /** + * Wrapper for chunk_split() function + * + * @param $value + * @param $chars + * @param string $split + * @return string + */ + public function chunkSplitFilter($value, $chars, $split = '-') + { + return chunk_split($value, $chars, $split); + } + /** * determine if a string contains another * @@ -596,6 +613,20 @@ class TwigExtension extends \Twig_Extension return $this->grav['language']->translate(func_get_args()); } + /** + * Translate Strings + * + * @param $args + * @param array|null $languages + * @param bool $array_support + * @param bool $html_out + * @return mixed + */ + public function translateLanguage($args, array $languages = null, $array_support = false, $html_out = false) + { + return $this->grav['language']->translate($args, $languages, $array_support, $html_out); + } + /** * @param $key * @param $index diff --git a/system/src/Grav/Common/Uri.php b/system/src/Grav/Common/Uri.php index bb95c77ec..45daf1a04 100644 --- a/system/src/Grav/Common/Uri.php +++ b/system/src/Grav/Common/Uri.php @@ -295,9 +295,9 @@ class Uri $uri = str_replace($setup_base, '', $uri); } - // If configured to, redirect trailing slash URI's with a 301 redirect + // If configured to, redirect trailing slash URI's with a 302 redirect if ($config->get('system.pages.redirect_trailing_slash', false) && $uri != '/' && Utils::endsWith($uri, '/')) { - $grav->redirect(str_replace($this->root, '', rtrim($uri, '/')), 301); + $grav->redirect(str_replace($this->root, '', rtrim($uri, '/')), 302); } // process params diff --git a/system/src/Grav/Common/User/User.php b/system/src/Grav/Common/User/User.php index d7f42112e..4a3831054 100644 --- a/system/src/Grav/Common/User/User.php +++ b/system/src/Grav/Common/User/User.php @@ -200,6 +200,10 @@ class User extends Data return false; } + if (!$this->authenticated) { + return false; + } + if (isset($this->state) && $this->state !== 'enabled') { return false; }