Merge branch 'release/0.9.30'

This commit is contained in:
Andy Miller
2015-07-08 13:26:01 -06:00
38 changed files with 1817 additions and 190 deletions

View File

@@ -44,7 +44,7 @@ before_install:
- go get github.com/aktau/github-release
- git clone --quiet --depth=50 --branch=master https://${BB_TOKEN}bitbucket.org/rockettheme/grav-devtools.git $RT_DEVTOOLS &>/dev/null;
- if [ ! -z "$TRAVIS_TAG" ]; then
cd $RT_DEVTOOLS;
cd "${RT_DEVTOOLS}";
./build-grav.sh skeletons.txt;
fi
script:

View File

@@ -1,3 +1,26 @@
# v0.9.30
## 07/08/2015
1. [](#new)
* BIG NEWS! Extensive Multi-Language support is all new in 0.9.30!
* Translation support via Twig filter/function and PHP method
* Page specific default route
* Page specific route aliases
* Canonical URL route support
* Added built-in session support
* New `Page.rawRoute()` to get a consistent folder-based route to a page
* Added option to always redirect to default page on alias URL
* Added language safe redirect function for use in core and plugins
2. [](#improved)
* Improved `Page.active()` and `Page.activeChild()` methods to support route aliases
* Various spelling corrections in `.php` comments, `.md` and `.yaml` files
* `Utils::startsWith()` and `Utils::endsWith()` now support needle arrays
* Added a new timer around `pageInitialized` event
* Updated jQuery library to v2.1.4
3. [](#bugfix)
* In-page CSS and JS files are now handled properly
* Fix for `enable_media_timestamp` not working properly
# v0.9.29
## 06/22/2015
@@ -35,7 +58,7 @@
* Fix for `+` in image filenames
* Fix for dot files causing issues with page processing
* Fix for Uri path detection on Windows platform
* Fix for atlernative media resolutions
* Fix for alternative media resolutions
* Fix for modularTypes key properties
# v0.9.27
@@ -49,7 +72,7 @@
* Added a new `parseLinks` method to Plugins class
* Added `starts_with` and `ends_with` Twig filters
2. [](#improved)
* Opitmized install of vendor libraries for speed improvement
* Optimized install of vendor libraries for speed improvement
* Improved configuration handling in preparation for admin plugin
* Cache optimization: Don't cache Twig templates when you pass dynamic params
* Moved `Utils::rcopy` to `Folder::rcopy`
@@ -63,7 +86,7 @@
* Fix for URLs with trailing slashes
* Handle condition where certain errors resulted in blank page
* Fix for issue with theme name equal to base_url and asset pipeline
* Fix to properly nomralize font rewrite path
* Fix to properly normalize font rewrite path
* Fix for absolute URLs below the current page
* Fix for `..` page references
@@ -81,7 +104,7 @@
2. [](#improved)
* Refactored media image handling to make it more flexible and support absolute paths
* Refactored page modification check process to make it faster
* User account improvements in preparation for Admin plugin
* User account improvements in preparation for admin plugin
* Protect against timing attacks
* Reset default system expires time to 0 seconds (can override if you need to)
3. [](#bugfix)
@@ -209,7 +232,7 @@
* Improved the markdown Lightbox functionality to better mimic Twig version
* Fullsize Lightbox can now have filters applied
* Added a new `mergeConfig()` method to Plugin class to merge system + page header configuration
* Added a new `disable()` method to Plugin class to programatically disable a plugin
* Added a new `disable()` method to Plugin class to programmatically disable a plugin
* Updated Parsedown and Parsedown Extra to address bugs
* Various PSR fixes
3. [](#bugfix)
@@ -262,7 +285,7 @@
* Added `publish_date` in page headers to automatically publish page
* Added `unpublish_date` in page headers to automatically unpublish page
* Added `dateRange()` capability for collections
* Added ability to dynamically control Cache lifetime programatically
* Added ability to dynamically control Cache lifetime programmatically
* Added ability to sort by anything in the page header. E.g. `sort: header.taxonomy.year`
* Added various helper methods to collections: `copy, nonVisible, modular, nonModular, published, nonPublished, nonRoutable`
2. [](#improved)
@@ -437,7 +460,7 @@
* Broke cache types out into multiple directories in the cache folder
* Removed vendor libs from github repository
* Various PSR cleanup of code
* Various Blueprint updates to support upcoming Admin plugin
* Various Blueprint updates to support upcoming admin plugin
* Added ability to filter page children for normal/modular/all
* Added `sort_by_key` twig filter
* Added `visible()` and `routable()` filters to page collections
@@ -510,7 +533,7 @@
* Addition of Dependency Injection Container
* Refactored plugins to use Symfony Event Dispatcher
* New Asset Manager to provide unified management of JavaScript and CSS
* Asset Pipelining to provide unification, minify, and optimazation of JavaScript and CSS
* Asset Pipelining to provide unification, minify, and optimization of JavaScript and CSS
* Grav Media support directly in Markdown syntax
* Additional Grav Generator meta tag in default themes
* Added support for PHP Stream Wrapper for resource location

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -6,22 +6,25 @@ author:
taxonomies: [category,tag] # Arbitrary list of taxonomy types
blog:
route: '/blog' # Route to blog (deprecated)
metadata:
description: 'My Grav Site' # Site description
summary:
enabled: true # enable or disable summary of page
format: short # long = summary delimiter will be ignored; short = use the first occurence of delimter or size
format: short # long = summary delimiter will be ignored; short = use the first occurrence of delimiter or size
size: 300 # Maximum length of summary (characters)
delimiter: === # The summary delimiter
redirects:
/redirect-test: / # Redirect test goes to home page
/old/(.*): /new/$1 # Would redirect /old/my-page to /new/my-page
routes:
/something/else: '/blog/sample-3' # Alias for /blog/sample-3
/another/one/here: '/blog/sample-3' # Another alias for /blog/sample-3
/new/*: '/blog/*' # Wildcard any /new/my-page URL to /blog/my-page Route
/new/(.*): '/blog/$1' # Regex any /new/my-page URL to /blog/my-page Route
blog:
route: '/blog' # Custom value added (accessible via system.blog.route)
#menu: # Sample Menu Example
# - text: Source

View File

@@ -31,9 +31,10 @@ pages:
'>': 'gt'
'<': 'lt'
types: 'txt|xml|html|json|rss|atom' # Pipe separated list of valid page types
expires: 0 # Page expires time in seconds (604800 seconds = 7 days)
expires: 604800 # Page expires time in seconds (604800 seconds = 7 days)
last_modified: false # Set the last modified date header based on file modifcation timestamp
etag: false # Set the etag header tag
redirect_default_route: false # Automatically redirect to a page's default route
cache:
enabled: true # Set to true to enable caching
@@ -60,9 +61,9 @@ assets: # Configuration for Assets Manager (JS, C
css_rewrite: true # Rewrite any CSS relative URLs during pipelining
js_pipeline: false # The JS pipeline is the unification of multiple JS resources into one file
js_minify: true # Minify the JS during pipelining
enable_asset_timestamp: false # Enable asset timetsamps
enable_asset_timestamp: false # Enable asset timestamps
collections:
jquery: system://assets/jquery/jquery-2.1.3.min.js
jquery: system://assets/jquery/jquery-2.1.4.min.js
errors:
display: true # Display full backtrace-style error page
@@ -82,5 +83,10 @@ media:
enable_media_timestamp: false # Enable media timetsamps
upload_limit: 0 # Set maximum upload size in bytes (0 is unlimited)
session:
enabled: true # Enable Session support
timeout: 1800 # Timeout in seconds
name: grav-site # Name prefix of the session cookie
security:
default_hash: $2y$10$kwsyMVwM8/7j0K/6LHT.g.Fs49xOCTp2b8hh/S5.dPJuJcJB6T.UK

View File

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

View File

@@ -47,7 +47,7 @@ class Assets
* Closure used by the pipeline to fetch assets.
*
* Useful when file_get_contents() function is not available in your PHP
* instalation or when you want to apply any kind of preprocessing to
* installation or when you want to apply any kind of preprocessing to
* your assets before they get pipelined.
*
* The closure will receive as the only parameter a string with the path/URL of the asset and
@@ -509,7 +509,7 @@ class Assets
/**
* Minifiy and concatenate CSS / JS files.
* Minify and concatenate CSS / JS files.
*
* @return string
*/
@@ -741,7 +741,7 @@ class Assets
/**
* Determine whether a link is local or remote.
*
* Undestands both "http://" and "https://" as well as protocol agnostic links "//"
* Understands both "http://" and "https://" as well as protocol agnostic links "//"
*
* @param string $link
*
@@ -920,7 +920,7 @@ class Assets
*
* @param string $directory
* @param string $pattern (regex)
* @param string $ltrim Will be trimed from the left of the file path
* @param string $ltrim Will be trimmed from the left of the file path
*
* @return array
*/

View File

@@ -271,7 +271,7 @@ class Cache extends Getters
/**
* Set the cache lifetime programatically
* Set the cache lifetime programmatically
*
* @param int $future timestamp
*/

View File

@@ -60,6 +60,12 @@ class Config extends Data
'' => ['user://themes'],
]
],
'languages' => [
'type' => 'ReadOnlyStream',
'prefixes' => [
'' => ['user://languages', 'system/languages'],
]
],
'cache' => [
'type' => 'Stream',
'prefixes' => [
@@ -85,17 +91,21 @@ class Config extends Data
protected $blueprintFiles = [];
protected $configFiles = [];
protected $languageFiles = [];
protected $checksum;
protected $timestamp;
protected $configLookup;
protected $blueprintLookup;
protected $pluginLookup;
protected $languagesLookup;
protected $finder;
protected $environment;
protected $messages = [];
protected $languages;
public function __construct(array $setup = array(), Grav $grav = null, $environment = null)
{
$this->grav = $grav ?: Grav::instance();
@@ -164,42 +174,61 @@ class Config extends Data
$this->blueprintLookup = $locator->findResources('blueprints://config');
$this->pluginLookup = $locator->findResources('plugins://');
$this->loadCompiledBlueprints($this->blueprintLookup, $this->pluginLookup, 'master');
$this->loadCompiledConfig($this->configLookup, $this->pluginLookup, 'master');
// process languages if supported
if ($this->get('languages', false)) {
$this->languagesLookup = $locator->findResources('languages://');
$this->loadCompiledLanguages($this->languagesLookup, $this->pluginLookup, 'master');
}
$this->initializeLocator($locator);
}
public function checksum()
{
$checkBlueprints = $this->get('system.cache.check.blueprints', false);
$checkConfig = $this->get('system.cache.check.config', true);
$checkSystem = $this->get('system.cache.check.system', true);
if (empty($this->checksum)) {
$checkBlueprints = $this->get('system.cache.check.blueprints', false);
$checkLanguages = $this->get('system.cache.check.languages', false);
$checkConfig = $this->get('system.cache.check.config', true);
$checkSystem = $this->get('system.cache.check.system', true);
if (!$checkBlueprints && !$checkConfig && !$checkSystem) {
$this->messages[] = 'Skip configuration timestamp check.';
return false;
if (!$checkBlueprints && !!$checkLanguages && $checkConfig && !$checkSystem) {
$this->messages[] = 'Skip configuration timestamp check.';
return false;
}
// Generate checksum according to the configuration settings.
if (!$checkConfig) {
$this->messages[] = 'Check configuration timestamps from system.yaml files.';
// Just check changes in system.yaml files and ignore all the other files.
$cc = $checkSystem ? $this->finder->locateConfigFile($this->configLookup, 'system') : [];
} else {
$this->messages[] = 'Check configuration timestamps from all configuration files.';
// Check changes in all configuration files.
$cc = $this->finder->locateConfigFiles($this->configLookup, $this->pluginLookup);
}
if ($checkBlueprints) {
$this->messages[] = 'Check blueprint timestamps from all blueprint files.';
$cb = $this->finder->locateBlueprintFiles($this->blueprintLookup, $this->pluginLookup);
} else {
$cb = [];
}
if ($checkLanguages) {
$this->messages[] = 'Check language timestamps from all language files.';
$cl = $this->finder->locateLanguageFiles($this->languagesLookup, $this->pluginLookup);
} else {
$cl = [];
}
$this->checksum = md5(json_encode([$cc, $cb, $cl]));
}
// Generate checksum according to the configuration settings.
if (!$checkConfig) {
$this->messages[] = 'Check configuration timestamps from system.yaml files.';
// Just check changes in system.yaml files and ignore all the other files.
$cc = $checkSystem ? $this->finder->locateConfigFile($this->configLookup, 'system') : [];
} else {
$this->messages[] = 'Check configuration timestamps from all configuration files.';
// Check changes in all configuration files.
$cc = $this->finder->locateConfigFiles($this->configLookup, $this->pluginLookup);
}
if ($checkBlueprints) {
$this->messages[] = 'Check blueprint timestamps from all blueprint files.';
$cb = $this->finder->locateBlueprintFiles($this->blueprintLookup, $this->pluginLookup);
} else {
$cb = [];
}
return md5(json_encode([$cc, $cb]));
return $this->checksum;
}
protected function autoDetectEnvironmentConfig($items)
@@ -312,6 +341,54 @@ class Config extends Data
$this->items = $cache['data'];
}
protected function loadCompiledLanguages($languages, $plugins, $filename = null)
{
$checksum = md5(json_encode($languages));
$filename = $filename
? CACHE_DIR . 'compiled/languages/' . $filename . '-' . $this->environment . '.php'
: CACHE_DIR . 'compiled/languages/' . $checksum . '-' . $this->environment . '.php';
$file = PhpFile::instance($filename);
$cache = $file->exists() ? $file->content() : null;
$languageFiles = $this->finder->locateLanguageFiles($languages, $plugins);
$checksum .= ':' . md5(json_encode($languageFiles));
$class = get_class($this);
// Load real file if cache isn't up to date (or is invalid).
if (
!is_array($cache)
|| !isset($cache['checksum'])
|| !isset($cache['@class'])
|| $cache['checksum'] != $checksum
|| $cache['@class'] != $class
) {
// Attempt to lock the file for writing.
$file->lock(false);
// Load languages.
$this->languages = new Languages;
foreach ($languageFiles as $files) {
$this->loadLanguagesFiles($files);
}
$this->languages->reformat();
$cache = [
'@class' => $class,
'checksum' => $checksum,
'files' => $languageFiles,
'data' => $this->languages->toArray()
];
// If compiled file wasn't already locked by another process, save it.
if ($file->locked() !== false) {
$this->messages[] = 'Saving compiled languages.';
$file->save($cache);
$file->unlock();
}
} else {
$this->languages = new Languages($cache['data']);
}
}
/**
* Load blueprints.
*
@@ -338,6 +415,14 @@ class Config extends Data
}
}
public function loadLanguagesFiles(array $files)
{
foreach ($files as $name => $item) {
$file = CompiledYamlFile::instance($item['file']);
$this->languages->join($name, $file->content(), '/');
}
}
/**
* Initialize resource locator by using the configuration.
*
@@ -380,4 +465,9 @@ class Config extends Data
return $schemes;
}
public function getLanguages()
{
return $this->languages;
}
}

View File

@@ -49,6 +49,18 @@ class ConfigFinder
return $list;
}
public function locateLanguageFiles(array $languages, array $plugins)
{
$list = [];
foreach (array_reverse($plugins) as $folder) {
$list += $this->detectLanguagesInFolder($folder, 'languages');
}
foreach (array_reverse($languages) as $folder) {
$list += $this->detectRecursive($folder);
}
return $list;
}
/**
* Get all locations for a single configuration file.
*
@@ -90,11 +102,11 @@ class ConfigFinder
$list = [];
if (is_dir($folder)) {
$iterator = new \DirectoryIterator($folder);
$iterator = new \FilesystemIterator($folder);
/** @var \DirectoryIterator $directory */
foreach ($iterator as $directory) {
if (!$directory->isDir() || $directory->isDot()) {
if (!$directory->isDir()) {
continue;
}
@@ -111,6 +123,34 @@ class ConfigFinder
return [$path => $list];
}
protected function detectLanguagesInFolder($folder, $lookup = null)
{
$path = trim(Folder::getRelativePath($folder), '/');
$list = [];
if (is_dir($folder)) {
$iterator = new \FilesystemIterator($folder);
/** @var \DirectoryIterator $directory */
foreach ($iterator as $directory) {
if (!$directory->isDir()) {
continue;
}
$name = $directory->getBasename();
$find = ($lookup ?: $name) . '.yaml';
$filename = "{$path}/{$name}/$find";
if (file_exists($filename)) {
$list["plugins"] = ['file' => $filename, 'modified' => filemtime($filename)];
}
}
}
return [$path => $list];
}
/**
* Detects all plugins with a configuration file and returns them with last modification time.
*

View File

@@ -0,0 +1,27 @@
<?php
namespace Grav\Common\Config;
use Grav\Common\Data\Data;
/**
* The Languages class contains configuration rules.
*
* @author RocketTheme
* @license MIT
*/
class Languages extends Data
{
public function reformat()
{
if (isset($this->items['plugins'])) {
$this->items = array_merge_recursive($this->items, $this->items['plugins']);
unset($this->items['plugins']);
}
}
public function mergeRecursive(array $data)
{
$this->items = array_merge_recursive($this->items, $data);
}
}

View File

@@ -32,7 +32,7 @@ trait DataMutatorTrait
}
/**
* Sey value by using dot notation for nested arrays/objects.
* Set value by using dot notation for nested arrays/objects.
*
* @example $value = $data->set('this.is.my.nested.variable', true);
*

View File

@@ -38,12 +38,12 @@ abstract class Folder
* Recursively find the last modified time under given path by file.
*
* @param string $path
* @param string $extensions
*
* @return int
*/
public static function lastModifiedFile($path)
public static function lastModifiedFile($path, $extensions = 'md|yaml')
{
// pipe separated list of extensions to search for changes with
$extensions = 'md|yaml';
$last_modified = 0;
$dirItr = new \RecursiveDirectoryIterator($path, \RecursiveDirectoryIterator::SKIP_DOTS);

View File

@@ -361,7 +361,7 @@ class GPM extends Iterator
}
if ($found = $this->findPackage($search)) {
// set override respository if provided
// set override repository if provided
if ($repository) {
$found->override_repository = $repository;
}

View File

@@ -158,7 +158,7 @@ class Installer
/**
* Unnstalls one or more given package
* Uninstalls one or more given package
*
* @param string $path The slug of the package(s)
* @param array $options Options to use for uninstalling

View File

@@ -65,7 +65,7 @@ class Upgrader
* Returns the changelog list for each version of Grav
* @param string $diff the version number to start the diff from
*
* @return array return the chagenlog list for each version
* @return array return the changelog list for each version
*/
public function getChangelog($diff = null)
{

View File

@@ -1,6 +1,7 @@
<?php
namespace Grav\Common;
use Grav\Common\Language\Language;
use Grav\Common\Page\Medium\ImageMedium;
use Grav\Common\Page\Pages;
use Grav\Common\Service\ConfigServiceProvider;
@@ -55,6 +56,8 @@ class Grav extends Container
$container['grav'] = $container;
$container['debugger'] = new Debugger();
$container['debugger']->startTimer('_init', 'Initialize');
@@ -76,6 +79,9 @@ class Grav extends Container
$container['cache'] = function ($c) {
return new Cache($c);
};
$container['session'] = function ($c) {
return new Session($c);
};
$container['plugins'] = function ($c) {
return new Plugins();
};
@@ -88,12 +94,16 @@ class Grav extends Container
$container['taxonomy'] = function ($c) {
return new Taxonomy($c);
};
$container['language'] = function ($c) {
return new Language($c);
};
$container['pages'] = function ($c) {
return new Page\Pages($c);
};
$container['assets'] = function ($c) {
return new Assets();
};
$container['assets'] = new Assets();
$container['page'] = function ($c) {
/** @var Pages $pages */
$pages = $c['pages'];
@@ -106,29 +116,16 @@ class Grav extends Container
$page = $pages->dispatch($path);
// handle redirect if not 'default route' configuration
if ($page && $c['config']->get('system.pages.redirect_default_route') && $page->route() != $path) {
$c->redirectLangSafe($page->route());
}
// if page is not found, try some fallback stuff
if (!$page || !$page->routable()) {
$path_parts = pathinfo($path);
$page = $c['pages']->dispatch($path_parts['dirname'], true);
if ($page) {
$media = $page->media()->all();
$parsed_url = parse_url(urldecode($uri->basename()));
$media_file = $parsed_url['path'];
// if this is a media object, try actions first
if (isset($media[$media_file])) {
$medium = $media[$media_file];
foreach ($uri->query(null, true) as $action => $params) {
if (in_array($action, ImageMedium::$magic_actions)) {
call_user_func_array(array(&$medium, $action), explode(',', $params));
}
}
Utils::download($medium->path(), false);
} else {
Utils::download($page->path() . DIRECTORY_SEPARATOR . $uri->basename(), true);
}
}
// Try fallback URL stuff...
$c->fallbackUrl($page, $path);
// If no page found, fire event
$event = $c->fireEvent('onPageNotFound');
@@ -174,6 +171,7 @@ class Grav extends Container
// Initialize configuration.
$debugger->startTimer('_config', 'Configuration');
$this['config']->init();
$this['session']->init();
$this['uri']->init();
$this['errors']->resetHandlers();
$debugger->init();
@@ -222,7 +220,9 @@ class Grav extends Container
$this->fireEvent('onPagesInitialized');
$debugger->stopTimer('pages');
$debugger->startTimer('pageinit', 'Page Initialized');
$this->fireEvent('onPageInitialized');
$debugger->stopTimer('pageinit');
$debugger->addAssets();
@@ -267,6 +267,25 @@ class Grav extends Container
exit();
}
/**
* Redirect browser to another location taking language into account (preferred)
*
* @param string $route Internal route.
* @param int $code Redirection code (30x)
*/
public function redirectLangSafe($route, $code = 303)
{
/** @var Language $language */
$language = $this['language'];
$config = $this['config'];
if ($language->enabled()) {
return $this->redirect($language->getLanguage() . $route, $code);
} else {
return $this->redirect($route);
}
}
/**
* Returns mime type for the file format.
*
@@ -387,4 +406,46 @@ class Grav extends Container
$this->fireEvent('onShutdown');
}
/**
* This attempts to fine media, other files, and download them
* @param $page
* @param $path
*/
protected function fallbackUrl($page, $path)
{
/** @var Uri $uri */
$uri = $this['uri'];
$path_parts = pathinfo($path);
$page = $this['pages']->dispatch($path_parts['dirname'], true);
if ($page) {
$media = $page->media()->all();
$parsed_url = parse_url(urldecode($uri->basename()));
$media_file = $parsed_url['path'];
// if this is a media object, try actions first
if (isset($media[$media_file])) {
$medium = $media[$media_file];
foreach ($uri->query(null, true) as $action => $params) {
if (in_array($action, ImageMedium::$magic_actions)) {
call_user_func_array(array(&$medium, $action), explode(',', $params));
}
}
Utils::download($medium->path(), false);
}
// has an extension, try to download it...
if (isset($path_parts['extension'])) {
$download = true;
// little work-around to ensure .css and .js files are always sent inline not downloaded
if (in_array($path_parts['extension'], ['.css', '.js'])) {
$download = false;
}
Utils::download($page->path() . DIRECTORY_SEPARATOR . $uri->basename(), $download);
}
}
}
}

View File

@@ -0,0 +1,310 @@
<?php
namespace Grav\Common\Language;
use Grav\Common\Grav;
/**
* Language and translation functionality for Grav
*/
class Language
{
protected $grav;
protected $enabled = true;
protected $languages = [];
protected $page_extensions = [];
protected $fallback_languages = [];
protected $default;
protected $active;
protected $config;
/**
* Constructor
*
* @param \Grav\Common\Grav $grav
*/
public function __construct(Grav $grav)
{
$this->grav = $grav;
$this->config = $grav['config'];
$this->languages = $this->config->get('system.languages.supported', []);
$this->init();
}
/**
* Initialize the default and enabled languages
*/
public function init()
{
$this->default = reset($this->languages);
if (empty($this->languages)) {
$this->enabled = false;
}
}
/**
* Ensure that languages are enabled
*
* @return bool
*/
public function enabled()
{
return $this->enabled;
}
/**
* Gets the array of supported languages
*
* @return array
*/
public function getLanguages()
{
return $this->languages;
}
/**
* Sets the current supported languages manually
*
* @param $langs
*/
public function setLanguages($langs)
{
$this->languages = $langs;
$this->init();
}
/**
* Gets a pipe-separated string of available languages
*
* @return string
*/
public function getAvailable()
{
return implode('|', $this->languages);
}
/**
* Gets language, active if set, else default
*
* @return mixed
*/
public function getLanguage()
{
return $this->active ? $this->active : $this->default;
}
/**
* Gets current default language
*
* @return mixed
*/
public function getDefault()
{
return $this->default;
}
/**
* Sets default language manually
*
* @param $lang
*
* @return bool
*/
public function setDefault($lang)
{
if ($this->validate($lang)) {
$this->default = $lang;
return $lang;
}
return false;
}
/**
* Gets current active language
*
* @return mixed
*/
public function getActive()
{
return $this->active;
}
/**
* Sets active language manually
*
* @param $lang
*
* @return bool
*/
public function setActive($lang)
{
if ($this->validate($lang)) {
$this->active = $lang;
return $lang;
}
return false;
}
/**
* Sets the active language based on the first part of the URL
*
* @param $uri
*
* @return mixed
*/
public function setActiveFromUri($uri)
{
$regex = '/(\/('.$this->getAvailable().')).*/';
// if languages set
if ($this->enabled()) {
// try setting from prefix of URL (/en/blah/blah)
if (preg_match($regex, $uri, $matches)) {
$this->active = $matches[2];
$uri = preg_replace("/\\" . $matches[1] . "/", '', $matches[0], 1);
// store in session if different
if ($this->config->get('system.session.enabled', false)
&& $this->config->get('system.language.session_store_active', true)
&& $this->grav['session']->active_language != $this->active) {
$this->grav['session']->active_language = $this->active;
}
} else {
// try getting from session, else no active
if ($this->config->get('system.session.enabled', false) && $this->config->get('system.language.session_store_active', true)) {
$this->active = $this->grav['session']->active_language ?: null;
} else {
$this->active = null;
}
}
}
return $uri;
}
/**
* Gets an array of valid extensions with active first, then fallback extensions
*
* @return array
*/
public function getFallbackPageExtensions($file_ext = null)
{
if (empty($this->page_extensions)) {
if (empty($file_ext)) {
$file_ext = CONTENT_EXT;
}
if ($this->enabled()) {
$valid_lang_extensions = [];
foreach ($this->languages as $lang) {
$valid_lang_extensions[] = '.'.$lang.$file_ext;
}
if ($this->active) {
$active_extension = '.'.$this->active.$file_ext;
$key = array_search($active_extension, $valid_lang_extensions);
unset($valid_lang_extensions[$key]);
array_unshift($valid_lang_extensions, $active_extension);
}
$this->page_extensions = array_merge($valid_lang_extensions, (array) $file_ext);
} else {
$this->page_extensions = (array) $file_ext;
}
}
return $this->page_extensions;
}
/**
* Gets an array of languages with active first, then fallback languages
*
* @return array
*/
public function getFallbackLanguages()
{
if (empty($this->fallback_languages)) {
if ($this->enabled()) {
$fallback_languages = $this->languages;
if ($this->active) {
$active_extension = $this->active;
$key = array_search($active_extension, $fallback_languages);
unset($fallback_languages[$key]);
array_unshift($fallback_languages, $active_extension);
}
$this->fallback_languages = $fallback_languages;
}
}
return $this->fallback_languages;
}
/**
* Ensures the language is valid and supported
*
* @param $lang
*
* @return bool
*/
public function validate($lang)
{
if (in_array($lang, $this->languages)) {
return true;
}
return false;
}
/**
* Translate a key and possibly arguments into a string using current lang and fallbacks
*
* @param Array $args first argument is the lookup key value
* other arguments can be passed and replaced in the translation with sprintf syntax
* @return string
*/
public function translate(Array $args, $languages = null)
{
$lookup = array_shift($args);
if ($this->enabled() && $lookup) {
if (empty($languages)) {
if ($this->config->get('system.languages.translations.fallback', true)) {
$languages = $this->getFallbackLanguages();
} else {
$languages = (array) $this->getDefault();
}
}
foreach ($this->getFallbackLanguages() as $lang) {
$translation = $this->getTranslation($lang, $lookup);
if ($translation) {
if (count($args) >= 1) {
return vsprintf($translation, $args);
} else {
return $translation;
}
}
}
}
return '<span class="untranslated">'.$lookup.'</span>';
}
/**
* Lookup the translation text for a given lang and key
*
* @param $lang lang code
* @param $key key to lookup with
*
* @return string
*/
public function getTranslation($lang, $key) {
$languages = $this->config->getLanguages();
$translation = $languages->get($lang.'.'.$key, null);
if (is_array($translation)) {
return (string) array_shift($translation);
}
return $translation;
}
}

View File

@@ -0,0 +1,770 @@
<?php
namespace Grav\Common\Language;
/**
* Language and translation functionality for Grav
*/
class LanguageCodes
{
protected static $codes = [
"ab" => [
"name" => "Abkhaz",
"nativeName" => "аҧсуа"
],
"aa" => [
"name" => "Afar",
"nativeName" => "Afaraf"
],
"af" => [
"name" => "Afrikaans",
"nativeName" => "Afrikaans"
],
"ak" => [
"name" => "Akan",
"nativeName" => "Akan"
],
"sq" => [
"name" => "Albanian",
"nativeName" => "Shqip"
],
"am" => [
"name" => "Amharic",
"nativeName" => "አማርኛ"
],
"ar" => [
"name" => "Arabic",
"nativeName" => "العربية"
],
"an" => [
"name" => "Aragonese",
"nativeName" => "Aragonés"
],
"hy" => [
"name" => "Armenian",
"nativeName" => "Հայերեն"
],
"as" => [
"name" => "Assamese",
"nativeName" => "অসমীয়া"
],
"av" => [
"name" => "Avaric",
"nativeName" => "авар мацӀ"
],
"ae" => [
"name" => "Avestan",
"nativeName" => "avesta"
],
"ay" => [
"name" => "Aymara",
"nativeName" => "aymar aru"
],
"az" => [
"name" => "Azerbaijani",
"nativeName" => "azərbaycan dili"
],
"bm" => [
"name" => "Bambara",
"nativeName" => "bamanankan"
],
"ba" => [
"name" => "Bashkir",
"nativeName" => "башҡорт теле"
],
"eu" => [
"name" => "Basque",
"nativeName" => "euskara"
],
"be" => [
"name" => "Belarusian",
"nativeName" => "Беларуская"
],
"bn" => [
"name" => "Bengali",
"nativeName" => "বাংলা"
],
"bh" => [
"name" => "Bihari",
"nativeName" => "भोजपुरी"
],
"bi" => [
"name" => "Bislama",
"nativeName" => "Bislama"
],
"bs" => [
"name" => "Bosnian",
"nativeName" => "bosanski jezik"
],
"br" => [
"name" => "Breton",
"nativeName" => "brezhoneg"
],
"bg" => [
"name" => "Bulgarian",
"nativeName" => "български език"
],
"my" => [
"name" => "Burmese",
"nativeName" => "ဗမာစာ"
],
"ca" => [
"name" => "Catalan",
"nativeName" => "Català"
],
"ch" => [
"name" => "Chamorro",
"nativeName" => "Chamoru"
],
"ce" => [
"name" => "Chechen",
"nativeName" => "нохчийн мотт"
],
"ny" => [
"name" => "Chichewa",
"nativeName" => "chiCheŵa"
],
"zh" => [
"name" => "Chinese",
"nativeName" => "中文"
],
"cv" => [
"name" => "Chuvash",
"nativeName" => "чӑваш чӗлхи"
],
"kw" => [
"name" => "Cornish",
"nativeName" => "Kernewek"
],
"co" => [
"name" => "Corsican",
"nativeName" => "corsu"
],
"cr" => [
"name" => "Cree",
"nativeName" => "ᓀᐦᐃᔭᐍᐏᐣ"
],
"hr" => [
"name" => "Croatian",
"nativeName" => "hrvatski"
],
"cs" => [
"name" => "Czech",
"nativeName" => "česky"
],
"da" => [
"name" => "Danish",
"nativeName" => "dansk"
],
"dv" => [
"name" => "Divehi",
"nativeName" => "ދިވެހި"
],
"nl" => [
"name" => "Dutch",
"nativeName" => "Nederlands"
],
"en" => [
"name" => "English",
"nativeName" => "English"
],
"eo" => [
"name" => "Esperanto",
"nativeName" => "Esperanto"
],
"et" => [
"name" => "Estonian",
"nativeName" => "eesti"
],
"ee" => [
"name" => "Ewe",
"nativeName" => "Eʋegbe"
],
"fo" => [
"name" => "Faroese",
"nativeName" => "føroyskt"
],
"fj" => [
"name" => "Fijian",
"nativeName" => "vosa Vakaviti"
],
"fi" => [
"name" => "Finnish",
"nativeName" => "suomi"
],
"fr" => [
"name" => "French",
"nativeName" => "français"
],
"ff" => [
"name" => "Fula",
"nativeName" => "Fulfulde"
],
"gl" => [
"name" => "Galician",
"nativeName" => "Galego"
],
"ka" => [
"name" => "Georgian",
"nativeName" => "ქართული"
],
"de" => [
"name" => "German",
"nativeName" => "Deutsch"
],
"el" => [
"name" => "Greek",
"nativeName" => "Ελληνικά"
],
"gn" => [
"name" => "Guaraní",
"nativeName" => "Avañeẽ"
],
"gu" => [
"name" => "Gujarati",
"nativeName" => "ગુજરાતી"
],
"ht" => [
"name" => "Haitian",
"nativeName" => "Kreyòl ayisyen"
],
"ha" => [
"name" => "Hausa",
"nativeName" => "هَوُسَ"
],
"he" => [
"name" => "Hebrew",
"nativeName" => "עברית"
],
"hz" => [
"name" => "Herero",
"nativeName" => "Otjiherero"
],
"hi" => [
"name" => "Hindi",
"nativeName" => "हिन्दी"
],
"ho" => [
"name" => "Hiri Motu",
"nativeName" => "Hiri Motu"
],
"hu" => [
"name" => "Hungarian",
"nativeName" => "Magyar"
],
"ia" => [
"name" => "Interlingua",
"nativeName" => "Interlingua"
],
"id" => [
"name" => "Indonesian",
"nativeName" => "Bahasa Indonesia"
],
"ie" => [
"name" => "Interlingue",
"nativeName" => "Interlingue"
],
"ga" => [
"name" => "Irish",
"nativeName" => "Gaeilge"
],
"ig" => [
"name" => "Igbo",
"nativeName" => "Asụsụ Igbo"
],
"ik" => [
"name" => "Inupiaq",
"nativeName" => "Iñupiaq"
],
"io" => [
"name" => "Ido",
"nativeName" => "Ido"
],
"is" => [
"name" => "Icelandic",
"nativeName" => "Íslenska"
],
"it" => [
"name" => "Italian",
"nativeName" => "Italiano"
],
"iu" => [
"name" => "Inuktitut",
"nativeName" => "ᐃᓄᒃᑎᑐᑦ"
],
"ja" => [
"name" => "Japanese",
"nativeName" => "日本語"
],
"jv" => [
"name" => "Javanese",
"nativeName" => "basa Jawa"
],
"kl" => [
"name" => "Kalaallisut",
"nativeName" => "kalaallisut"
],
"kn" => [
"name" => "Kannada",
"nativeName" => "ಕನ್ನಡ"
],
"kr" => [
"name" => "Kanuri",
"nativeName" => "Kanuri"
],
"ks" => [
"name" => "Kashmiri",
"nativeName" => "कश्मीरी"
],
"kk" => [
"name" => "Kazakh",
"nativeName" => "Қазақ тілі"
],
"km" => [
"name" => "Khmer",
"nativeName" => "ភាសាខ្មែរ"
],
"ki" => [
"name" => "Kikuyu",
"nativeName" => "Gĩkũyũ"
],
"rw" => [
"name" => "Kinyarwanda",
"nativeName" => "Ikinyarwanda"
],
"ky" => [
"name" => "Kirghiz",
"nativeName" => "кыргыз тили"
],
"kv" => [
"name" => "Komi",
"nativeName" => "коми кыв"
],
"kg" => [
"name" => "Kongo",
"nativeName" => "KiKongo"
],
"ko" => [
"name" => "Korean",
"nativeName" => "한국어"
],
"ku" => [
"name" => "Kurdish",
"nativeName" => "كوردی‎"
],
"kj" => [
"name" => "Kwanyama",
"nativeName" => "Kuanyama"
],
"la" => [
"name" => "Latin",
"nativeName" => "latine"
],
"lb" => [
"name" => "Luxembourgish",
"nativeName" => "Lëtzebuergesch"
],
"lg" => [
"name" => "Luganda",
"nativeName" => "Luganda"
],
"li" => [
"name" => "Limburgish",
"nativeName" => "Limburgs"
],
"ln" => [
"name" => "Lingala",
"nativeName" => "Lingála"
],
"lo" => [
"name" => "Lao",
"nativeName" => "ພາສາລາວ"
],
"lt" => [
"name" => "Lithuanian",
"nativeName" => "lietuvių kalba"
],
"lu" => [
"name" => "Luba-Katanga",
"nativeName" => "Luba-Katanga"
],
"lv" => [
"name" => "Latvian",
"nativeName" => "latviešu valoda"
],
"gv" => [
"name" => "Manx",
"nativeName" => "Gaelg"
],
"mk" => [
"name" => "Macedonian",
"nativeName" => "македонски јазик"
],
"mg" => [
"name" => "Malagasy",
"nativeName" => "Malagasy fiteny"
],
"ms" => [
"name" => "Malay",
"nativeName" => "بهاس ملايو‎"
],
"ml" => [
"name" => "Malayalam",
"nativeName" => "മലയാളം"
],
"mt" => [
"name" => "Maltese",
"nativeName" => "Malti"
],
"mi" => [
"name" => "Māori",
"nativeName" => "te reo Māori"
],
"mr" => [
"name" => "Marathi",
"nativeName" => "मराठी"
],
"mh" => [
"name" => "Marshallese",
"nativeName" => "Kajin M̧ajeļ"
],
"mn" => [
"name" => "Mongolian",
"nativeName" => "монгол"
],
"na" => [
"name" => "Nauru",
"nativeName" => "Ekakairũ Naoero"
],
"nv" => [
"name" => "Navajo",
"nativeName" => "Diné bizaad"
],
"nb" => [
"name" => "Norwegian Bokmål",
"nativeName" => "Norsk bokmål"
],
"nd" => [
"name" => "North Ndebele",
"nativeName" => "isiNdebele"
],
"ne" => [
"name" => "Nepali",
"nativeName" => "नेपाली"
],
"ng" => [
"name" => "Ndonga",
"nativeName" => "Owambo"
],
"nn" => [
"name" => "Norwegian Nynorsk",
"nativeName" => "Norsk nynorsk"
],
"no" => [
"name" => "Norwegian",
"nativeName" => "Norsk"
],
"ii" => [
"name" => "Nuosu",
"nativeName" => "ꆈꌠ꒿ Nuosuhxop"
],
"nr" => [
"name" => "South Ndebele",
"nativeName" => "isiNdebele"
],
"oc" => [
"name" => "Occitan",
"nativeName" => "Occitan"
],
"oj" => [
"name" => "Ojibwe, Ojibwa",
"nativeName" => "ᐊᓂᔑᓈᐯᒧᐎᓐ"
],
"cu" => [
"name" => "Church Slavic",
"nativeName" => "ѩзыкъ словѣньскъ"
],
"om" => [
"name" => "Oromo",
"nativeName" => "Afaan Oromoo"
],
"or" => [
"name" => "Oriya",
"nativeName" => "ଓଡ଼ିଆ"
],
"os" => [
"name" => "Ossetian",
"nativeName" => "ирон æвзаг"
],
"pa" => [
"name" => "Panjabi",
"nativeName" => "ਪੰਜਾਬੀ"
],
"pi" => [
"name" => "Pāli",
"nativeName" => "पाऴि"
],
"fa" => [
"name" => "Persian",
"nativeName" => "فارسی"
],
"pl" => [
"name" => "Polish",
"nativeName" => "polski"
],
"ps" => [
"name" => "Pashto",
"nativeName" => "پښتو"
],
"pt" => [
"name" => "Portuguese",
"nativeName" => "Português"
],
"qu" => [
"name" => "Quechua",
"nativeName" => "Runa Simi"
],
"rm" => [
"name" => "Romansh",
"nativeName" => "rumantsch grischun"
],
"rn" => [
"name" => "Kirundi",
"nativeName" => "kiRundi"
],
"ro" => [
"name" => "Romanian",
"nativeName" => "română"
],
"ru" => [
"name" => "Russian",
"nativeName" => "русский язык"
],
"sa" => [
"name" => "Sanskrit",
"nativeName" => "संस्कृतम्"
],
"sc" => [
"name" => "Sardinian",
"nativeName" => "sardu"
],
"sd" => [
"name" => "Sindhi",
"nativeName" => "सिन्धी"
],
"se" => [
"name" => "Northern Sami",
"nativeName" => "Davvisámegiella"
],
"sm" => [
"name" => "Samoan",
"nativeName" => "gagana faa Samoa"
],
"sg" => [
"name" => "Sango",
"nativeName" => "yângâ tî sängö"
],
"sr" => [
"name" => "Serbian",
"nativeName" => "српски језик"
],
"gd" => [
"name" => "Scottish Gaelic",
"nativeName" => "Gàidhlig"
],
"sn" => [
"name" => "Shona",
"nativeName" => "chiShona"
],
"si" => [
"name" => "Sinhala",
"nativeName" => "සිංහල"
],
"sk" => [
"name" => "Slovak",
"nativeName" => "slovenčina"
],
"sl" => [
"name" => "Slovene",
"nativeName" => "slovenščina"
],
"so" => [
"name" => "Somali",
"nativeName" => "Soomaaliga"
],
"st" => [
"name" => "Southern Sotho",
"nativeName" => "Sesotho"
],
"es" => [
"name" => "Spanish",
"nativeName" => "español"
],
"su" => [
"name" => "Sundanese",
"nativeName" => "Basa Sunda"
],
"sw" => [
"name" => "Swahili",
"nativeName" => "Kiswahili"
],
"ss" => [
"name" => "Swati",
"nativeName" => "SiSwati"
],
"sv" => [
"name" => "Swedish",
"nativeName" => "svenska"
],
"ta" => [
"name" => "Tamil",
"nativeName" => "தமிழ்"
],
"te" => [
"name" => "Telugu",
"nativeName" => "తెలుగు"
],
"tg" => [
"name" => "Tajik",
"nativeName" => "тоҷикӣ"
],
"th" => [
"name" => "Thai",
"nativeName" => "ไทย"
],
"ti" => [
"name" => "Tigrinya",
"nativeName" => "ትግርኛ"
],
"bo" => [
"name" => "Tibetan",
"nativeName" => "བོད་ཡིག"
],
"tk" => [
"name" => "Turkmen",
"nativeName" => "Türkmen"
],
"tl" => [
"name" => "Tagalog",
"nativeName" => "Wikang Tagalog"
],
"tn" => [
"name" => "Tswana",
"nativeName" => "Setswana"
],
"to" => [
"name" => "Tonga",
"nativeName" => "faka Tonga"
],
"tr" => [
"name" => "Turkish",
"nativeName" => "Türkçe"
],
"ts" => [
"name" => "Tsonga",
"nativeName" => "Xitsonga"
],
"tt" => [
"name" => "Tatar",
"nativeName" => "татарча"
],
"tw" => [
"name" => "Twi",
"nativeName" => "Twi"
],
"ty" => [
"name" => "Tahitian",
"nativeName" => "Reo Tahiti"
],
"ug" => [
"name" => "Uighur",
"nativeName" => "Uyƣurqə"
],
"uk" => [
"name" => "Ukrainian",
"nativeName" => "українська"
],
"ur" => [
"name" => "Urdu",
"nativeName" => "اردو"
],
"uz" => [
"name" => "Uzbek",
"nativeName" => "zbek"
],
"ve" => [
"name" => "Venda",
"nativeName" => "Tshivenḓa"
],
"vi" => [
"name" => "Vietnamese",
"nativeName" => "Tiếng Việt"
],
"vo" => [
"name" => "Volapük",
"nativeName" => "Volapük"
],
"wa" => [
"name" => "Walloon",
"nativeName" => "Walon"
],
"cy" => [
"name" => "Welsh",
"nativeName" => "Cymraeg"
],
"wo" => [
"name" => "Wolof",
"nativeName" => "Wollof"
],
"fy" => [
"name" => "Western Frisian",
"nativeName" => "Frysk"
],
"xh" => [
"name" => "Xhosa",
"nativeName" => "isiXhosa"
],
"yi" => [
"name" => "Yiddish",
"nativeName" => "ייִדיש"
],
"yo" => [
"name" => "Yoruba",
"nativeName" => "Yorùbá"
],
"za" => [
"name" => "Zhuang",
"nativeName" => "Saɯ cueŋƅ"
]
];
public static function getName($code)
{
return static::get($code, 'name');
}
public static function getNativeName($code)
{
return static::get($code, 'nativeName');
}
public static function getNames(array $keys)
{
$results = [];
foreach ($keys as $key) {
if (isset(static::$codes[$key])) {
$results[$key] = static::$codes[$key];
}
}
return $results;
}
protected static function get($code, $type)
{
if (isset(static::$codes[$code][$type])) {
return static::$codes[$code][$type];
} else {
return false;
}
}
}

View File

@@ -23,7 +23,7 @@ trait ParsedownGravTrait
protected $twig_link_regex = '/\!*\[(?:.*)\]\((\{([\{%#])\s*(.*?)\s*(?:\2|\})\})\)/';
/**
* Initialiazation function to setup key variables needed by the MarkdownGravLinkTrait
* Initialization function to setup key variables needed by the MarkdownGravLinkTrait
*
* @param $page
* @param $defaults

View File

@@ -216,7 +216,7 @@ class ImageMedium extends Medium
}
/**
* Turn the current Medium inta a Link with lightbox enabled
* Turn the current Medium into a Link with lightbox enabled
*
* @param int $width
* @param int $height

View File

@@ -60,7 +60,7 @@ class Medium extends Data implements RenderableInterface
{
parent::__construct($items, $blueprint);
if (self::getGrav()['config']->get('media.enable_media_timestamp', true)) {
if (self::getGrav()['config']->get('system.media.enable_media_timestamp', true)) {
$this->querystring('&' . self::getGrav()['cache']->getKey());
}
@@ -339,7 +339,7 @@ class Medium extends Data implements RenderableInterface
}
/**
* Turn the current Medium inta a Link with lightbox enabled
* Turn the current Medium into a Link with lightbox enabled
*
* @param int $width
* @param int $height

View File

@@ -90,7 +90,7 @@ class ThumbnailImageMedium extends ImageMedium
}
/**
* Turn the current Medium inta a Link with lightbox enabled
* Turn the current Medium into a Link with lightbox enabled
*
* @param int $width
* @param int $height

View File

@@ -34,16 +34,9 @@ class Page
* @var string Filename. Leave as null if page is folder.
*/
protected $name;
/**
* @var string Folder name.
*/
protected $folder;
/**
* @var string Path to the folder. Add $this->folder to get full path.
*/
protected $path;
protected $extension;
protected $parent;
protected $template;
@@ -54,6 +47,9 @@ class Page
protected $unpublish_date;
protected $slug;
protected $route;
protected $raw_route;
protected $url;
protected $routes;
protected $routable;
protected $modified;
protected $id;
@@ -120,7 +116,7 @@ class Page
$this->header();
$this->date();
$this->metadata();
$this->slug();
$this->url();
$this->visible();
$this->modularTwig($this->slug[0] == '_');
@@ -142,6 +138,7 @@ class Page
}
}
$this->published();
$this->extension();
}
/**
@@ -226,6 +223,9 @@ class Page
if (isset($this->header->slug)) {
$this->slug = trim($this->header->slug);
}
if (isset($this->header->routes)) {
$this->routes = (array)($this->header->routes);
}
if (isset($this->header->title)) {
$this->title = trim($this->header->title);
}
@@ -617,6 +617,22 @@ class Page
return null;
}
/**
* Get page extension
*
* @param $var
*
* @return mixed
*/
public function extension($var = null)
{
if ($var !== null) {
$this->extension = $var;
}
return $this->extension;
}
/**
* Save page if there's a file assigned to it.
* @param bool $reorder Internal use.
@@ -820,7 +836,7 @@ class Page
$this->template = $var;
}
if (empty($this->template)) {
$this->template = ($this->modular() ? 'modular/' : '') . str_replace(CONTENT_EXT, '', $this->name());
$this->template = ($this->modular() ? 'modular/' : '') . str_replace($this->extension, '', $this->name());
}
return $this->template;
}
@@ -1050,16 +1066,13 @@ class Page
{
if ($var !== null) {
$this->slug = $var;
$baseRoute = $this->parent ? (string) $this->parent()->route() : null;
$this->route = isset($baseRoute) ? $baseRoute . '/'. $this->slug : null;
}
if (empty($this->slug)) {
$regex = '/^[0-9]+\./u';
$this->slug = preg_replace($regex, '', $this->folder);
$baseRoute = $this->parent ? (string) $this->parent()->route() : null;
$this->route = isset($baseRoute) ? $baseRoute . '/'. $this->slug : null;
}
return $this->slug;
}
@@ -1104,19 +1117,36 @@ class Page
/**
* Gets the url for the Page.
*
* @param bool $include_host Defaults false, but true would include http://yourhost.com
* @return string The url.
* @param bool $include_host Defaults false, but true would include http://yourhost.com
* @param bool $canonical true to return the canonical URL
*
* @return string The url.
*/
public function url($include_host = false)
public function url($include_host = false, $canonical = false)
{
/** @var Pages $pages */
$pages = self::getGrav()['pages'];
/** @var Language $language */
$language = self::getGrav()['language'];
// get pre-route
$pre_route = $language->enabled() && $language->getActive() ? '/'.$language->getActive() : '';
// get canonical route if requested
if ($canonical) {
$route = $pre_route . $this->routeCanonical();
} else {
$route = $pre_route . $this->route();
}
/** @var Uri $uri */
$uri = self::getGrav()['uri'];
$rootUrl = $uri->rootUrl($include_host) . $pages->base();
$url = $rootUrl.'/'.trim($this->route(), '/');
$url = $rootUrl.'/'.trim($route, '/');
// trim trailing / if not root
if ($url !== '/') {
@@ -1127,7 +1157,8 @@ class Page
}
/**
* Gets the route for the page based on the parents route and the current Page's slug.
* Gets the route for the page based on the route headers if available, else from
* the parents route and the current Page's slug.
*
* @param string $var Set new default route.
*
@@ -1138,9 +1169,81 @@ class Page
if ($var !== null) {
$this->route = $var;
}
if (empty($this->route)) {
// calculate route based on parent slugs
$baseRoute = $this->parent ? (string) $this->parent()->route() : null;
$this->route = isset($baseRoute) ? $baseRoute . '/'. $this->slug() : null;
if (!empty($this->routes) && isset($this->routes['default'])) {
$this->routes['aliases'][] = $this->route;
$this->route = $this->routes['default'];
return $this->route;
}
}
return $this->route;
}
public function rawRoute($var = null)
{
if ($var !== null) {
$this->raw_route = $var;
}
if (empty($this->raw_route)) {
$baseRoute = $this->parent ? (string) $this->parent()->rawRoute() : null;
$regex = '/^[0-9]+\./u';
$slug = preg_replace($regex, '', $this->folder);
$this->raw_route = isset($baseRoute) ? $baseRoute . '/'. $slug : null;
}
return $this->raw_route;
}
/**
* Gets the route aliases for the page based on page headers.
*
* @param array $var list of route aliases
*
* @return array The route aliases for the Page.
*/
public function routeAliases($var = null)
{
if ($var !== null) {
$this->routes['aliases'] = (array) $var;
}
if (!empty($this->routes) && isset($this->routes['aliases'])) {
return $this->routes['aliases'];
} else {
return [];
}
}
/**
* Gets the canonical route for this page if its set. If provided it will use
* that value, else if it's `true` it will use the default route.
*
* @param null $var
*
* @return bool|string
*/
public function routeCanonical($var = null)
{
if ($var !== null) {
$this->routes['canonical'] = (array)$var;
}
if (!empty($this->routes) && isset($this->routes['canonical'])) {
return $this->routes['canonical'];
}
return $this->route();
}
/**
* Gets and sets the identifier for this Page object.
*
@@ -1500,10 +1603,14 @@ class Page
*/
public function active()
{
/** @var Uri $uri */
$uri = self::getGrav()['uri'];
if ($this->url() == $uri->url()) {
return true;
$uri_path = self::getGrav()['uri']->path();
$routes = self::getGrav()['pages']->routes();
if (isset($routes[$uri_path])) {
if ($routes[$uri_path] == $this->path()) {
return true;
}
}
return false;
}
@@ -1516,20 +1623,18 @@ class Page
*/
public function activeChild()
{
/** @var Uri $uri */
$uri = self::getGrav()['uri'];
$config = self::getGrav()['config'];
$pages = self::getGrav()['pages'];
$uri_path = $uri->path();
$routes = self::getGrav()['pages']->routes();
// Special check when item is home
if ($this->home()) {
$paths = $uri->paths();
$home = ltrim($config->get('system.home.alias'), '/');
if (isset($paths[0]) && $paths[0] == $home) {
return true;
}
} else {
if (strpos($uri->url(), $this->url()) === 0) {
return true;
if (isset($routes[$uri_path])) {
$child_page = $pages->dispatch($uri->route())->parent();
while (!$child_page->root()) {
if ($this->path() == $child_page->path()) {
return true;
}
$child_page = $child_page->parent();
}
}
@@ -1734,7 +1839,6 @@ class Page
return $results;
}
/**
* Returns whether or not this Page object has a .md file associated with it or if its just a directory.
*

View File

@@ -6,6 +6,7 @@ use Grav\Common\Config\Config;
use Grav\Common\Utils;
use Grav\Common\Cache;
use Grav\Common\Taxonomy;
use Grav\Common\Language;
use Grav\Common\Data\Blueprint;
use Grav\Common\Data\Blueprints;
use Grav\Common\Filesystem\Folder;
@@ -66,6 +67,8 @@ class Pages
*/
static protected $types;
static protected $home_route;
/**
* Constructor
*
@@ -276,7 +279,7 @@ class Pages
try {
$found = preg_replace($pattern, $replace, $url);
if ($found != $url) {
$this->grav->redirect($found);
$this->grav->redirectLangSafe($found);
}
} catch (ErrorException $e) {
$this->grav['log']->error('site.redirects: '. $pattern . '-> ' . $e->getMessage());
@@ -451,6 +454,48 @@ class Pages
return $pages->getList();
}
/**
* Get's the home route
*
* @return string
*/
public static function getHomeRoute()
{
if (empty(self::$home)) {
$grav = Grav::instance();
/** @var Config $config */
$config = $grav['config'];
/** @var Language $language */
$language = $grav['language'];
$home = $config->get('system.home.alias');
if ($language->enabled()) {
$home_aliases = $config->get('system.home.aliases');
if ($home_aliases) {
$active = $language->getActive();
$default = $language->getDefault();
try {
if ($active) {
$home = $home_aliases[$active];
} else {
$home = $home_aliases[$default];
}
} catch (ErrorException $e) {
$home = $home_aliases[$default];
}
}
}
self::$home_route = trim($home, '/');
}
return self::$home_route;
}
/**
* Builds pages.
*
@@ -463,6 +508,9 @@ class Pages
/** @var Config $config */
$config = $this->grav['config'];
/** @var Language $language */
$language = $this->grav['language'];
/** @var UniformResourceLocator $locator */
$locator = $this->grav['locator'];
$pagesDir = $locator->findResource('page://');
@@ -486,7 +534,7 @@ class Pages
$last_modified = Folder::lastModifiedFile($pagesDir);
}
$page_cache_id = md5(USER_DIR.$last_modified.$config->checksum());
$page_cache_id = md5(USER_DIR.$last_modified.$language->getActive().$config->checksum());
list($this->instances, $this->routes, $this->children, $taxonomy_map, $this->sort) = $cache->fetch($page_cache_id);
if (!$this->instances) {
@@ -523,15 +571,21 @@ class Pages
protected function recurse($directory, Page &$parent = null)
{
$directory = rtrim($directory, DS);
$iterator = new \DirectoryIterator($directory);
$page = new Page;
/** @var Config $config */
$config = $this->grav['config'];
// Fire event for memory and time consuming plugins...
if ($parent === null && $config->get('system.pages.events.page')) {
$this->grav->fireEvent('onBuildPagesInitialized');
/** @var Language $language */
$language = $this->grav['language'];
// stuff to do at root page
if ($parent === null) {
// Fire event for memory and time consuming plugins...
if ($config->get('system.pages.events.page')) {
$this->grav->fireEvent('onBuildPagesInitialized');
}
}
$page->path($directory);
@@ -552,17 +606,39 @@ class Pages
throw new \RuntimeException('Fatal error when creating page instances.');
}
$content_exists = false;
$pages_found = glob($directory.'/*'.CONTENT_EXT);
$page_extensions = $language->getFallbackPageExtensions();
if ($pages_found) {
foreach ($page_extensions as $extension) {
foreach ($pages_found as $found) {
if (preg_match('/^.*\/[0-9A-Za-z\-\_]+('.$extension.')$/', $found)) {
$page_found = $found;
$page_extension = $extension;
break 2;
}
}
}
}
if (!empty($page_found)) {
$file = new \SplFileInfo($page_found);
$page->init($file);
$page->extension($page_extension);
$content_exists = true;
if ($config->get('system.pages.events.page')) {
$this->grav->fireEvent('onPageProcessed', new Event(['page' => $page]));
}
}
// set current modified of page
$last_modified = $page->modified();
// flat for content availability
$content_exists = false;
/** @var \DirectoryIterator $file */
foreach ($iterator as $file) {
if ($file->isDot()) {
continue;
}
foreach (new \FilesystemIterator($directory) as $file) {
$name = $file->getFilename();
@@ -571,15 +647,6 @@ class Pages
if ($file->getBasename() !== '.DS_Store' && ($modified = $file->getMTime()) > $last_modified) {
$last_modified = $modified;
}
if (preg_match('/^[^.].*'.CONTENT_EXT.'$/', $name)) {
$page->init($file);
$content_exists = true;
if ($config->get('system.pages.events.page')) {
$this->grav->fireEvent('onPageProcessed', new Event(['page' => $page]));
}
}
} elseif ($file->isDir()) {
if (!$page->path()) {
$page->path($file->getPath());
@@ -623,29 +690,45 @@ class Pages
/** @var $taxonomy Taxonomy */
$taxonomy = $this->grav['taxonomy'];
// Get the home route
$home = self::getHomeRoute();
// Build routes and taxonomy map.
/** @var $page Page */
foreach ($this->instances as $page) {
$parent = $page->parent();
if ($parent) {
$route = rtrim($parent->route(), '/') . '/' . $page->slug();
$this->routes[$route] = $page->path();
$page->route($route);
}
if (!empty($route)) {
if (!$page->root()) {
// process taxonomy
$taxonomy->addTaxonomy($page);
} else {
$page->routable(false);
$route = $page->route();
$raw_route = $page->rawRoute();
$page_path = $page->path();
// add regular route
$this->routes[$route] = $page_path;
// add raw route
if ($raw_route != $route) {
$this->routes[$raw_route] = $page_path;
}
// add canonical route
$route_canonical = $page->routeCanonical();
if ($route_canonical && ($route !== $route_canonical)) {
$this->routes[$route_canonical] = $page_path;
}
// add aliases to routes list if they are provided
$route_aliases = $page->routeAliases();
if ($route_aliases) {
foreach ($route_aliases as $alias) {
$this->routes[$alias] = $page_path;
}
}
}
}
/** @var Config $config */
$config = $this->grav['config'];
// Alias and set default route to home page.
$home = trim($config->get('system.home.alias'), '/');
if ($home && isset($this->routes['/' . $home])) {
$this->routes['/'] = $this->routes['/' . $home];
$this->get($this->routes['/' . $home])->route('/');

View File

@@ -0,0 +1,35 @@
<?php
namespace Grav\Common;
/**
* Wrapper for Session
*/
class Session extends \RocketTheme\Toolbox\Session\Session
{
protected $grav;
protected $session;
public function __construct(Grav $grav)
{
$this->grav = $grav;
}
public function init()
{
/** @var Uri $uri */
$uri = $this->grav['uri'];
$config = $this->grav['config'];
if ($config->get('system.session.enabled')) {
// Define session service.
parent::__construct(
$config->get('system.session.timeout', 1800),
$config->get('system.session.path', '/' . ltrim($uri->rootUrl(false), '/'))
);
$site_identifier = $config->get('site.title', 'unkown');
$this->setName($config->get('system.session.name', 'grav_site') . '_' . substr(md5($site_identifier), 0, 7));
$this->start();
}
}
}

View File

@@ -52,13 +52,13 @@ class Taxonomy
$page_taxonomy = $page->taxonomy();
}
if (!$page->published()) {
if (!$page->published() || empty($page_taxonomy)) {
return;
}
/** @var Config $config */
$config = $this->grav['config'];
if ($config->get('site.taxonomies') && count($page_taxonomy) > 0) {
if ($config->get('site.taxonomies')) {
foreach ((array) $config->get('site.taxonomies') as $taxonomy) {
if (isset($page_taxonomy[$taxonomy])) {
foreach ((array) $page_taxonomy[$taxonomy] as $item) {
@@ -76,7 +76,7 @@ class Taxonomy
*
* @param array $taxonomies taxonomies to search, eg ['tag'=>['animal','cat']]
* @param string $operator can be 'or' or 'and' (defaults to 'or')
* @return Colleciton Collection object set to contain matches found in the taxonomy map
* @return Collection Collection object set to contain matches found in the taxonomy map
*/
public function findTaxonomy($taxonomies, $operator = 'and')
{

View File

@@ -221,7 +221,13 @@ class Themes extends Iterator
protected function loadConfiguration($name, Config $config)
{
$themeConfig = CompiledYamlFile::instance("themes://{$name}/{$name}" . YAML_EXT)->content();
$config->joinDefaults("themes.{$name}", $themeConfig);
if ($this->grav['language']->enabled()) {
$languages = CompiledYamlFile::instance("themes://{$name}/languages". YAML_EXT)->content();
if ($languages) {
$config->getLanguages()->mergeRecursive($languages);
}
}
}
}

View File

@@ -57,6 +57,7 @@ class Twig
public function __construct(Grav $grav)
{
$this->grav = $grav;
$this->twig_paths = [];
}
/**
@@ -72,7 +73,23 @@ class Twig
$locator = $this->grav['locator'];
$debugger = $this->grav['debugger'];
$this->twig_paths = $locator->findResources('theme://templates');
/** @var Language $language */
$language = $this->grav['language'];
$active_language = $language->getActive();
$language_append = $active_language ? '/'.$active_language : '';
// handle language templates if available
if ($language->enabled()) {
$lang_templates = $locator->findResource('theme://templates/'.$active_language);
if ($lang_templates) {
$this->twig_paths[] = $lang_templates;
}
}
$this->twig_paths = array_merge($this->twig_paths, $locator->findResources('theme://templates'));
$this->grav->fireEvent('onTwigTemplatePaths');
$this->loader = new \Twig_Loader_Filesystem($this->twig_paths);
@@ -131,9 +148,9 @@ class Twig
'config' => $config,
'uri' => $this->grav['uri'],
'base_dir' => rtrim(ROOT_DIR, '/'),
'base_url' => $this->grav['base_url'],
'base_url_absolute' => $this->grav['base_url_absolute'],
'base_url_relative' => $this->grav['base_url_relative'],
'base_url' => $this->grav['base_url'] . $language_append,
'base_url_absolute' => $this->grav['base_url_absolute'] . $language_append,
'base_url_relative' => $this->grav['base_url_relative'] . $language_append,
'theme_dir' => $locator->findResource('theme://'),
'theme_url' => $this->grav['base_url'] .'/'. $locator->findResource('theme://', false),
'site' => $config->get('site'),

View File

@@ -16,11 +16,13 @@ class TwigExtension extends \Twig_Extension
{
protected $grav;
protected $debugger;
protected $config;
public function __construct()
{
$this->grav = Grav::instance();
$this->debugger = isset($this->grav['debugger']) ? $this->grav['debugger'] : null;
$this->config = $this->grav['config'];
}
/**
@@ -54,7 +56,8 @@ class TwigExtension extends \Twig_Extension
new \Twig_SimpleFilter('absolute_url', [$this, 'absoluteUrlFilter']),
new \Twig_SimpleFilter('markdown', [$this, 'markdownFilter']),
new \Twig_SimpleFilter('starts_with', [$this, 'startsWithFilter']),
new \Twig_SimpleFilter('ends_with', [$this, 'endsWithFilter'])
new \Twig_SimpleFilter('ends_with', [$this, 'endsWithFilter']),
new \Twig_SimpleFilter('t', [$this, 'translateFilter'])
];
}
@@ -72,6 +75,7 @@ class TwigExtension extends \Twig_Extension
new \Twig_SimpleFunction('debug', [$this, 'dump'], ['needs_context' => true, 'needs_environment' => true]),
new \Twig_SimpleFunction('gist', [$this, 'gistFunc']),
new \Twig_simpleFunction('random_string', [$this, 'randomStringFunc']),
new \Twig_simpleFunction('t', [$this, 'translateFunc'])
];
}
@@ -108,7 +112,7 @@ class TwigExtension extends \Twig_Extension
* Truncate content by a limit.
*
* @param string $string
* @param int $limit Nax number of characters.
* @param int $limit Max number of characters.
* @param string $break Break point.
* @param string $pad Appended padding to the end of the string.
* @return string
@@ -335,7 +339,7 @@ class TwigExtension extends \Twig_Extension
public function markdownFilter($string)
{
$page = $this->grav['page'];
$defaults = $this->grav['config']->get('system.pages.markdown');
$defaults = $this->$config->get('system.pages.markdown');
// Initialize the preferred variant of Parsedown
if ($defaults['extra']) {
@@ -349,14 +353,19 @@ class TwigExtension extends \Twig_Extension
return $string;
}
public function startsWithFilter($needle, $haystack)
public function startsWithFilter($haystack, $needle)
{
return Utils::startsWith($needle, $haystack);
return Utils::startsWith($haystack, $needle);
}
public function endsWithFilter($needle, $haystack)
public function endsWithFilter($haystack, $needle)
{
return Utils::endsWith($needle, $haystack);
return Utils::endsWith($haystack, $needle);
}
public function translateFilter()
{
return $this->grav['language']->translate(func_get_args());
}
/**
@@ -459,4 +468,9 @@ class TwigExtension extends \Twig_Extension
{
return Utils::generateRandomString($count);
}
public function translateFunc()
{
return $this->grav['language']->translate(func_get_args());
}
}

View File

@@ -1,6 +1,9 @@
<?php
namespace Grav\Common;
use Grav\Common\Page\Page;
use Grav\Common\Page\Pages;
/**
* The URI object provides information about the current URL
*
@@ -72,7 +75,10 @@ class Uri
*/
public function init()
{
$config = Grav::instance()['config'];
$grav = Grav::instance();
$config = $grav['config'];
$language = $grav['language'];
// resets
$this->paths = [];
@@ -86,6 +92,19 @@ class Uri
// process params
$uri = $this->processParams($uri, $config->get('system.param_sep'));
// set active language
$uri = $language->setActiveFromUri($uri);
// redirect to language specific homepage if configured to do so
if ($uri == '/' && $language->enabled() && !$language->getActive()) {
if ($config->get('system.languages.home_redirect.include_route', true)) {
$prefix = $config->get('system.languages.home_redirect.include_lang', true) ? $language->getDefault() . '/' : '';
$grav->redirect($prefix . Pages::getHomeRoute());
} elseif ($config->get('system.languages.home_redirect.include_lang', true)) {
$grav->redirect($language->getDefault() . '/');
}
}
// split the URL and params
$bits = parse_url($uri);
@@ -384,7 +403,7 @@ class Uri
}
/**
* Retrun the IP address of the current user
* Return the IP address of the current user
*
* @return string ip address
*/
@@ -407,6 +426,7 @@ class Uri
return $ipaddress;
}
/**
* Is this an external URL? if it starts with `http` then yes, else false
*
@@ -450,7 +470,7 @@ class Uri
*
* @return string the more friendly formatted url
*/
public static function convertUrl($page, $markdown_url)
public static function convertUrl(Page $page, $markdown_url)
{
$grav = Grav::instance();

View File

@@ -90,7 +90,7 @@ class User extends Data
}
/**
* Checks user authorisation to the action.
* Checks user authorization to the action.
*
* @param string $action
* @return bool

View File

@@ -19,6 +19,16 @@ abstract class Utils
*/
public static function startsWith($haystack, $needle)
{
if (is_array($needle)) {
$status = false;
foreach ($needle as $each_needle) {
$status = $status || ($each_needle === '' || strpos($haystack, $each_needle) === 0);
if ($status) {
return $status;
}
}
return $status;
}
return $needle === '' || strpos($haystack, $needle) === 0;
}
@@ -29,6 +39,16 @@ abstract class Utils
*/
public static function endsWith($haystack, $needle)
{
if (is_array($needle)) {
$status = false;
foreach ($needle as $each_needle) {
$status = $status || ($each_needle === '' || substr($haystack, -strlen($each_needle)) === $each_needle);
if ($status) {
return $status;
}
}
return $status;
}
return $needle === '' || substr($haystack, -strlen($needle)) === $needle;
}
@@ -72,7 +92,7 @@ abstract class Utils
if (strlen(preg_replace('/<.*?>/', '', $text)) <= $length) {
return $text;
}
// splits all html-tags to scanable lines
// splits all html-tags to scannable lines
preg_match_all('/(<.+?>)?([^<>]*)/s', $text, $lines, PREG_SET_ORDER);
$total_length = strlen($ending);
$truncate = '';
@@ -137,7 +157,7 @@ abstract class Utils
}
// if the words shouldn't be cut in the middle...
if (!$exact) {
// ...search the last occurance of a space...
// ...search the last occurrence of a space...
$spacepos = strrpos($truncate, ' ');
if (isset($spacepos)) {
// ...and cut the text in this position

View File

@@ -281,7 +281,7 @@ class SandboxCommand extends Command
private function perms()
{
$this->output->writeln('');
$this->output->writeln('<comment>Permisions Initializing</comment>');
$this->output->writeln('<comment>Permissions Initializing</comment>');
$dir_perms = 0755;

View File

@@ -96,7 +96,7 @@ class SelfupgradeCommand extends Command
exit;
}
// not used but pre-loaded just in case!
// not used but preloaded just in case!
new ArrayInput([]);
$questionHelper = $this->getHelper('question');

View File

@@ -142,8 +142,6 @@ class UninstallCommand extends Command
*/
private function uninstallPackage($slug, $package)
{
$locator = self::getGrav()['locator'];
$path = self::getGrav()['locator']->findResource($package->package_type . '://' .$slug);
Installer::uninstall($path);
$errorCode = Installer::lastErrorCode();

View File

@@ -160,7 +160,7 @@ class UpdateCommand extends Command
$commandExec = $installCommand->run($args, $this->output);
if ($commandExec != 0) {
$this->output->writeln("<red>Error:</red> An error occured while trying to install the extensions");
$this->output->writeln("<red>Error:</red> An error occurred while trying to install the extensions");
exit;
}